How to keep p:tabView tab active after roundtrip to external site - jsf

How can i keep active tab after i get redirected from anoter site?
On my interface i would like to return information on a tab window after a user has done credit card authorization using a third party service.
I have frontend done by Primefaces.
Tab view on xhtml:
<p:tabView id="mytabs" styleClass="mystyle" style="min-height: 600px;" activeIndex="#{backinBean.getActiveTab}">
<p:tab title="tab" titleStyleClass="mystyle">
<ui:include src="reports.xhtml"/>
</p:tab>
</p:tabView>
</p:tab>
<p:tab id="anothertab" title="Another tab" titleStyleClass="myclass"
<ui:include src="2ndtab.xhtml"/>
</p:tab>
</p:tabView>
My backinBean:
private Integer activeTab = 0;
public Integer getActiveTab() {
return activeTab;
}
public void setActiveTab(Integer activeTab) {
this.activeTab = activeTab;
}
reports.xhtml contains a button which directs user to credit card authorization service.
After the authorization the user gets redirected back to my site, where information should be displayed on the same tab, which has the button for auth service. Information is displayed correctly, but client doesn't keep the tab active. How to achieve this?
Tested solution from this question didn't work. After redirect page shows the first tab on index.xhtml
index.xhtml tabs
<p:tabView id="mytabs" styleClass="mystyle" style="min-height: 600px;">
<p:ajax event="tabChange" listener="#{backinBean.onTabChange}" />
<p:tab title="tab" titleStyleClass="mystyle">
<ui:include src="firstTab"/>
</p:tab>
</p:tabView>
</p:tab>
<p:tab id="wantedActiveTab" title="Another tab" titleStyleClass="myclass"
<ui:include src="2ndtab.xhtml"/>
</p:tab>
</p:tabView>
backinBean:
private Integer activeTab = 0;
public void onTabChange(TabChangeEvent event) {
TabView tabView = (TabView) event.getComponent();
activeTab = tabView.getChildren().indexOf(event.getTab());
}

Almost there. You should not have removed activeIndex="#{backinBean.activeTab}" from your p:tabView. Also, there is a typo in your EL. You should not use getActiveTab to get activeTab, simply use activeTab.
Also, in this case, the scope of your bean should be set to #SessionScoped.
See also:
How does EL #{bean.id} call managed bean method bean.getId()
How to choose the right bean scope?

Related

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 On-Demand Loading tabbed panel component does not work

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";
}

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.

Cannot show dialog in Primefaces RequestContext.execute() call

