Coming Up for Air

JSF 2, h:dataTable, and Ajax Updates

While JSF has had Ajax support for a long time now, it has always been through external libraries such as Ajax4Jsf/RichFaces, ICEfaces, DWR, DynaFaces, etc. With JSF 2, the framework now has first class, standardized support for Ajax. This is good news on several fronts. For those that want Ajax support but would rather not import another library, that capability is now baked in, and, for those familiar with a4j or DynaFaces, it should look very familiar. However, for those that don’t mind the external dependency, the standardized Ajax will make it much easier to mix and match component libraries on the same page, an issue that has plagued JSF for while. In this post, I’d like to take the first approach and show how easy it is to achieve Ajaxy updates on your h:dataTable using only standard JSF.

In this example, we’re going to use a part of the JSF API that I have overlooked or ignored for a long time: the DataModel class. While you may not recognize the class, you’ve almost certainly used it. When you pass a List to the a h:dataTable, JSF wraps that list in a DataModel implicitly. However, if you use the class explicitly, you gain a lot more power over your data and your table. We’ll look at that in a moment. First, though, let’s start with our basic table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<h:dataTable id="memberTable" value="#{memberBean.members}" var="member">
    <h:column>
        <f:facet name="header">
            <h:outputText style="font-weight: bold;" value="Last Name"/>
        </f:facet>
        #{member.lastName}
    </h:column>
    <h:column>
        <f:facet name="header">
            <h:outputText style="font-weight: bold;" value="First Name"/>
        </f:facet>
        #{member.firstName}
    </h:column>
    <h:column>
        <f:facet name="header">
            <h:outputText style="font-weight: bold;" value="Email Addresss"/>
        </f:facet>
        #{member.emailAddress}
    </h:column>
</h:dataTable>

This should look pretty familiar to most of you. We’re simply creating a three column table, using the data return by MemberBean.getMembers(). As I mentioned earlier, we’ll be using the DataModel API directly, so let’s take a look at MemberBean now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@ManagedBean
@SessionScoped
public class MemberBean implements Serializable {
    public static final String NAV_LIST = "/admin/members/list";
    public static final String NAV_REDIRECT = "?faces-redirect=true";
    private DataAccessController dataAccess = new DataAccessController();
    private DataModel dataModel;
    private int rowsPerPage = 5;
    private GroupMember current;
    private int selectedItemIndex = -1;
    private Paginator paginator;
    public void resetList() {
        dataModel = null;
    }
    public String prepareList() {
        resetList();
        return NAV_LIST + NAV_REDIRECT;
    }
    public void resetPagination(ComponentSystemEvent event) {
        paginator = null;
    }
    public Paginator getPaginator() {
        if (paginator == null) {
            paginator = new Paginator(rowsPerPage) {
                @Override
                public int getItemsCount() {
                    return dataAccess.count(GroupMember.class);
                }
                @Override
                public DataModel createPageDataModel() {
                    return new ListDataModel(
                        dataAccess.findRange(GroupMember.class, getPageFirstItem(),
                        getPageFirstItem()+getPageSize()));
                }
            };
        }
        return paginator;
    }
    public DataModel getMembers() {
        if (dataModel == null) {
            dataModel = getPaginator().createPageDataModel();
        }
        return dataModel;
    }
    public void next() {
        getPaginator().nextPage();
        resetList();
    }
    public void previous() {
        getPaginator().previousPage();
        resetList();
    }
}

The interesting methods here are getPaginator(), getMembers(), next(), and previous(). Before getting too far along, I feel I should note that this code is based off that generated by the really nice JSF 2 support in NetBeans 6.8, though I have done some editing. At any rate, starting with getMembers(), we see the null check on the DataModel. If it’s null, we ask the pagintator to create it for us. In getPaginator(), we create a new instance of the abstract Paginator class (see below), overriding a couple of methods to make use of instance variables from our class. In createDataModel(), we see that we ask our DataAccessController [1] to return a List of GroupMember`s, starting at index `getPageFirstItem(), of, at most getPageSize() items. While the DataAccessController is not too important to our discussion here, the Paginator certainly is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public abstract class Paginator {
    private int pageSize;
    private int page;
    public Paginator(int pageSize) {
        this.pageSize = pageSize;
    }
    public abstract int getItemsCount();
    public abstract DataModel createPageDataModel();
    public int getPageFirstItem() {
        return page*pageSize;
    }
    public int getPageLastItem() {
        int i = getPageFirstItem() + pageSize -1;
        int count = getItemsCount() - 1;
        if (i > count) {
            i = count;
        }
        if (i < 0) {
            i = 0;
        }
        return i;
    }
    public boolean isHasNextPage() {
        return (page+1)*pageSize+1 <= getItemsCount();
    }
    public void nextPage() {
        if (isHasNextPage()) {
            page++;
        }
    }
    public boolean isHasPreviousPage() {
        return page > 0;
    }
    public void previousPage() {
        if (isHasPreviousPage()) {
            page--;
        }
    }
    public int getPageSize() {
        return pageSize;
    }
}

There’s no real complex logic there, so I’ll let you read that, and we’ll move on to the next and previous links. Immediately under the table, I have this:

1
2
3
4
5
6
7
8
<h:commandLink id="prevLink" action="#{memberBean.previous}"
    value="Previous #{memberBean.paginator.pageSize}"
    rendered="#{memberBean.paginator.hasPreviousPage}"/>
