1

I have a JSF 2.0 facelets view containing of a search form, an ui:repeat for displaying results, and a pager. It is backed by a request bean. The form post consists of the current page, and the search criteria. The pager shows next and previous page links depending on the position in the dataset, and the numberof results from the query with the search criteria from the form. So the component tree is dependant on request attributes. For example, I'm using the rendered attribute on the next page link like this

<h:commandLink action="#{listBean.nextPage}" rendered="#{listBean.hasNextPage}" >...

When the user clicks this link, the POST request contains the current page, and the search criteria. The problem is, that the component tree is already build in RESTORE_VIEW. In this moment, because request attributes have not been applied yet, I can neither tell on which page the user is currently, or how many records are in the dataset because i don't have the search criteria. So listBean.hasNextPage evaluates to false in this phase. This seems to cause that the CommandLink disappears from the component tree. After APPLY_REQUEST_VALUES I'm able to construct my count query. With this information and the current page, I could calculate listBean.hasNextPage. However, it doesn't seem to get evaluated again until RENDER_RESPONSE. The action does not get called in INVOKE_APPLICATION at all. Also there is no error, which is annoying.

It works when replacing rendered with c:if. c:if is only evaluated in RENDER_RESPONSE once, and the component is in the tree in the first phases by default. I don't really like it, because in the (rare, admitted) case, that the dataset count changes so that there is in fact no next page, it would still call the action and the user would be in an illegal page. Also I understand using JSTL tags in Facelets is commonly advise against. I don't know why.

Is there any trick to have rendered evaluation delayed until after APPLY_REQUEST_VALUES? There must be a way to use this attribute on properties that are dependant on the current request. Just FYI, this is a portlet 2.0 app on Liferay with the JBoss portlet bridge, but I guess it's a generic JSF problem.

Thanks in advance for any answers. I hope I'm just overlooking something, I'm still learning JSF - I can't be that hard to write a pager, right :-)

Paul Schyska
  • 666
  • 8
  • 17
  • Is this exact copy of your view? You don't have # char in your expressions. Also I am not sure "rendered" attribute affects including component in the tree. c:if on the other hand is evaluated only during tree construction, it's a tag handler not a component. The tree can be somehow rebuilt before rendering (possibly in part). – mrembisz Jul 25 '11 at 14:38
  • I've written with during my morning coffee from the top of my head, thanks for the pointer :-). The # are in the view. – Paul Schyska Jul 25 '11 at 18:59

1 Answers1

2

From your description it seems that you think you need the new dataset at the start of the new request (the postback), but this is not the case.

During RESTORE_VIEW up until INVOKE_APPLICATION you need to have the dataset available that was used to render the page on which the user has just clicked, not the one that is going to be used to render the current request.

The easiest way to achieve this is to make your backing bean @ViewScoped and hold a reference to the dataset via an instance variable. After the postback, JSF will then automatically retain the 'old' dataset and use this for the first part of the life cycle. Then in INVOKE_APPLICATION you have all the data to load your next page and thus set the 'new' dataset and boolean for hasNextPage.

