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 /> 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.