I have a tabview in which I want to refresh contents of one specific tab whenever user selects that tab. I also want modal dialog to pop up while tab is being refreshed.
Here is tabView with tabChange ajax event handler
<p:dialog widgetVar="statusDialog" modal="true" draggable="false" minimizable="false" appendToBody="true" closable="false" header="Processing..." resizable="false" maximizable="false">
<p:graphicImage library="assets" name="ajax-loader.gif"></p:graphicImage>
</p:dialog>
<p:tabView id="tabview" orientation="top" dynamic="false">
<p:ajax event="tabChange" listener="#{bean.tabChangeListener}"></p:ajax>
<p:tab title="tab1">
<ui:include src="/WEB-INF/views/tab1.xhtml"/>
</p:tab>
<p:tab title="tab2">
<p:remoteCommand actionListener="#{bean.refreshData}" update="someTab2ElementID" name="refresh" global="true" onstart="statusDialog.show()" oncomplete="statusDialog.hide()"/>
<ui:include src="/WEB-INF/views/tab2.xhtml"/>
</p:tab>
</p:tabView>
Here is tabChangeListener:
public void tabChangeListener(TabChangeEvent event) {
if ( event.getTab().getId().equalsIgnoreCase("tab2") ) {
RequestContext.getCurrentInstance().execute("refresh()");
}
}
The refresh remoteCommand is being called as expected, but my statusDialog is never shown. If the same remoteCommand is triggered by a button click the statusDialog appears. There are no errors in JavaScript console.
Why is statusDialog not shown when remoteCommand is triggered by RequestContext.execute() and how can I make it appear? I even tried adding statusDialog.show() to execute() but it didnt help.
I figured it out. Here is the solution:
<h:outputScript>
var blockCount=0;
function showStatus() {
if (blockCount==0) statusDialog.show();
blockCount++;
};
function hideStatus() {
blockCount--;
if (blockCount==0) statusDialog.hide();
};
</h:outputScript>
<p:ajaxStatus onstart="showStatus();" onsuccess="hideStatus();" onerror="blockCount=0;statusDialog.hide();errorDialog.show();"/>
<p:remoteCommand actionListener="#{bean.refreshData}" update="someTab2ElementID" name="refresh" global="true" onstart="showStatus()" oncomplete="hideStatus()"/>
Apparently the reason why my status dialog was not being shown is that another Ajax call finished and called statusDialog.hide() while my refresh() thing was going on. The JS code above maintains a counter of how many Ajax calls are in progress, and will only hide the status dialog when all calls have finished. Works as intended now!
If you want to show dialog when a specific tab is changed, you can inform a js function about, the tab selected control that it is 2 or not by adding a callback parameter via ajax response:
public void tabChangeListener(TabChangeEvent event) {
if ( event.getTab().getId().equalsIgnoreCase("tab2") ) {
RequestContext.getCurrentInstance().addCallbackParam("index", 2);
}
}
And use:
<p:ajax event="tabChange" listener="#{bean.tabChangeListener}"
oncomplete="showOrNot(xhr,status,args)"></p:ajax>
This will call the js function and it's going to control that the changed tab is 2 or not:
function showOrNot(xhr,status,args) {
if(args.index==2) {
statusDialog.show();
}
}
Or different approach is defining a value and using visible property of the p:dialog:
public void tabChangeListener(TabChangeEvent event) {
if ( event.getTab().getId().equalsIgnoreCase("tab2") ) {
this.condition=true;
}
}
And <p:dialog visible="#{bean.condition}"/>. And maybe you need to focus on your first approach and you can give an ID to p:dialog and update it via p:remoteCommand:
<p:remoteCommand actionListener="#{bean.refreshData}" update="someTab2ElementID dialog" name="refresh" global="true" onstart="statusDialog.show()" oncomplete="statusDialog.hide()"/>
Don't forget to give the exact client ID of the dialog in the update property of p:remoteCommand.

primefaces accordion panel within single form - validation only on opened tab

I have checked around but I didn't found a clear example on how to submit the accordion using a single form.
The problem I find is when I want to process (validate) only fields of the opened tab.
The structure of my page is as follows:
<h:form>
<p:accordionPanel activeIndex="#{bean.selectedTab}">
<p:ajax event="tabChange" listener="#{bean.tabChangeListener}"/>
<p:tab title="Tab1" id="t1">
<p:inputText id="reqField1" required="true"/>
</p:tab>
<p:tab title="Tab2" id="t2">
<p:inputText id="reqField2" required="true"/>
</p:tab>
</p:accordionPanel>
<p:commandButton update="list" immediate="true" actionListener="#{bean.addRecord}"/>
</h:form>
method:
#SessionScoped
public class Bean{
public void tabChangeListener(AjaxBehaviorEvent evt){
AccordionPanel panel = (AccordionPanel) evt.getComponent();
// getLoadedTabs() returns an empty list
String currentlyLoadedTabId = panel.getLoadedTabs().get(this.selectedTab).getClientId();
}
}
Is there anyway to specify the id of the currently opened tab as part of the process attribute in p:commandButton tag?
UPDATE
When Tab1 is open, on submit, I want to be validated only reqField1 and not reqField2. Viceversa, when Tab2 is open, I want to be validated only reqField2 and not reqField1.
Try this.
Bind the activeIndex of the accordionPanel to a backing bean int attribute
activeIndex="#{myBean.selectedTab}"
Get the id of the currently selected tab using the bound activeIndex. To do this in your actionListener, get a handle on the accordion panel
EDIT: Since your actionListener has been set to immediate, the activeIndex and selectedTab will need to be updated via ajax. Add the following <p:ajax/> to your accordionPanel
<p:ajax event="tabChange" listener="#{bean.tabChangeListener}" />
In your backing bean, you'll now have
public void (AjaxBehaviorEvent evt){
AccordionPanel panel = (AccordionPanel) evt.getComponent();
String currentlyLoadedTabId = panel.getLoadedTabs().get(this.selectedTab).getClientId();
}
Using the the id you got from 2 above, you can include that in the JSF list of things to be rerendered using ajax on the server side. In the same action listener method:
FacesContext.getCurrentInstance().getPartialViewContext().getRenderIds().add(currentlyLoadedTabId);
Alternatively, you might just consider placing a command button in each of those tabs and having a form in each tab as well. This way, each command button processes only it's own parent form

Resources