LadyAni
LadyAni

Reputation: 43

Primefaces CommandButton in DataTable transfers random object

I'm quite new to PrimeFaces and JSF in general and I couldn't find a topic with similar situation. I checked I have no nested forms. Seems like I'm running into some weird behaviour - at least I have no idea why it does what it does ;)

Let's say I have a List. Class 'Module' has a List. My intention is to show data in a table, nicely grouped by module and elements in rows under it. Seemed like subtable was my wish come true, but I found out it's only for reporting issues, so it's not selectable. Unfortunately I need to know the selection to run actions like 'Delete' or 'Edit' on an module. I tried to place a button in subtable facet with an actionlistener setting the current object in backing bean and then opening a dialog to offer further actions (on selected module). I first thought it works, but then noticed that pressing the same button, a randomly module object was transfered into backing bean.

<p:dataTable id="displayModules" var="displayModuleBean"
     value="#{displayModuleController.displayModuleBeans}"
     emptyMessage="Es existieren keine Module für diese Kampagne.">
     <p:row>
        <p:column headerText="Vermarkter" />
        <p:column headerText="Platzierung" />
        <p:column headerText="Umfeld" />
        <p:column headerText="Format(e)" />
        <p:column headerText="Startdatum" />
        <p:column headerText="Enddatum" />
        <p:column headerText="Abrechnungsart" />
     </p:row>

     <p:subTable id="displayModuleElements" var="displayModuleElement"
        value="#{displayModuleBean.moduleElements}"
        sortBy="#{displayModuleBean.module.name}" sortOrder="descending">
        <f:facet name="header" style="vertical-align: middle">
             #{displayModuleBean.module.name}

           <p:commandButton alt="Modul bearbeiten" icon="ui-icon-pencil"
              style="float:right"
              actionListener="#{displayModuleController.setSelectedDisplayModuleBean(displayModuleBean)}"
              oncomplete="displayModuleEditDialog.show();"
              update=":tabview:displayModulesDialogForm:editDisplayModules">
           </p:commandButton>
        </f:facet>

        <p:column headerText="Vermarkter">
           <h:outputText value="#{displayModuleElement.advertiserName}" />
        </p:column>
     </p:subTable>
  </p:dataTable>

Backing Bean (DisplayModuleController, SessionScoped):

public void setSelectedDisplayModuleBean(ModuleBean selectedDisplayModuleBean)
{
    this.selectedDisplayModuleBean = selectedDisplayModuleBean;
    this.setSelectedDisplayModuleName(this.selectedDisplayModuleBean != null ? this.selectedDisplayModuleBean.getModule().getName()
        : null);
    this.addFacesMessage(FacesMessage.SEVERITY_INFO, "Set selected ModuleBean: ",
        this.selectedDisplayModuleBean != null ? this.selectedDisplayModuleBean.getModule().getName() : "keins");
}

I even tried a workaround with an Accordion Panel and Datatables on each tab, but I'm ending at the same weird behaviour. I also tried adding a button in each row to transfer the selected element itself (not the parent module), but that works exactly the same randomly way.

What the hell am I missing? Would be great if someone could help me to get on the right way ;)

I'm using: PrimeFaces 4.0, MyFaces 2.1.13, Apache Tomcat 7.0.42

LG, Ani

Upvotes: 2

Views: 2569

Answers (2)

BalusC
BalusC

Reputation: 1108642

EL method arguments are evaluated at the moment the action method is decoded during form submit. They are not evaluated at the moment the JSF component tree is built, or that its HTML output is generated.

In order to ensure that the right EL method argument value is being evaluated, you need in this specific case ensure that the model behind #{displayModuleController.displayModuleBeans} is exactly the same during processing the form submit as it was during rendering (displaying) the form.

In order to achieve that, the backing bean must be view scoped and the getter of that model must be absolutely free of any business logic. The model must be prepared and filled in an one-time init/action method such as @PostConstruct in case you'd like to populate it "on page load" or in an action(listener) method in case you'd like to populate it "on submit" beforehand.

Otherwise, the model may be off because you for example first sorted and then reloaded the model from the DB by unnecessarily performing a DB call in the getter method like as many starters do.


As a completely different alternative, in case you can't ensure the integrity of the model and thus you risk it to be incompatibly changed in between displaying the form and processing the form submit, then you could use <f:param> to pass a static parameter. It will represent exactly that value as it is during dusplaying the form (during render response) and not during processing the form submit.

E.g.

<p:commandButton ... actionListener="#{displayModuleController.setSelectedDisplayModuleBean}">
    <f:param name="id" value="#{displayModuleBean.id}" />
</p:commandButton>

whereby you set it via <f:viewParam name="id">, or @ManagedProperty("#{param.id}"), or externalContext.getRequestParameterMap().get("id") and convert back to concrete DisplayModuleBean based on a DB find() by ID. See also How can I pass selected row to commandLink inside dataTable?

Upvotes: 1

Kuba
Kuba

Reputation: 2089

If you are dealing with Entities You might as well pass unique ID as an argument to the BackingBean, write a select method that takes ID as a parameter and sets selected Object as an Instance of your @WindowScoped BackingBean. Then you access your instances like so:

 #{myBean.instance.propertyName}

This way you'll be sure that you use the correct instance.

Upvotes: 0

Related Questions