5

The view:

<h:form ...
  <p:dataTable value="#{myBean.list}" var="data" ...
     <p:column ...
        <h:commandButton action="#{controller.method(data.id)}" />
     </p:column>
  </p:dataTable>
</h:form>

The controller:

@ApplicationScoped
public class Controller {
   public String method(final Long dataId) {
        /* Do business */
        return URL_WITH_REDIRECT;
   }
}

The producer

(using the @ViewScoped CDI annotation as described here)

@ApplicationScoped
public class Producer {
   @Named @ViewScoped @Producer
   public MyBean getMyBean() {
        final MyBean bean = new MyBean();
        bean.list = new ArrayList<Data>(); // where Data has a Long id field
        /* Do business and populate list */
        return bean;
   }
}

The problem & its scenario

  1. GET the page
    1. The bean is produced
    2. View is rendered
    3. Response sent to browser
  2. Click the button
    1. Data is POSTed to server
    2. Phases 1-4 are executed without any issue, and that use @ViewScoped beans as expected
    3. Phase 5: controller.method is called with data.id and accesses beans generated at 1.1
    4. Method returns redirect String
    5. !! The producer is called again !! - we're still in APPLICATION_INVOCATION phase, but after the actual method call
  3. Browser receives redirect
  4. GET next page ...

The half-"donkey" solution that works:

In short: on click, copy the id outside the datatable, and trigger a click on a submit button.

On the h:commandButton inside the table column added :

onclick="$('input[id*=selectedDataId]').val('#{data.id}'); $('button[id*=callMethod]').trigger('click');"

Outside the table:

<h:inputHidden id="{selectedDataId}"binding="#{selectedDataId}"/>
<p:commandButton type="submit"
                 id="callMethod"
                 label="Hidden button"
                 action="#{controller.method(selectedDataId.value)}"/>

At the end it works, but I was not able to figure out what causes the first & base approach to reinitialize the view scoped bean. Looking at the stack trace (see below) it seems like it is rebuilding the rows.

Question:

Does anyone have an explanation, and maybe caveats to look out for regarding this issue?

Stack trace

Where: getPipelinecheckSearchResults is the call for retrieving the list that backs the table, that causes the producer to be called

stack trace

What I've already looked through:

I've read following articles / SO questions without gaining any better understanding on why the above (1st) solution works as it does.

ViewScoped bean is recreated everytime I click on commandButton in my dataTable

Why does @PostConstruct callback fire every time even though bean is @ViewScoped? JSF

How can I pass selected row to commandLink inside dataTable?

http://balusc.blogspot.de/2010/06/benefits-and-pitfalls-of-viewscoped.html

https://java.net/jira/browse/JAVASERVERFACES-1492

Community
  • 1
  • 1
