Primefaces On-Demand Loading tabbed panel component does not work - jsf

I am trying to do a Primefaces On-Demand Loading tabbed panel component which has a inputTextArea component inside it. I follow this tutorial Primefaces Tutorial.
My .xhtml form looks like this:
<h:form>
<h:outputText
value="Write a comment" />
<p:tabView dynamic="true" cache="true">
<p:tab title="Possitive">
<h:panelGrid columns="2" cellpadding="10">
<p:inputTextarea rows="5" cols="130" />
</h:panelGrid>
</p:tab>
<p:tab title="Negative">
<h:panelGrid columns="2" cellpadding="10">
<p:inputTextarea rows="5" cols="130" />
</h:panelGrid>
</p:tab>
</p:tabView>
<p:commandButton value="Publish"></p:commandButton>
</h:form>
But these inputTextBox are always loaded, nevertheless i did not click on them. Can they be loaded only on demand and how to do this?
Thanks in advance for the help!

Primefaces builds JSF tree for all tabs. Both inputTextarea(s) are created on server side. It can be solved by ui:include. Put the content of p:tab into extra file and use ui:include to fetch the content:
<p:tabView>
<p:tab title="Possitive">
<ui:include src="#{tabViewBean.getTabSource('tabPositive')}"/>
</p:tab>
<p:tab title="Negative">
<ui:include src="#{tabViewBean.getTabSource('tabNegative')}"/>
</p:tab>
</p:tabView>
TabViewBean holds active tab name and is used for resolving tab sources as well. If a tab is NOT ACTIVE then the method tabViewBean.getTabSource() returns another file with EMPTY content:
public String getTabSource(String tabName)
{
return tabName.equals(activeTabName) ?
getTabSourceForTabName(tabName) :
"EmptyTab.xhtml";
}
public String getTabSourceForTabName(String tabName)
{
if ("tabPositive".equals(tabName)) return "TabPositive.xhtml";
if ("tabNegative".equals(tabName)) return "TabNegative.xhtml";
}
You should have EmptyTab.xhtml somewhere. Set listener in p:tabView to listen the tab change event :
<p:tabView listener="#{tabViewBean.tabChangeListener}">
tabChange listener sets the name of active tab:
public void tabChangeListener(UIComponentBase tabView)
{
activeTabName = getTabNameByIndex(tabView.getActiveIndex());
}
public String getTabNameByIndex(int index)
{
if (i==0) return "tabPositive";
if (i==1) return "tabNegative";
}

Related

How to ID a tab inside p:accordionPanel for controller functions?

Please refer to my previous question.
I need to be able to identify the entity associated with each tab (or row) in a p:accordionPanel. E.g. if the rows correspond to entities whose IDs are 1, 2, 3, when I click on each tab, I want to trigger an event to be processed by the controller that uses the ID of that tab. IOW, I need a reference pointer between each tab and the controller.
The ID atribute, as can be seen from my previous post, did not work. How can I establish identity reference between each tab in the view and the controller?
You can try to use the activeIndex attribute of the accordionPanel which is the index of the active tab.
<p:accordionPanel activeIndex="#{playgroundController.activeIndex}">
<p:ajax event="tabChange" listener="#{playgroundController.onTabChange()}" />
<p:tab title="Godfather Part I">
<h:panelGrid columns="2" cellpadding="10">
<h:outputText
value="The story begins as Don Vito Corleone..." />
</h:panelGrid>
</p:tab>
<p:tab title="Godfather Part II">
<h:panelGrid columns="2" cellpadding="10">
<h:outputText value="Francis Ford Coppola's legendary..." />
</h:panelGrid>
</p:tab>
<p:tab title="Godfather Part III">
<h:panelGrid columns="2" cellpadding="10">
<h:outputText value="After a break of more than 15 years..." />
</h:panelGrid>
</p:tab>
</p:accordionPanel>
Bean:
private String activeIndex = "";
public void onTabChange() {
logger.debug("onTabChange : activeIndex : {} ", activeIndex);
}
While clicking on one of the tabs it prints:
09:38:55,822 DEBUG [PlaygroundController] onTabChange : activeIndex : 0
09:38:56,625 DEBUG [PlaygroundController] onTabChange : activeIndex : 1
09:38:57,426 DEBUG [PlaygroundController] onTabChange : activeIndex : 2