RENDER_RESPONSE will then happen with the new values and will render your new page.

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
  • Arja, thanks for your detailed answer. It makes perfectly sense to me for this simple case (the pager).Still, imagine I want to re-evaluate if the request is still valid when making the second request. It could be that the size of the dataset changed after the page was rendered, so that there is no next page anymore. I admit this is very a constructed example but I try to grok the JSF way to do it. Is there some alternative which would work with a request scoped bean? – Paul Schyska Jul 25 '11 at 19:06
  • Also, I'm not sure if the ViewScope would work when considering multiple browser windows/tabs/browser back button, because it's basically session state, right? I try to avoid storing stuff in session for this reason. A purely request-based approach would work as expected in all these situations. – Paul Schyska Jul 25 '11 at 19:08
  • ViewScope is definitely not any kind of sugar for the session state. It's a scope that consists only between postbacks of the same page and is unique for that particular postback. It's absolutely save to use between multiple windows or tabs. You're completely right for avoiding storing stuff in the session scope as much as possible, but again, the view scope is absolutely not the same as the session scope. – Arjan Tijms Jul 25 '11 at 19:17
  • > It could be that the size of the dataset changed after the page was rendered, so that there is no next page anymore. - But that's perfectly okay. For the first phases you need the exact same data that was used to render in the previous request. This is to make sure among others that it's legal that the action method is being called. Then in the action method you fetch the next page, and surprise, there's no more data. You now simply set the reference to null or an empty collection and when the rendering starts it will simply display nothing. – Arjan Tijms Jul 25 '11 at 19:25
  • Optionally your action method can recognize the situation of a changed dataset (request for next page, but no more data) and add a faces message that tells the user about this. – Arjan Tijms Jul 25 '11 at 19:27
  • > Is there some alternative which would work with a request scoped bean? - I personally think you should not have to want this, but if you really insist you can use Tomahawk's `t:saveState` tag. This basically stores the data in the same location, but contrary to putting the entire bean in the view scope you can use it to put individual data items there. But again, don't fear the view scope. Every JSF page already uses it, since many components themselves already keep state in this scope. @ViewScoped just allows you to have a backing bean with the same scope as the components on your page. – Arjan Tijms Jul 25 '11 at 19:33
  • My quick research left the impression, that the ViewScope lives until you navigate to another view (page). For it to work like a between-request-scope (having the values from the last request until UPDATE_MODEL_VALUES 4, and the being overwritten with the current request parameters), every property in the bean has to be set in every request. I think that's doable. But I don't think it's save in multi-tab scenario. Image a user opens a second tab, pages a few times there, then returns to the first tab and pages again. Both tabs share the same scope, so the result will be unexpected – Paul Schyska Jul 26 '11 at 12:24
  • On the other hand, I will post the current page on every request, so this time it wouldn't behave unexpected :-). Still the property you described earlier: _During RESTORE_VIEW up until INVOKE_APPLICATION you need to have the dataset available that was used to render the page on which the user has just clicked, not the one that is going to be used to render the current request._ wouldn't hold with multiple tabs. Thanks again for you thorough explanations! Im marking this as accepted. – Paul Schyska Jul 26 '11 at 12:27
  • Still have another idea: Could the following be implemented with a JSF custom scope: Keep scope data form the last request until before Phase 4 UPDATE_MODEL VALUE, then discard whole scope and rebuilt it from the bindings. – Paul Schyska Jul 26 '11 at 12:28
  • > But I don't think it's save in multi-tab scenario. - trust me, there's probably nobody in the world more concerned about multi-tab scenarios than I am ;) ViewScope is 100% save between tabs. It works with a view ID (GUID) that is unique for a given rendering and that is used to index the data partition that holds the view state. If it wasn't save, JSF wouldn't work at all since as I mentioned your normal components are already using this to store data. – Arjan Tijms Jul 26 '11 at 12:42
  • >Both tabs share the same scope - No, this is absolutely not the case. ViewScope is per INSTANCE of a view. You seem to be thinking that all views called foo.xhtml share the same scope, but this is not how it works. Every instance (tab/window) of foo.xhtml has it's own view scope. Compare this with session scope, not all users (browsers) that do something with it share the session right? Every user instance has it's own session scope (own unique JSESSIONID). – Arjan Tijms Jul 26 '11 at 12:58
  • I was under the impression that its one bean per view per user. Didn't know of the view id mechanism. So essentially JSF creates a new copy of the view scope, when it detects a new tab? How does it detect it? Ot can it defere from the view id, that something was submitted from another tab or after browser back button. – Paul Schyska Jul 26 '11 at 13:11
  • >JSF creates a new copy of the view scope, when it detects a new tab? - When you open a link in a new tab, it happens via a GET request. Every non-faces (non-postback) request starts a new instance of the view scope. Postbacks retain the scope, but every postback also creates a new GUID for the newly rendered version. Check the value of `javax.faces.ViewState` in the HTML source of your rendered pages. This is the key into the view state. – Arjan Tijms Jul 26 '11 at 13:37