'How to maintain accordionPanels in the same order?

I'm using accordionPanel in Primefaces. When I refresh the page the order changes! For example: I have 1,2,3 then they become 2,3,1 or 3,2,1 ! The order of the panels changes everytime I refresh the page!

         <p:panel toggleable="true" header="Mesure" rendered="#{not empty mesureController.varValues['structure']}">

    <p:accordionPanel value="#{mesureController.structureComposants.toArray()}" var="sc">
        <p:tab title="#{sc.nomComposant}" closable="false">
            <p:dataTable value="#{sc.plans.toArray()}" var="plan" id="plansTable" emptyMessage="">
                <p:column headerText="Plan">
                    <p:outputLabel value="#{plan.nomPlan}"/>
                </p:column>

                <p:column headerText="Image">
                    <p:lightBox styleClass="imagebox" id="lighbox1"
                                rendered="#{not empty plan.image}">
                        <h:outputLink
                            value="data:image/png;base64,#{ligneBean.getImageContentsAsBase64(plan)}"
                            title="#{plan.commentaire}">
                            <img src="data:image/png;base64,#{ligneBean.getImageContentsAsBase64(plan)}"
                                style="height: 120px; width: auto; margin-right: 10px;"/>
                        </h:outputLink>
                    </p:lightBox>
                </p:column>

                <p:column headerText="#{mesureController.varValues['typeMesure']}">
                    <ui:repeat value="#{plan.reperes.toArray()}" var="repere">
                        <p:panelGrid columns="5" layout="grid">
                            <p:outputLabel id="nom_repere" value="#{repere.nomRepere}"/>
                            <p:tooltip id="toolTipGrow" for="nom_repere" value="#{repere.commentaire}"
                                       position="left"/>
                            <p:outputLabel value="#{repere.repereConfig.temperature.minTemperature}"
                                           title="min temperature"
                                           rendered="#{mesureController.selectedTypeMesure.name().equals('TEMPERATURE')}"/>
                            <p:outputLabel value="#{repere.repereConfig.epaisseur.minEpaisseur}"
                                           title="min epaisseur"
                                           rendered="#{mesureController.selectedTypeMesure.name().equals('EPAISSEUR')}"/>
                            <p:inputText value="#{mesureController.currentMesure.structureDetails[repere]}"
                                         disabled="#{not mesureController.firstTask}"
                                         converter="javax.faces.Double" converterMessage="valeur numerique"
                                         style="width: 50px">
                                <p:ajax event="change" listener="#{mesureController.validateMesureValue}"
                                        update=":iwFormsForm">
                                </p:ajax>
                            </p:inputText>
                            <p:outputLabel value="#{repere.repereConfig.temperature.maxTemperature}"
                                           title="max temperature"
                                           rendered="#{mesureController.selectedTypeMesure.name().equals('TEMPERATURE')}"/>
                            <p:outputLabel value="#{repere.repereConfig.epaisseur.maxEpaisseur}"
                                           title="max epaisseur"
                                           rendered="#{mesureController.selectedTypeMesure.name().equals('EPAISSEUR')}"/>
                        </p:panelGrid>
                    </ui:repeat>
                </p:column>

            </p:dataTable>
        </p:tab>
    </p:accordionPanel>
    <ui:fragment rendered="#{mesureController.typeStructure.equals('FOUR')}">
        <ui:include src="graph.xhtml"/>
    </ui:fragment>
    <p:accordionPanel value="#{mesureController.fourZones.toArray()}" var="fz" activeIndex="0,1,2,3,4,5">
        <p:tab title="#{fz.description}">
            <p:dataTable value="#{fz.points.toArray()}" var="fpoint" id="fpointsTable" emptyMessage="" >
                <p:column headerText="point">
                    <p:outputLabel value="#{fpoint.description}  #{fpoint.position}(m)"/>
                </p:column>
                <p:column headerText="#{mesureController.varValues['typeMesure']}">
                    <p:panelGrid columns="3" layout="grid">
                        <p:outputLabel value="#{fpoint.temperature.minTemperature}" title="min temperature"
                                       rendered="#{mesureController.selectedTypeMesure.name().equals('TEMPERATURE')}"/>
                        <p:outputLabel value="#{fpoint.epaisseur.minEpaisseur}" title="min epaisseur"
                                       rendered="#{mesureController.selectedTypeMesure.name().equals('EPAISSEUR')}"/>
                        <p:inputText value="#{mesureController.currentMesure.fourDetails[fpoint]}"
                                     converter="javax.faces.Double" converterMessage="valeur numerique"
                                     style="width: 50px">
                            <p:ajax event="change" update=":iwFormsForm"
                                    listener="#{mesureController.validateMesureFourValue}"/>
                        </p:inputText>
                        <p:outputLabel value="#{fpoint.temperature.maxTemperature}" title="max temperature"
                                       rendered="#{mesureController.selectedTypeMesure.name().equals('TEMPERATURE')}"/>
                        <p:outputLabel value="#{fpoint.epaisseur.maxEpaisseur}" title="max epaisseur"
                                       rendered="#{mesureController.selectedTypeMesure.name().equals('EPAISSEUR')}"/>
                    </p:panelGrid>
                </p:column>
            </p:dataTable>
        </p:tab>
    </p:accordionPanel>

    <p:accordionPanel value="#{mesureController.tadZones.toArray()}" var="tz">
        <p:tab title="#{tz.description}">
            <p:dataTable value="#{tz.points.toArray()}" var="tpoint" id="tpointsTable" emptyMessage="">
                <p:column headerText="point">
                    <p:outputLabel value="#{tpoint.description}  #{tpoint.position}(m)"/>
                </p:column>
                <p:column headerText="#{mesureController.varValues['typeMesure']}">
                    <p:panelGrid columns="3" layout="grid">
                        <p:outputLabel value="#{tpoint.temperature.minTemperature}" title="min temperature"
                                       rendered="#{mesureController.selectedTypeMesure.name().equals('TEMPERATURE')}"/>
                        <p:outputLabel value="#{tpoint.epaisseur.minEpaisseur}" title="min epaisseur"
                                       rendered="#{mesureController.selectedTypeMesure.name().equals('EPAISSEUR')}"/>
                        <p:inputText value="#{mesureController.currentMesure.tadDetails[tpoint]}"
                                     converter="javax.faces.Double" converterMessage="valeur numerique"
                                     style="width: 50px">
                            <p:ajax event="change" update=":iwFormsForm"
                                    listener="#{mesureController.validateMesureTADValue}"/>
                        </p:inputText>
                        <p:outputLabel value="#{tpoint.temperature.maxTemperature}" title="max temperature"
                                       rendered="#{mesureController.selectedTypeMesure.name().equals('TEMPERATURE')}"/>
                        <p:outputLabel value="#{tpoint.epaisseur.maxEpaisseur}" title="max epaisseur"
                                       rendered="#{mesureController.selectedTypeMesure.name().equals('EPAISSEUR')}"/>
                    </p:panelGrid>
                </p:column>
            </p:dataTable>
        </p:tab>
    </p:accordionPanel>

    <p:panel toggleable="true" header="Validation des mesures" rendered="#{mesureController.isValidationMesures()}">
        <p:panelGrid id="tmp_validation2" columns="2" class="Wid100">
            <h:outputLabel value="Valider les mesure *"/>
            <p:selectOneMenu value="#{mesureController.varValues['valider_les_mesures']}" required="true">
                <f:selectItem itemLabel="Sélectionner" itemValue="#{null}"/>
                <f:selectItem itemLabel="Oui" itemValue="Oui"/>
                <f:selectItem itemLabel="Non" itemValue="Non"/>
                <p:ajax event="change" process="@this"/>
            </p:selectOneMenu>
        </p:panelGrid>
    </p:panel>