Switch to tab in tabView with invalid input

The problem is really simple. I have a tabview with multiple tabs:
<h:form>
<p:tabView id="tabView">
<p:tab>
<p:inputText required="true"></p:inputText>
</p:tab>
<p:tab>
<p:inputText required="true"></p:inputText>
</p:tab>
</p:tabView>
<p:commandButton value="submit" action="#{myBB.submit}" update="tabView"/>
</h:form>
What I would like is to switch to the tab with missing field when the client-side validation fails. I thought that this must be a common use-case for form in tabs, but I cannot find any solution.
The only thing I can think of is call some oncomplete action on the submit button which would go through the DOM and find the invalid components, but it seems overly complicated for a thing which I believe should be default behavior.
So I've found a way to do that. After the ajax completes, I created an oncomplete method to search through the tabs and select the correct one.
First I had to modify the source a bit(just added ids and widgetVar):
<h:form>
<p:tabView id="tabView" widgetVar="tabViewWv">
<p:tab id="tab1">
<p:inputText required="true"></p:inputText>
</p:tab>
<p:tab id="tab2">
<p:inputText required="true"></p:inputText>
</p:tab>
</p:tabView>
<p:commandButton ... oncomplete="switchToInvalidTab()"/>
</h:form>
and the javascript:
function switchToInvalidTab() {
var lastIndex = 0;
var tabParent = '#mainForm\\:tabView\\:';
var tabs = ['tab1', 'tab2']; //can add more, but the order must be the same as in form
tabs.some(function (tabName){
if ($(tabParent + tabName)) { //tab may not exist in my app
if ($(tabParent + tabName).find(':input').hasClass('ui-state-error')){
PF('tabViewWv').select(lastIndex);
return true;
}
lastIndex++;
}
return false;
});
}
There are more tabs in my app and some of them doesn exist sometimes and this code works for all cases. Maybe it helps someone

Primefaces ring items are superposed on each other when placed into a tabView

