1

Finally I've managed how to build a true dynamic table with the DOM-like approach in JSF when the data table structure is defined according to an external source. ( DataTable with dynamic columns exactly what I need) Basically, the following code is quite enough for me:

<p:layoutUnit position="west">
    <!-- 2 -->
    <p:tree value="#{tableViewsPageBean.root}" var="node" dynamic="true" cache="false"
                                        selectionMode="single" selection="#{tableViewsPageBean.selectedNode}">
        <!-- 3 -->
        <p:ajax event="select" listener="#{tableViewsPageBean.onNodeSelect}"/>
        <p:treeNode id="treeNode">
            <h:outputText value="#{node}"/>
        </p:treeNode>
    </p:tree>
</p:layoutUnit>
<p:layoutUnit position="center">
    <h:panelGroup>
        <h:panelGroup rendered="#{tableViewsPageBean.isRegeneratable}">
            <p:commandButton value="#{fu:$(languageBean, 'REGENERATE')}"
                             action="#{dynamicTableViewBean.regenerate}"/>
        </h:panelGroup>
        <!-- 1 -->
        <h:panelGroup binding="#{dynamicTableViewBean.dataTableGroup}"/> 
    </h:panelGroup>
</p:layoutUnit>

But there is a problem I currently do not know how to solve. If you take a look at the code above, you can see the numeric markers in the comments that represent the flow of the backing beans methods:

  1. binding="#{dynamicTableViewBean.dataTableGroup}"
  2. selection="#{tableViewsPageBean.selectedNode}"
  3. listener="#{tableViewsPageBean.onNodeSelect}"

This order is not good for me, because the binding of the h:panelGroup must be defined after a user changed the selection in the tree view. Simply saying, the very best flow would be:

  1. selection="#{tableViewsPageBean.selectedNode}" (previously 2)
  2. listener="#{tableViewsPageBean.onNodeSelect}" (previously 3)
  3. binding="#{dynamicTableViewBean.dataTableGroup}" (previously 1)

so I could build a data table component respecting the user selection in the tree. So is it a way to force another order of executing the methods above? PrimeFaces version used: 3.2

Thanks in advance. Your help is appreciated a lot.

Community
  • 1
  • 1
Lyubomyr Shaydariv
  • 20,327
  • 12
  • 64
  • 105

2 Answers2

2

It worked that way in old JSF 1.x on JSP, but not anymore in JSF 2.x on Facelets. But the invocation order should not matter. Just make the binding getter/setter a normal getter/setter and manipulate the dataTableGroup inside the listener method instead. It's after all the same object reference.

public void onNodeSelect(NodeSelectEvent event) {
    dataTableGroup.getChildren().add(...);
    // ...
}

You only need to add the update attribute to <p:ajax> in order to really update the bound panelgroup or one of its parents when the ajax request completes, otherwise you will effectively see no change in the view.

E.g.

<h:form id="formId">
    ....
    <p:ajax event="select" listener="#{tableViewsPageBean.onNodeSelect}" update=":formId:groupId" />
    ...
    <h:panelGroup id="groupId" binding="#{dynamicTableViewBean.dataTableGroup}"/> 
    ...
</h:form>

By the way, are you aware of PrimeFaces' <p:columns> component? It may be a better solution if all you want is dynamic columns. That solution didn't exist in flavor of any UIComponent back in the dark JSF 1.x ages, that's why the awkward binding workaround was been applied.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for the reply. :) I would like to clarify: what does "normal" getter/setter mean? I also don't understand clearly: can it allow me to build a new data table each time after a user clicks a tree node? I'm still afraid of the order: and if I manipulate the data table inside the listener, how should expose the change outside to the facelet? (Considering the PrimeFaces dynamic columns: yep, I'm aware of them and find them a really weak solution, and I have to specify each column in a different way unlikely to what offers.) – Lyubomyr Shaydariv May 02 '12 at 16:38
  • Just let it get and set the property. Just do `return dataTableGroup;` in getter and `this.dataTableGroup = dataTableGroup;` in setter. Nothing else. When you want to create/manipulate the component on initial pageload, do the job in bean's (post)constructor instead. when you want to create/manipulate the component on action(listener) events, do the job in exactly those methods instead wherein you just reference the `dataTableGroup` property. Do not do the creation/manipulation job in the getter/setter. It worked in old JSF 1.x on JSP because of a different view handling mechanism. – BalusC May 02 '12 at 16:40
  • Ehm, ok... But I still cannot realize how can it help to make the bound `binding="..."` to be executed afterwards. I still have that binding executing first, and then the PF `onNodeSelect` is invoked... – Lyubomyr Shaydariv May 03 '12 at 10:37
  • I'm not understanding your concrete problem anymore. Have you fixed the code as per my answer? I.e. do nothing different in getter/setter and do the job in action listener method instead. This way the order of the getter call doesn't matter. – BalusC May 03 '12 at 15:58
  • yes, exactly as you suggested. Probably I asked the question in a not so clear way or so. Recently I've found a pic depicting the JSF 2.0 life cycle http://java-tutorial.ch/java-server-faces/jsf-2-lifecycle . According to this pic, the `binding` is executed first, and `onNodeSelect` is executed afterwards... – Lyubomyr Shaydariv May 03 '12 at 16:13
  • Are you using Mojarra or MyFaces? Have you configured the JSF state saving different from defaults in web.xml? – BalusC May 03 '12 at 16:15
  • Mojarra 2.1.7 (+ PrimeFaces 3.2). `javax.faces.PARTIAL_STATE_SAVING` is set to `true`. – Lyubomyr Shaydariv May 03 '12 at 16:18
  • I copied your example into a Tomcat 7.0.27 playground environment with Mojarra 2.1.7 and PrimeFaces 3.2, I could reproduce your problem and then I realized that you forgot the `update` attribute in ``. Once I added it, it worked for me. I updated the answer. – BalusC May 03 '12 at 20:07
  • you're truly awesome and legendary! That works exactly how I wanted it to work and you really saved a lot. Thank you very and very much! :) – Lyubomyr Shaydariv May 04 '12 at 13:19
1

You cannot force binding expression to be executed afterwards, because it's a feature of JSF lifecycle - to set bindings at the very beginning of request processing (during phase "Restore View").
I would also recommend to trigger model-based tree updates from action listeners (i.e. in #{tableViewsPageBean.onNodeSelect} and/or #{dynamicTableViewBean.regenerate}), because they are executed on phase "Invoke Application" (after submitted values have been converted, validated, and applied to the model objects) just before "Render Response".
You can still use PanelGroup's reference obtained from binding expression and manipulate with its children in Java code.

aleksa.dp
  • 246
  • 2
  • 3