Primefaces 6.0 (community edition); Mojarra: 2.2.8-18; Glassfish: 4.1; Java version: 1.8.0_102; jQuery
[EDIT: 2016-10-05 An answer using pure Primefaces+JSF for the following general question has now been self-answered (see detailed answer below):
Q1: How link to and target/open a p:tab within an p:accordionPanel within a p:tab within a p:tabview ?
The remainder of the descriptions of here below concern failed attempts at achieving the same end by using JavaScript pseudo-clicking on an outer p:tab (within a p:tabView) and then an inner p:tab (within a p:accordionPanel). The challenge (now basically academic) is how to synchronise these when both the p:tabView and p:accordionPanel are dynamic and non-cached.]
Under @ViewScoped I have a inner p:tab within a dynamic, non-cached p:accordionPanel within an outer p:tab within a dynamic, non-cached p:tabView. I have to be able to leave that inner tab and return to it from other pages.
IMPORTANT: the problem only happens for dynamic p:tabView and p:accordionPanel, and any acceptable answer must work for dynamic mode, as the real web app requires dynamic because it uses an expert system and the real content shown in any innermost tab may be impacted on by data from elsewhere
<h:body>
<h:outputScript name="js/changeTab.js" />
<h:form id="form" prependId="false">
<p:tabView id='tabview' dynamic="true" cache="false" widgetVar="widgetTabView">
<p:tab title="Outer Tab1" id="tabOuter1">Content of Tab1</p:tab>
<p:tab title="Outer Tab2" id="tabOuter2">
<p:accordionPanel
id="accordion"
activeIndex="null"
dynamic="true"
cache="false"
widgetVar="widgetAccordion"
>
<p:tab title="Inner Tab1" id="tabInner1">Content of inner Tab1</p:tab>
<p:tab title="Inner Tab2" id="tabInner2">Content of inner Tab2</p:tab>
</p:accordionPanel>
</p:tab>
</p:tabView>
<ui:param name="tabViewId" value="#{param['tabViewId']}"/>
<ui:param name="tabId" value="#{param['tabId']}"/>
<ui:param name="accordionId" value="#{param['accordionId']}"/>
<ui:param name="accordionTabId" value="#{param['accordionTabId']}"/>
I need the following to open up the innermost tab:
/faces/tabs_accordions.xhtml?tabViewId=tabview&tabId=tabOuter2&accordionId=accordion&accordionTabId=tabInner2
My JavaScript (included via changeTab.js), which imitates clicking on the outermost tab then the innermost accordion tab, does not catch the innermost accordionPanel+tab, the outermost tabView+panel has not made the targets available by the time I try to access the inner most parts.
I show one callback-based approach below first/ I have researched dozens of suggestions here for how to force consecutive synchronous functions, including using .done()
, Promises, and many other approaches (see edited examples below), but all of them seem to fail for the same reason, something to with how Primefaces is handling the first click() on the outermost tabView/tab.
Example using basic callbacks: From changeTab.js
:
function changeTabDynamicOuterCBwithParams(tabViewId, tabId, accordionId, accordionTabId, callback)
{
// ... log params omitted ...
$('#' + tabViewId + ' ul li a[href="#' + tabViewId + ':' + tabId + '"]').click();
callback(tabViewId,accordionId,accordionTabId);
}
/**
* Simulates clicking, and thus selection, of a p:tab within a p:accordionPanel.
*
* IMPORTANT: For h:form with prepend=false !
*
* NB: There is usually a p:tabView, p:tab, p:accordionPanel, p:tab.
* but the first p:tab does not affect the ultimate client-side id.
*
* @param {type} tabViewId Identifier of an outer ancestor p:tabView
* @param {type} accordionId Identifier of an immediate parent p:accordion
* @param {type} tabId Identifier of an immediate child p:tab
* @returns {undefined}
*/
function changeTabViewAccordionTabDynamic(tabViewId,accordionId, accordionTabId)
{
var i = 'changeTabViewAccordionTabDynamic';
console.log(i+": tabViewId("+tabViewId+")");
console.log(i+": accordionId("+accordionId+")");
console.log(i+": accordionTabId("+accordionTabId+")");
var id = tabViewId + ":"+accordionId+":"+accordionTabId;
console.log(i+": id("+id+")");
var div = $("[id=\'" + id + "'\]");
console.log(div.length);
// The id is on the DIV following the H3 to click on.
console.log(i+": div.length("+div.length+")");
var h3 = div.prev();
console.log(i+": h3.length("+h3.length+")");
console.log(i+": clicking header of inner tab within accordion within outer tab within tabview");
h3.click();
}
Simplified for testing (without checking for existence of query params etc.) the following is in the page with the tabView/tab/accordionPanel/tab:
changeTabDynamicOuterCBwithParams(
'#{tabViewId}',
'#{tabId}',
'#{accordionId}',
'#{accordionTabId}',
changeTabViewAccordionTabDynamic
);
The JS console gives:
changeTabDynamicOuterCB2: tabViewId(tabview)
changeTab.js:100 changeTabDynamicOuterCB2: tabId(tabOuter2)
changeTab.js:101 changeTabDynamicOuterCB2: accordionId(accordion)
changeTab.js:102 changeTabDynamicOuterCB2: accordionTabId(tabInner2)
changeTab.js:103 changeTabDynamicOuterCB2: clicking on outer tab within tabview THEN calling back on inner tab
changeTab.js:128 changeTabViewAccordionTabDynamic: tabViewId(tabview)
changeTab.js:129 changeTabViewAccordionTabDynamic: accordionId(accordion)
changeTab.js:130 changeTabViewAccordionTabDynamic: accordionTabId(tabInner2)
changeTab.js:133 changeTabViewAccordionTabDynamic: id(tabview:accordion:tabInner2)
changeTab.js:135 0
changeTab.js:137 changeTabViewAccordionTabDynamic: div.length(0)
changeTab.js:139 changeTabViewAccordionTabDynamic: h3.length(0)
Note how the DIV and H3 selection fail, because they are not yet available.
However, if I do it in the JavaScript console of a browser, after the first click()
is finished and the tabs with inner accordion/tabview have loaded properly, this selection works fine:
id='tabview:accordion:tabInner2'
var div = $("[id=\'" + id + "'\]");
var h3 = div.prev();
Result:
h3;
<h3 class="ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" role="tab" aria-expanded="false" aria-selected="false" tabindex="0">…</h3>
Using PrimeFaces widget vars also does not work for the same reason:
function changeAccordionTabPF(accordionWidget,index) {
PF(accordionWidget).select(index);
}
If I use this for the outer part first, then the inner part, the accordionWidget var is not yet available.
Q2: How can I target a click()
on the innermost tab by making sure it is available using synchronisation ?
An acceptable answer to this question must answer it for this specific Primefaces tab situation; please do not just refer me to any of the other stackoverflow answers about executing asynchronous functions in order using callbacks, deferred/done/next, or Promises, I have already read and tried dozens of them.
[EDIT: some more examples that don't work because the targets of the selection for the innermost tab of the accordion (within a tab within a tabView) are not available when the 2nd click simulation runs. Some are adapted from: https://stackoverflow.com/questions/39717433/how-pass-parameters-to-downstream-function-with-done-or-next]
function changeTabDynamicOuterDeferred(tabViewId, tabId)
{
$('#' + tabViewId + ' ul li a[href="#' + tabViewId + ':' + tabId + '"]').click();
return $.Deferred().resolve();
}
function changeTabViewAccordionTabDynamic(tabViewId, accordionId, accordionTabId)
{
var id = tabViewId + ":" + accordionId + ":" + accordionTabId;
var div = $("[id=\'" + id + "'\]");
// The id is on the DIV following the H3 to click on.
var h3 = div.prev();
h3.click();
}
Called as:
changeTabDynamicOuterDeferred(
'#{tabViewId}',
'#{tabId}'
).done(
function() {
changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}')
}
).fail(
function(err) {
console.log(err)
}
);
Everything seems to work (and the values for the targeted id pass fine) EXCEPT that by the time changeTabViewAccordionTabDynamic
the elements to be selected are not yet available (apparently not yet completely rendered from the click() in changeTabDynamicOuterDeferred
), despite the usage of done(
.
Same problem with this variation using a promise with then(
:
function changeTabDynamicOuterDeferredPromise(tabViewId, tabId)
{
$('#' + tabViewId + ' ul li a[href="#' + tabViewId + ':' + tabId + '"]').click();
return $.Deferred(
function (dfd) {
dfd.resolve()
}
).promise();
}
With changeTabViewAccordionTabDynamic
as above and called as:
changeTabDynamicOuterDeferredPromise(
'#{tabViewId}',
'#{tabId}'
).then(
function () {
changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}')
}
).fail(
function(err) {
console.log(err)
}
);
EDIT: I tried putting a while loop into the 2nd function for imitating a click on the inner tab within the accordionPanel (within a tab within a tabView), in part so I can see better what is available when. The effect of the click() on the outer tab (within the top-level tabView) can't be seen until after the long loop completes, and thus the selection always fails:
function changeTabViewAccordionTabDynamic(tabViewId, accordionId, accordionTabId)
{
var id = tabViewId + ":" + accordionId + ":" + accordionTabId;
var timer = 0;
var div = $("[id=\'" + id + "'\]");
while (div.length < 1 && timer++ < 100000) {
div = $("[id=\'" + id + "'\]"); // Proper code would make this DRY
console.log(div.length);
}
// The id is on the DIV following the H3 to click on.
var h3 = div.prev();
h3.click();
}
This occurs no matter which synchronisation strategy I use from above. And similarly, the primefaces widgetVar of the inner accordion and tab are not available.
EDIT: More attempts that fail. The following is adapted from How to execute JavaScript after page load?:
jQuery(document).ready(function () {
jQuery(document).ready(function () {
changeTabDynamic('#{tabViewId}','#{tabId}');
jQuery(document).ready(function () {
changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}');
});
});
});
The changes due to changeTabDynamic('#{tabViewId}','#{tabId}')
are still not available to the next function changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}')
by the time it runs, so the selectors fail.
I tried variations on using p:remoteCommand, with the same problem:
<p:remoteCommand oncomplete="changeTabDynamic('#{tabViewId}','#{tabId}')" autoRun="true" async="false"/>
<p:remoteCommand oncomplete="changeTabViewAccordionTabDynamic('#{tabViewId}','#{accordionId}','#{accordionTabId}')" autoRun="true" async="false"/>
Same problem.