&#160;
<h:commandLink id="nextLink" action="#{memberBean.next}"
    value="Next #{memberBean.paginator.pageSize}"
    rendered="#{memberBean.paginator.hasNextPage}"/>
</div>

If you were to click on the next link, MemberBean.next() would execute, which would increment the page number, and the table would rerender, getting us the next set of five `GroupMember`s. It does this, however, using a full page refresh (FPR), which is exactly what we’re trying to avoid. So, then, how does one ajaxify these links? Looking at just the next link for brevity and clarity, we add on simple line:

1
2
3
4
5
<h:commandLink id="nextLink" action="#{memberBean.next}"
    value="Next #{memberBean.paginator.pageSize}"
    rendered="#{memberBean.paginator.hasNextPage}">
    <f:ajax execute="@this" render="@form"/>
</h:commandLink>

The f:ajax tag is all you need. There are many more options for the tag, but in this simple use case, we’re telling JSF to add Ajax behavior to the default event for the component (in this case, the click), we want to execute the current component (@this tells JSF to call the action method specified on the parent component, #{memberBean.next}), and then rerender the form that encloses this component (@form). That’s all there is to it. Very easy and very clean.

Let’s go a step further. Let’s add the ability to change the number of rows per page, and, of course, let’s do it in an Ajaxy manner. First, we must add some methods to our managed bean to make it all happen:

1
2
3
4
5
6
7
8
9
10
11
public int getRowsPerPage() {
    return rowsPerPage;
}
public void setRowsPerPage(int rowsPerPage) {
    this.rowsPerPage = rowsPerPage;
    resetList();
    resetPagination();
}
public void resetPagination() {
    paginator = null;
}

This is the basic getter/setter pattern managed beans typically expose, plus some extra logic in the setter to destroy the Paginator, which will force its recreation when the table renders. The markup on the page might look something like this:

1
2
3
4
5
6
7
8
9
<h:outputText value="#{memberBean.paginator.pageFirstItem + 1} to #{memberBean.paginator.pageLastItem + 1} of #{memberBean.paginator.itemsCount}"/>
(
<h:selectOneMenu id="rowsPerPage" value="#{memberBean.rowsPerPage}">
    <f:ajax execute="@this" render="@form"/>
    <f:selectItem itemValue="5" />
    <f:selectItem itemValue="10" />
    <f:selectItem itemValue="15" />
</h:selectOneMenu>
) per page

This snippet adds some text telling the user the range he’s currently viewing, as well as a h:selectOneMenu listing some (hardcoded) options for the number of rows per page. If you’ve been doing JSF long, this looks pretty normal. The ajaxification, just like the `commandLink`s above, is a simple, one-line addition (line 4), that works the same way.

Note that in each Ajax case, we’re rerendering the entire form. While convenient, it’s not necessary. If we wanted to, we could list each component clientId, separated by spaces, that we want to rerender. For large complex forms, a more selective rerendering would probably be desirable. However, if the number of components to rerender is high, it’s a better idea to group them (in one or more groups as necessary) with, say, h:panelGroup, and rerender the groups, as that makes for a more maintainable list. In this case, we simply rerender the form as it’s small enough to do that quickly.

Before we finish up, let me touch on why I stressed the explicit use of DataModel. While not even indirectly related to Ajax interactions, its explicit use makes master/detail relationships, for example, a little easier. In this example, if we want to edit a particular member, the DataModel makes it really easy. Let’s add a "command" column to the end of our table:

1
2
3
4
5
6
7
8
9
<h:column>
    <f:facet name="header">
        <h:outputText value="&nbsp;"/>
    </f:facet>
    <h:commandLink action="#{memberBean.view}" value="View"/>&nbsp;
    <h:commandLink action="#{memberBean.edit}" value="Edit"/>&nbsp;
    <h:commandLink action="#{memberBean.delete}" value="Delete"
        onclick="return confirm('Are you sure you want to delete #{member.firstName} #{member.lastName}?');" />
</h:column>

Now, lets take a look at MemberBean.edit():

1
2
3
4
public String edit() {
    current = (GroupMember)getMembers().getRowData();
    return "edit?faces-redirect=true";
}

This method asks the DataModel what the current row is and saves that in an instance variable. It then navigates, using JSF 2’s simplified navigation to the view "edit." The inclusiong of "?faces-redirect=true" in the action outcome tells JSF to redirect to the target view. This allows the location in the browser to reflect the current page, rather than being one page behind.

JSF handles updating the state of the DataModel for you, so all that’s left for you is asking it what its state is. The approach I’ve seen (and used, sadly) most often, is to use either f:param or f:setPropertyActionListener to pass the id of the current member back to the server. My action method would then have to either query the Request or an instance variable for the ID, ask the model layer for the GroupMember that matched the ID, and then forward. While it worked, it was pretty ugly and often required more getters and setters than I cared to put on the bean. By using DataModel directly, we avoid all that cruft and left JSF do the heavy lifting for us, which is, of course, what frameworks are for.

So there you have a basic Ajaxy data table using only standard JSF components. Given how simple it is to add Ajax to a JSF page with JSF 2, you can easily start adding such features to existing JSF 1.2 applications as you upgrade to the new version without requiring massive changes to your application. Of course, if you want more complex Ajax interactions, there are still myriads of third party component sets that offer that. For the simple case, though, you no longer need to shop around.

[1] This class is simple a JPA 2 utility class. It performs the basic JPA functions, including transaction support, etc. It’s specific contents are not relevant here. :)

Quotes

Sample quote

Quote source