4

I'm currently fighting a little bit with JSF. I want to display a list of items. Each item can be displayed with 2 facelets (one if the item is editable and one otherwise).

Code snippet:

<div>
   <c:forEach items="#{bean.itemList}" var="item">
      <c:choose>
         <c:when test="#{bean.isEditable(item.id)}">
            <ui:include src="#{item.editableFaceletPath}>
               <ui:param name="item" value="#{item}" />
            </ui:include>
         </c:when>
         <c:otherwise>
            <ui:include src="#{item.normalFaceletPath}>
               <ui:param name="item" value="#{item}" />
            </ui:include>      
         </c:otherwise>
      </c:choose>
   </c:forEach>
</div>

This works fine as long as I don't set an item to editable. However if I have 3 items: item1, item2 and item3, and I set item1 to editable, I'll get item2, item2, item3 displayed.

I understand why it doesn't work but I have absolutely no idea how I could implement it otherwise. Has anyone an idea how?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
metalhamster
  • 105
  • 1
  • 10
  • What does not work? Do you have an error? Specify a little more! – Aritz Oct 09 '13 at 15:15
  • 1
    Have you tried using `` instead of ``? Also, wrap your data inside a `` that will also generate a `
    `.
    – Luiggi Mendoza Oct 09 '13 at 15:16
  • 1
    @Luiggi: Using instead of is not possible, because the EL for the is evaluated during the view built time and isn't valid since it relies on a variable that is made available by the during the view render time. – metalhamster Oct 09 '13 at 15:22

2 Answers2

2

See this link for an explanation about common mistakes regarding the evaluation in different lifecycle phases of JSF.

The problem is that your JSTL tags will only be evaluated once, when the view is being built. If you change the item to editable, it won't have an effect anymore on the component tree which was built already before.

The solution is to replace the <c:choose><c:when><c:otherwise> with two <ui:fragment>s with rendered="#{bean.isEditable(item.id)}" and rendered="#{not bean.isEditable(item.id)}".

That way you will have BOTH branches of the component tree in your view, but at render time only one of them will be evaluated and displayed because of the rendered attribute.

But this whole construct will only work as long as you do not change the list of items. Because adding or removing items will not effect the <c:forEach> anymore. In that case you would have to do it completely without <ui:include> and go with <ui:repeat> and a combination of <ui:fragment rendered="#{...}">.

Roger Keays
  • 3,117
  • 1
  • 31
  • 23
noone
  • 19,520
  • 5
  • 61
  • 76
  • After all, I'll do it this way. Although the code won't be as nice as I'd wished it would be, I have decided for this solution because many people had advised me against rebuilding the view. – metalhamster Oct 14 '13 at 15:32
2

If you change the model where JSTL depends on by a postback action, then you need to tell JSF to rebuild the view so that JSTL get re-executed before the view get rendered. The JSTL tags are by design namely not re-executed with new conditions during view render time.

public void someActionMethodWhichSetsItemEditable() {
    // Do actual job here.
    item.setEditable(true);

    // Then rebuild the view (re-executes all JSTL).
    FacesContext context = FacesContext.getCurrentInstance();
    String viewId = context.getViewRoot().getViewId();
    context.setViewRoot(context.getApplication().getViewHandler()
        .createView(context, context.getViewRoot().getViewId()));
}

Beware: all view scoped beans are garbaged and reconstructed this way. So if you intend to keep some data alive in the request and turning them into request scoped beans isn't an option, then let the view scoped bean put the data in the request scope before rebuilding the view and let it read the data from the request scope in postconstruct.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Hhmmm, this seems to be a pretty extreme hack to me, because it couples the business logic and JSF specifics and for everyone who didn't write that code, it would be completely impossible to understand why this is being done there... still +1 because it sure is a valid solution. – noone Oct 09 '13 at 19:17
  • @noone: That's payoff of using request based EL variables for building of ``. – BalusC Oct 09 '13 at 19:18
  • @BalusC: Thanks, it works. But only if I set items to editable and vice versa. But if I add a new item it won't be displayed (just the old ones). However, not even reloading the page seems to help. Only if close the webpage and open it again, or if I navigate (by clicking on a link) again to that view that displays the items, then all are shown - including the new one. – metalhamster Oct 10 '13 at 09:18
  • @metalhamster You would have to do the same thing when adding/removing items from the list. The reason why it works if you reopen the browser is because of the GET request that happens. In this case the view will be rebuilt. Only in case of postbacks you have a problem. – noone Oct 10 '13 at 11:58
  • @noone: I use the same method for refreshing the page when a add an item. However rebuilding the view as above doesn't seem to result in a GET request (at least I don't see one in my logs)... Actually I realized that setting an item to editable still doesn't work... It seems to me that I just tested an special case that works. – metalhamster Oct 10 '13 at 13:11
  • @metalhamster I didn't say that rebuilding the view triggers any kind of request. I just said that it is ALSO rebuilt when a GET request happens. Have you tried my solution? In theory it should work as well. – noone Oct 10 '13 at 13:14
  • @noone Unfortunately I can't try your solution because I must have s and I must use request based EL variables for them. – metalhamster Oct 10 '13 at 13:27