</p:panel>

So I need to know how to fix that, in order to maintain the order of panels in the same way as it is in the html.



Solution 1:[1]

The panels and their 'order' come from

value="#{mesureController.structureComposants.toArray()}"

Which is not PrimeFaces, nor java-ee, nor java but EL (so the tagging is sort of 'wrong'). But effectively the EL is a toArray() on an a field of an object. So you could (should) create a simple unit test and see what it does in plain java.

Then with 99.999% certainty you'll see the order is 'random' too, making this question a pure java one. Effectivly the order in Set is not guaranteed as it depends on the implementation (an OrderedSet is, others might not be)

So using an OrderedSet or an (implementation of) Array directly (e.g. an ArrayList) will 'fix' this issue

See also

Is a set's "toArray" deterministic?

Solution 2:[2]

The panels and their 'order' come from

value="#{mesureController.structureComposants.toArray()}"

Because to fix it, you must create a new property with this value but ordered. For example, if your variable structureComposants is a Set, you could try the following.

    List<Entity> structureComposantsList = structureComposants.stream().collect(Collectors.toList());
Collections.sort(structureComposantsList, (o1, o2) -> o1.getAttribute1().compareTo(o2.getAttribute1()));

Note: Change the Entity class for your class and initialize in your @PostContruct method

Finally, use the variable structureComposantsList in your code as

 value="#{mesureController.structureComposantsList}"

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 martosfre