Reputation: 13702
I have the following structure
<h:form>
<!-- some elements -->
<p:accordionPanel id="outer" multiple="true" var="node" dynamic="false" value="#{model.nodes}">
<p:tab id="outerId">
<p:accordionPanel id="inner" multiple="true" dynamic="false" var="child" value="#{node.children}">
<p:tab id="innerId">
<!-- iterated components -->
</p:tab>
</p:accordionPanel>
</p:tab>
</p:accordionPanel>
</h:form>
Then I tried to use in ajax postback & rerender from within the inner tabs & from the outside of the whole hierarchy:
<f:ajax event="click" render="@form" execute="@form" />
or
<p:ajax process="@form" partialSubmit="false" update="@form" />
The problem was that the active indexes got persisted only for the outer accordion, but not the child accordions (though looking at the POST data, child accordion active indexes are sent as well). Am I doing something wrong or I shouldn't expect this to work out of the box?
What would work
On other hand I know that I could manage manually the active indexes by providing for example a field on model and every node, that would persist this data. (Didn't test but after digging through a lots of pf posts/so pages that's what I'd expect)
<p:accordionPanel activeIndex="#{model.activeIndex}"...
<p:accordionPanel activeIndex="#{node.activeIndex}"...
Can anyone confirm that this second approach is the only way around? Or I'm doing something wrong in the first case?
Primefaces 3.4.2
Glassfish stack 3.1.2.2
UPDATE 05.04.2013
The latter approach doesn't work either. Because on ajax POST node.activeIndex receives the value ""
(and only the root active index is set correctly)
Data scenario (POST data details)
javax.faces.partial.ajax=true javax.faces.source=j_idt106:j_idt271:1:j_idt121:j_idt110:0:j_idt113:featureRepeater:11:featureCheckboxP javax.faces.partial.execute=gridDetailPage javax.faces.partial.render=gridDetailPage javax.faces.behavior.event=valueChange javax.faces.partial.event=change gridDetailPage=gridDetailPage j_idt106:j_idt271:1:j_idt121:j_idt110_active=0,1 // INNER OPEN TABS j_idt106:j_idt271_active=0,1 // OUTER OPEN TABS javax.faces.ViewState=4232962649695633063:-8633977119414123467
javax.faces.partial.ajax=true javax.faces.source=j_idt106:j_idt271:1:j_idt121:j_idt110:0:j_idt113:featureRepeater:0:featureCheckboxP javax.faces.partial.execute=gridDetailPage javax.faces.partial.render=gridDetailPage javax.faces.behavior.event=valueChange javax.faces.partial.event=change gridDetailPage=gridDetailPage j_idt106:j_idt271:1:j_idt121:j_idt110_active=0 // INNER OPEN TABS j_idt106:j_idt271_active=0,1 // OUTER OPEN TABS javax.faces.ViewState=4232962649695633063:-8633977119414123467
Upvotes: 1
Views: 3636
Reputation: 13702
Solution proposed by andyba:
"In order to ensure that the activeIndex values are being propagated you need to tickle the accordionPanel, you do this by simply adding an empty p:ajax tag (<p:ajax/>
) inside the top level accordionPanel and this work."
Unfortunately that did not work (Tried every combination outer, inner, both + with & without activeIndex).
But this did
no need to set activeIndex, which is OK, because on the server side I'm not interested in the indexes):
in the outer:
<p:ajax event="tabChange"/>
(which basically is a restricted form of your solution
In the inner accordion I've added:
<p:ajax event="tabChange" process="@this" update="@form"/>
Meaning that in my case only a whole form rerender is necesarry on changing the inner tab status
Unfortunately this adds ugly flickering because of the complex page structure. I was hoping I could resolve it without ajax on tabchange.
Also found on primefaces forum
At the end I've ended up brewing my own solution. That is:
Having the constraint that we display the same number of accordions in the same order, we can save their status in a string containing 1 for every open and 0 for ever closed accordion. So the solution is as follows:
var CONTAINER_SELECTOR = 'selector of container containing all accordions';
// called whenever we click on an accordion (see assignment in init)
var saveAccordionsState = function () {
var state = '';
$(CONTAINER_SELECTOR + ' .ui-accordion-header').each(function (i, el) {
// for every accordion put a 0 or 1 in the state string
state += ($(el).hasClass('ui-state-active') ? '1' : '0');
});
// put the state string in an input that will be submitted to the server
$('#accordionState').val(state);
};
// this method is called right after accordions are rendered
var init = function () {
// retrieve the saved state
var state = $('#accordionState').val();
// get reference to all accordions
var $accordions = $('.ui-accordion-header');
// turn jquery effects off for quick restoration (otherwise we'll have glitches
$.fx.off = true;
if (state === '' || $accordions.length !== state.length) { // if no state defined
// open all accordions by simulating a click on them
$('.ui-accordion-header').not('.ui-state-active').trigger('click');
} else { // otherwise
for (var i = state.length - 1; i >= 0; --i) {
var $accordion = $($accordions[i]);
var c = state[i];
// for every accordion that is in the opposite state
if ((c === '1' && !$accordion.hasClass('ui-state-active')) ||
(c === '0' && $accordion.hasClass('ui-state-active'))) {
$accordion.trigger('click'); // simulate a click on its header to toggle it
}
}
}
// turn effects back on
$.fx.off = false;
// save the state
saveAccordionsState();
// assign the save state method to every accordion header click
$('.ui-accordion-header', $featureTable).click(saveAccordionsState);
};
The xhtml looks something like this:
<h:form>
<p:accordionPanel ... >
<p:tab ... >
<h:panelGroup ... >
<p:accordionPanel ... >
<p:tab ... >
<h:panelGroup ... />
</p:tab>
</p:accordionPanel>
</h:panelGroup>
</p:tab>
</p:accordionPanel>
<h:inputHidden id="accordionState" value="#{bean.accordionState}"/>
<script type="text/javascript">
init();
</script>
</h:form>
Upvotes: 2