BootsFacesJSF

Comments in JSF Files

Earlier this day, a developer opened a bug ticket for BootsFaces. <b:panelGrid />, they said, is buggy. Adding a comment between the columns corrupts the layout, they said.

Actually, that’s true, but I wouldn’t call it a bug of BootsFaces. It’s a peculiarity of JSF. We could solve it in BootsFaces. At least, most of the time. I’m sure there are corner cases we’ll always miss unless we start to parse the code as thoroughly as a compiler. That’s possible, but it doesn’t match the general idea of JSF. Even worse, we can only solve the bug for BootsFaces components. It still exists for standard JSF, PrimeFaces, ButterFaces, and every other JSF framework you combine with BootsFaces. So I prefer to explain the bug instead of solving it.

Setting the stage

In order to understand the bug, we have to have a look at the implementation of JSF itself. Consider this code snippet:

<b:panelGrid columns="2">
  <b:badge value="row 1, column 1"></b:badge>
  <b:badge value="row 1, column 2"></b:badge>
  <b:badge value="row 2, column 1"></b:badge>
  <b:badge value="row 2, column 2"></b:badge>
</b:panelGrid>

This example consists of two rows, each consisting of two badges.

Adding comments to the code

Now let’s add comments to the source code, as one does in order to make it more readable or to deactivate parts of the code:

<b:panelGrid columns="2">
  <b:badge value="row 1, column 1"></b:badge>
  <!-- next column, still in the top row: -->
  <b:badge value="row 1, column 2"></b:badge>
  <!-- hide this one! b:badge value="row 2, column 1"></b:badge> -->
  <b:badge value="row 2, now column 1"></b:badge>
</b:panelGrid>

Did you expect this result?

If you did, you’re a seasoned JSF programmer. Even so, you should notice a difference between the captions and the real position of the badges. The second badge should be in the second column, but it isn’t. In fact, there’s no such thing as a second column. Or rather, it’s invisible.

The JSF component tree

Looking at the JSF component tree sheds light on the problem. The component tree of the first example looks like so:

The algorithm of <b:panelGrid /> is very simple. It generates a column for each child of the JSF DOM tree. After completing a row, it starts a new row. This results in the nice two-by-two design of the first example.

The second source code still distributes the children of <b:panelGrid /> into the two columns, creating a new row once a row is completed. The problem is that JSF files are also XML file. This, in turn, means that comments aren’t really ignored. They are nodes of the XML tree. More precisely, they are comment nodes. So the JSF component tree contains two unexpected nodes. The diagram displays them in red color:

Now you can probably guess what’s going on. The <b:panelGrid /> renders five nodes. The first node is rendered as a badge. The second node is a comment node. There’s no renderer for that, so BootsFaces only renders an empty (and invisible) cell. After that, it starts the new row. That’s the badge meant to be in the second column of the first row. Too bad this cell has already been occupied by the comment. So the badge is rendered in the next available cell. That’s the first column of the second row.

The next badge has been commented. Obviously, the programmer wanted it to be ignored completely, but again, it’s a comment node in XML. So BootsFaces renders an empty cell. After that, it starts a new row. That’s why the last cell is rendered in the third row.

<ui:remove />

<ui:remove /> to the rescue. The official solution of JSF is to comment code not by XML comments, but by <ui:remove />. This approach works well. The layout of the panelgrid ceases being corrupted.

There’s a catch. If there’s an EL expression inside <ui:remove />, it’s executed. Calling methods of a JSF bean from commented code causes all kinds of side effects. So my general recommendation is to combine both <ui:remove /> and the standard XML comments. Granted, the code looks ugly. That’s JSF for you. The bottom line is that is works without surprises:

<b:panelGrid columns="2">
  <b:badge value="row 1, column 1"></b:badge>
  <ui:remove>
    <!-- next column, still in the top row: -->
  </ui:remove>
  <b:badge value="row 1, column 2"></b:badge>
  <ui:remove>
    <!-- hide this one! b:badge value="row 2, column 1"></b:badge> -->
  </ui:remove>
  <b:badge value="row 2, now column 1"></b:badge>
</b:panelGrid>

Configure JSF to remove comments automatically

There’s another option. You can add a parameter to the web.xml to remove every XML parameter from the HTML code. Note that this removes every comment from the HTML page.

<context-param>
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
    <param-value>true</param-value>
</context-param>

Wrapping it up

Sometimes it shows that JSF is based on XML. Comments are one such example. Most of the time you won’t notice. You comment an element, and it’s gone. The only exception is tabular components, such as panelGrids and data tables. So in general I recommend a double comments: remove it from the JSF tree using <ui:remove />, and add an XML comment to prevent the EL expressions from being evaluated.

Dig deeper

Discussion on StackOverflow about comments
StackOverflow discussion on <ui:remove />

5 thoughts on “Comments in JSF Files

  1. Why can’t b:panelGrid check for the type of the children and render only those of type b:badge?

    If children are found that are not of the expected type they are ignored or an exception is thrown or a warning logged, whatever.

    I did something similar once in a backing bean of a composite component, so you should be able to do the same in your case.

    1. I’m not sure it’s possible to detect comment nodes. If someone puts simple HTML into the panelGrid, they probably want it to be rendered. So we can’t restrict the rendering to JSF components. We also need to render the XML nodes containing simple HTML.

      At the point, the JSF API gets a bit odd. As far as I know, there’s no official API to inspect such an XML nodes. If I’m not mistaken, the MyFaces implementation looks different from the Mojarra implementation (as one should expect if there’s no official API). Plus, solving the problem for a single component means that the problem still exists for all the other components, such as the standard JSF components and the PrimeFaces components.

  2. Question is: do they really want to have those rendered? I mean I don’t know BootsFaces at all, but what I get from your article is that the panelGrid component only renders correctly when b:badge is an immediate child of it and no other node types exist inside of panelGrid. If other nodes exist, the rendering is at best weird and most of the time not what a reasonable developer would expect.

    So do you really want to support edge cases which lead only to corrupt rendering?

    Of course, if component.getChildren() doesn’t return comment children then you can’t do anything.

    1. Good point. Sometimes you have pairs of simple HTML tags and JSF components. For instance, a label plus an input field. This case is still sort of a corner case, but not an exotic one.

  3. While it would be possible there is no need for it to do so.
    It is intended behaviour – this is how the component tree works and the result is intended behaviour of the panelGrid component.

    It’s the same odd thing as the intended behaviour of Ajax and h:messages – if you don’t update your message component on ajax you are not able to see if phase3 or phase4 failed.
    Very odd but if know it you know how to handle it.

Leave a Reply

Your email address will not be published.