Matyas
  • 13,473
  • 3
  • 60
  • 73
  • oh nice, I think I recently observed a similar behaviour – Steve Oh May 27 '13 at 21:25
  • did you try to call a method of __myBean__ instead of __controller__ from _commandButton action_? – Steve Oh May 27 '13 at 21:30
  • did you read this http://stackoverflow.com/questions/7675319/session-scoped-managed-bean-and-actionlistener – Steve Oh May 27 '13 at 21:36
  • 1. Tried calling method off of `myBean`. Same issue. (Though I wouldn't put logic into models, since they should be dumb). 2. Yes read it, but that refers to `actionListener`, and in my case, the re`@Produc`tion is **after** the APPLICATION_INVOCATION, so `action` finds its way correctly – Matyas May 28 '13 at 07:26

2 Answers2

1

I've found some sources for jsf/primefaces/ee-api/glassfish etc. to debug the behavior, so here's the answer:

In short

If a component:

  • Triggers an action (controller.method) that causes redirect
  • And is placed in a datatable
  • And the datatable generates its rows based on a @ViewScoped bean

Then:

  • After the controller.method invocation the @ViewScoped bean that the datatable depends on will be regenerated (with all its dependencies of course)

Tested: In version 2.1.7 of JSF. Looked in sources of 2.1.19, and I'd expect same behavior there.

Details

For those who cry out loud in lonely summer nights asking: "Why?"

The chain of "Events" that lead to this behavior (with references to sources):

  1. The user clicks a button inside a table row.
  2. Data is POSTed to the server
  3. Phases 1-4 go as planned
  4. APPLICATION_INVOCATION
    1. The click event is received by JSF. Important: The click event that references the button is wrapped in an event that contains information about the table & the row number the click happened on. For simplicity: rowEvent & clickEvent
    2. The event is "broadcasted" in the tree of the components @ UIViewRoot:794
    3. javax.faces.UIData The grandparent of org.primefaces.component.datatable.DataTable backing p:datatable starts processing the event @ UIData.broadcast(FacesEvent)
      1. The broadcast method first saves the index of the last selected row
      2. Then it selects the one specified by the rowEvent
      3. Dispatches the clickEvent on the child UIComponent, in our case on the Button
        1. Everything is well & fine, and the event starts getting processed by ActionListener.processAction(ActionEvent)
          1. This in turn invokes controller.method which returns a redirect String and things begin to go downhill
          2. At the end of the method the redirectString is processed by a NavigationHandler
            1. This one seeing that we're about to redirect quickly clears the ViewMap removing all @ViewScoped beans from it at line 179. Which if we think about it is kind of logical, since we're on our way out.
      4. On arriving back in UIData.broadcast which
        • having broadcasted the inner event,
        • not knowing that some inner event caused a redirect and everything it does will be thrown to the garbage (because of 302)
        • as a last action, tries to select the row whose index it saved at step 4.3.1
      5. And of course to select a row, it needs to know the data for it, and this is where the @ViewScoped bean(s) needed by the table get regenerated.

THE END

Notice

Though I haven't tested I'd expect the same behavior h:datatable, p:accordionPanel, p:carousel, p:galleria, p:dataGrid etc. In short every component that subclasses UIData and doesn't provide a redirect - aware broadcast method.

Matyas
  • 13,473
  • 3
  • 60
  • 73
  • Hi. Same problem here, but I think it is a Primefaces issue. If I use plain JSF tags (I mean h:datatable and so on), all works ok. Did you find any solution? Primefaces even doesn't work if "action method" returns "null" (which wouldn't fire the recreation of the Viewscoped bean. – choquero70 Nov 26 '13 at 21:17
  • Even more... my problem is when I click the p:commandbutton to edit a row of the 2nd, 3rd, etc pages of the primefaces datable. If I edit a row of the first row, it works ok, the Viewscoped bean isn't recreated. – choquero70 Nov 26 '13 at 21:24
  • Adding log info to trace, I've discovered that the "action method" isn't called when I click the p:commandbutton of the 2nd or following pages of the datatable. – choquero70 Nov 26 '13 at 21:59
0

Unless I'm understanding this incorrectly you're using a bean that is scoped to the view somehow (Seam 3, CODI, or your own custom scope you've written). You're fine as long as the JSF life cycle is operating on the same view (which would be a correct assumption), but you're surprised when you change the view id that you get a new instance of the view scoped bean?? The whole purpose of the view scope is to keep things in the same view state in JSF, as soon as you tell JSF to go to a different view id it creates a new view state. Sounds to me what you're actually looking for is the conversation scope.

LightGuard
  • 5,298
  • 19
  • 19
  • The used `@ViewScope` is the one [described here][1] [1] http://www.verborgh.be/articles/2010/01/06/porting-the-viewscoped-jsf-annotation-to-cdi/ – Matyas May 28 '13 at 17:02
  • On the other hand: If you check the **Scenario** above, the problem is that the producer is called before the *actual* redirect that is sent to the browser (between steps **2.4** and **3**. So we are still in the `POST` request, that originates from the click, and which successfully uses `@ViewsScoped` beans produced in step **1.1**. + I am aware that once navigating away from the page, or losing or not using the right `ViewState` id on the client side leads to losing all `@ViewScoped`. Unfortunately this is not the problem, since phases 1-4 and the method call works and finds these beans. – Matyas May 28 '13 at 17:12
  • Moreover: Everything works as expected using the workaround described above (If I move the `h/p:commandButton` outside the `p:dataTable`) – Matyas May 28 '13 at 17:16
  • Hm, I wonder what the AJAX call is sending. It's possible something is missing from the request. – LightGuard May 28 '13 at 20:44
  • Same behavior with/without AJAX + checked the POSTed data, and contains the correct ViewState (that's why everything in the first 4 phases work) and don't cause producer executions – Matyas May 28 '13 at 21:36
  • Which phase is changing the viewid? I'd expect that to be during invoke_application. – LightGuard May 28 '13 at 22:33
  • I've managed to scrap together sources for my ee stack and debugged the problem. See my answer & thank you for your time. – Matyas May 29 '13 at 21:20