I have an xhtml page where I have put one <p:tabView> containing 3 <p:tab>.
In each <p:tab> I have one <p:ring>. Like this:
<p:tabView>
<p:tab id="tab1">
<p:ring/>
</p:tab>
<p:tab id="tab2">
<p:ring/>
</p:tab>
<p:tab id="tab3">
<p:ring/>
</p:tab>
</p:tabView>
The ring of the first tab (tab1) is displaying normally.. but for the 2 others (rings of tab2 and tab3), the ring items are displayed one over the other. And it's only when I click on some of its items that the original disposition is retrieved.
He is a screenshot of the abnormal display:
and the normal one (obtained when I click on an item):
Is there a problem in the ring combined with the tabView or am I missing something?
**EDIT: **
Here is the code of my xhtml page (the code of one tab actually):
<p:tab id="tabExec" title="Exécutable">
<p:growl id="displayE" showDetail="true" />
<h:form id="formDev">
<h:panelGrid columns="2">
<h:outputText value="Nom de l'exécutable: *" />
<p:inputText value="#{developerController.nomLivrable}"
label="Nom du livrable" required="true" />
<h:outputText value="Exécutable: *" />
<h:panelGroup layout="block">
<p:fileUpload fileUploadListener="#{ftpFileUpload.upload}"
description="Sélectionnez un fichier"
update=":BigTabView:displayE :BigTabView:formDev:outFileNameE"
style="margin-right: 20px;" required="true" label="Exécutable" />
<h:outputText id="outFileNameE"
value="#{ftpFileUpload.getFileName()}" />
</h:panelGroup>
<h:panelGroup layout="block">
<p:commandButton value="Valider"
actionListener="#{developerController.storeFile(developerController.nomLivrable)}"
update=":BigTabView:displayE :BigTabView:formDevs:historiqueDevsRing"
style="margin-right: 5px;" />
<p:commandButton value="Annuler" type="reset" />
</h:panelGroup>
</h:panelGrid>
</h:form>
<h:form id="formDevs">
<p:ring id="historiqueDevsRing"
value="#{developerController.executableList}" var="exec">
<p:graphicImage value="#{resource['images/exe.png']}"
height="100px" />
<p:commandLink value="#{exec.nomlivrable}"
actionListener="#{developerController.connectToFtpServer()}"
oncomplete="window.open('ftp://#{exec.emplacementlivrable}'); return false;"
update=":BigTabView:displayE">
</p:commandLink>
</p:ring>
</h:form>
</p:tab>
The problem resides on the fact that p:ring uses absolute position. In order to prevent this behavior (not the positioning, the "stacked" ring), you need to implement a tabChange event handler as follows, AFTER adding the forms:
<body>
<p:tabView>
<p:ajax event="tabChange" listener="#{bean.onTabChange}"/>
<p:tab id="tab1">
<h:form>
<p:ring value="#{bean.list}" var="st">
<p:outputLabel value="1#{st}"/>
</p:ring>
</h:form>
</p:tab>
<p:tab id="tab2">
<h:form>
<p:ring value="#{bean.list}" var="st">
<p:outputLabel value="2#{st}"/>
</p:ring>
</h:form>
</p:tab>
<p:tab id="tab3">
<h:form>
<p:ring value="#{bean.list}" var="st">
<p:outputLabel value="3#{st}"/>
</p:ring>
</h:form>
</p:tab>
</p:tabView>
</body>
In your managedBean:
#ManagedBean(name="bean")
public class BeanView implements Serializable {
private static final long serialVersionUID = 1775631010811130942L;
private List<String> list;
#PostConstruct
public void init() {
list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
}
public void onTabChange(TabChangeEvent event){
Tab selected = event.getTab();
for (UIComponent comp : selected.getChildren())
if (comp instanceof HtmlForm)
for (UIComponent comp2 : comp.getChildren())
if (comp2 instanceof Ring){
//first solution, primefaces update
RequestContext.getCurrentInstance().update(comp2.getClientId());
//second solution, jquery click
FacesContext faces = FacesContext.getCurrentInstance();
String separator = UINamingContainer.getSeparatorChar(faces) + "";
String usingId = comp2.getClientId().replaceAll(separator, "\\\\\\\\" + separator);
RequestContext.getCurrentInstance().execute("$('#" + usingId + " li').last().click();");
}
}
public List<String> getList() {
return list;
}
This example works and the rings get updated on every tab change, the answer is based on the comment OP made.
This however, will bring your p:ring into a default state. For example, if my ring contains 5 values as:
data0 data1 data2 data3 data4
the first item displayed on the ring is data0. If I select data3, change tabs, reopen the same tab with the ring inside then data0 will be displayed. To prevent this behavior, you need to implement a selection into your managedBean (f:setPropertyActionListener is your best bet).

Dynamically Disable/Enable of p:tab in p:accordionPanel

I want only single Tab(not entire accordion panel) to be enabled or disabled dynamically.
I tried following approach, here is my code:
index.xhtml
<p:accordionPanel id="accordionPanelId" widgetVar="accordionPanelWidget">
<p:tab id="tab1" title="First Tab">
<h:outputText value="Contents of Tab1"/>
<h:form>
<p:commandButton value="Enable Tab2" action="#{tabBean.buttonAction}" update=":accordionPanelId:tab2"/>
</h:form>
</p:tab>
<p:tab id="tab2" title="Second Tab" disabled="#{tableBean.disableTab}">
<h:outputText value="Contents of Tab2"/>
</p:tab>
</p:accordionPanel>
TabBean.java
#ManagedBean
#ViewScoped
public class TabBean implements Serializable {
private boolean disableTab=true;
public boolean isDisableTab() {
return disableTab;
}
public void setDisableTab(boolean disableTab) {
this.disableTab = disableTab;
}
public void buttonAction()
{
disableTab = false;
}
}
With the above approach Tab2's content is showing but the Tab Header is still freezes,
May be this is not a god approach to achieve this...
Please suggest any other way or changes in this approach.
EDIT : The above approach is working fine if I update entire Accordion panel as follows:
<p:commandButton value="Enable Tab2" action="#{tabBean.buttonAction}" update=":accordionPanelId"/>
But I don't want to update all the tabs.
You need to bind the disabled attribute of the tab to the disableTab backing bean variable that you'll toggle on the server side. To reflect the changes you made on the server side, you then ajax-update the <p:accordionPanel/> on the client side. Using your current setup, the only changes you'll need to make is:
Bind the disabled attribute to the backing bean variable you've setup
<p:tab id="tab2" title="Second Tab" disabled="#{tabBean.disableTab}">
<h:outputText value="Contents of Tab2"/>
</p:tab>
Automatically switch to the tab after enabling it with the javascript API
<p:tab id="tab1" title="First Tab">
<h:outputText value="Contents of Tab1"/>
<h:form>
<p:commandButton value="Enable Tab2" oncomplete="accordionPanelWidget.select(2)" action="#{tabBean.buttonAction}" update=":accordionPanelId:tab2"/>
</h:form>
</p:tab>
You have to update all the accordionPanel in order to enable/disable tabs. In this case you'll have to update accordionPanelId. However, you can use dynamic="true" attribute in accordionPanel to avoid update the parts that aren't rendered.

commandlink action doesn't fire inside p:accordion

I have command link inside an accordion inside of primefaces wizard and they're all in the same form - I don't have nested forms and yet the action of the command link is not fired.
Here's my page:
<ui:define name="content">
<h:form id="createSubject">
<p:wizard flowListener="#{createWizard.flowListener}" widgetVar="wiz" showNavBar="false">
<p:tab id="personal" title="#{createWizard.tab1Title}">
....
</p:tab>
<p:tab id="conclusion" title="#{createWizard.tab2Title}">
<p:panel header="#{createWizard.panel2Title}">
<p:accordionPanel value="#{createWizard.similarSubjects}" var="subject">
<p:tab title="#{subject.VPrenom} #{subject.VNom}">
<h:panelGrid columns="2" cellpadding="5">
<h:outputText value="#{createWizard.firstName}: " />
<h:outputText value="#{subject.VPrenom}" />
<h:commandLink value="#{createWizard.completeSheet}" actionListener="#{createWizard.completeSubjectSheet(subject.VIdPool)}"/>
</h:panelGrid>
</p:tab>
</p:accordionPanel>
</p:panel>
</p:tab>
</p:wizard>
<p:commandButton value="#{createWizard.nextButtonTitle}" onclick="wiz.next()" style="float:right;"/>
</h:form>
</ui:define>
and my backing bean method:
public void completeSubjectSheet(String subId){
mk.sdc.helpers.Link link = mk.sdc.helpers.Link.getLink();
try {
FacesContext.getCurrentInstance().getExternalContext().redirect(link.mkLink("/POOL/view.jsp?V_ID_POOL="+subId));
} catch (IOException ex) {
Logger.getLogger(CreateWizard.class.getName()).log(Level.SEVERE, null, ex);
}
}
Any ideas? Thanks.
Because actionListener can't take any arguments, you have to use action attribute for commandLink.
EDIT
Can you use h:link or h:outputLink for that?Like
<h:link outcome="/POOL/view.jsp?V_ID_POOL=#{subject.id}" />

Resources