Friday, August 3, 2012

Programming UIData(HtmlDataTable or ui:repeat)

On important concept first:  Build time VS Render time.

Build Time is the time when components are added to the tree declaratively (using facelet) or programmatically. In JSF, the code is like this parentComponent.getChildren().add(childComponent). These codes are called to construct a UI component tree.  At the Render time, the rendering engine converts your component tree into HTML which is displayed by browser.

Usually, there is one-to-one correspondence between component and backing bean value. For example, you have one UIInput for user name, and one UIInput for password. So each form input in browser side has a match in server component.  For these kinds of components, you really do not need to understand what are build time and render time. Programmatically, you can hookup your component with backbean directly. For example,

UIInput.getAttributes.put("mybean", beanForThisInput).
or
<h:inputText...>
<f:attribute name="myBean", value="#{bean}"></f:attribute>
</h:inputText>

Here the beanForThisInput is an object associated with this input. This piece of code establishes their relationship by attaching the beanForThisInput to input attributes. You can aslo attach actionListener like this

UICommand.addActionListener(mylistener).
<h:commandButton actionListener="#{bean.do}"></h:commandButton>


UIData(HtmlDataTable and ui:repeat) is the exceptional to this rule. Suppose a data table is used to display the user name for 10 users. The user name is displayed through a UIOutput component. So the tree structure is like this UIData->UIColumn->UIOutput. There is only one UIColumn component. Under UIColumn there is only one UIOutput although there are 10 user names for display. In the build time, the code is like this UIColumn.getChildren().add(UIOutput). This piece code runs only once so UIColumn has only one UIOutput component.  At the render time, the UIComlumn->UIOutput will be rendered 10 times: one for each user name.  At build time, you could give a value to UIOutput like this:
UIOutput.setValue(user.getName());
However, this approach does not work for component under UIData. Why?

First, this code is called at build time only once. It of course does not work for 10 user names.
Second, when it comes to renderer time, the control is passed to renderkit. There is no hook to 1) pass you the current user and let you call setValue programmatically.
Remember, this piece of code is called 10 times, one time for each user.

For component tree under UIdata, the tree is like a template. The template will be looped multiple times. So how can you set the value for UIOutput correctly? Find the current object from the context. The context is provided by UIData through "var" variable.

Declaratively approach.
<h:outputText value="#{user.name}"><h:outputText>
Here the "user" is  value for "var" attribute in h:dataTable. 
Programmatically, approach
HtmlDataTable uidata=new HtmlDataTable();
uidata.setVar("user");
...

HtmlColumn colum=new HtmlColumn();
uidata.getChildren().add(column);
FacesContext fc=FacesContext.getCurrentInstance()
HtmlOutputText text=new HtmlOutputText();
ValueExpression vimage =fc.getApplication().getExpressionFactory().createValueExpression(fc.getELContext(),"#{user.name}",Object.class);
text.setValueExpression("value", text);
column.getChildren().add(text);

This solution works. Unfortunately, it looks odd to programmer from Swing. In Swing, there is no expression language. Object connects each other through method call. Of course, In Swing, there is no mechanism to construct the UI declaratively, either.

Further reading: c:forEach vs ui:repeat in Facelets










No comments:

Post a Comment