Monday, January 20, 2014

RichFaces 4.3.x Tip of the Day: Complex RichFaces Data Tables

Introduction

I have been working on JSF tables for the various projects I have been involved with over the years. Starting in 2012, I began looking at RichFaces <rich:dataTable /> for some projects at my day job. The research into how to handle a number of complex situations has been enlightening to say the least.

The table is the most complex component in HTML. It is seemingly boundless in its extensibility. You can have multi-column headers that span multiple rows, you can multi-row cells, or multi-column cells. Tables can be displayed left-to-right, or right-to-left, top-to-bottom and vice-versa. As a result, when developing components for JSF, or any component framework, decisions must be made on how to generate them.

A couple of the component frameworks like PrimeFaces, and RichFaces allow developers to create more complex tables with more ease. However there are limitations with each of these frameworks. We trade flexibility for consistency, and this is fine in most cases.

The demonstration code in this post is about getting some of the flexibility back, or taking advantage of the flexibility that comes with a framework like RichFaces. We will gain the flexibility back, but it is a function of complexity. The examples will show you techniques for doing the "same thing" in multiple ways. For example, sorting can be done on the server, client, or a combination of both.

The question is where we put the complex bits. The answer to that question depends on you as a developer. You need to examine the problem domain, and understand the limits to the techniques presented.

Solutions

Please let me confess something. I like building HTML objects programmatically. There I said it. In this case I am trading the ease of development for flexibility. The solutions below will demonstrate the different techniques for accomplishing the same functionality. Please examine the code carefully before discounting it. I spent a lot of time playing with it to make it look simple.

The code for this project was developed using NetBeans and Apache Maven. The code was tested on GlassFish 3.1.2.2 and 4.0. It should work on other application servers, but I have not tested it on other servers. This project assumes you are using NetBeans which includes a sample database that these examples require. If you are not using NetBeans, you will need to create your own database with sample data to display some of the tables.

The code can be downloaded from Bitbucket at the link below, or in the references section at the end of the post.

richfaces-tables-poc

Dynamic Data Table with Sorting

Dynamic Table with Sorting
This example uses the binding attribute of the <rich:dataTable /> to bind our table to a CDI @ManagedBean. The bean is responsible for generating the table programmatically, and returning it back to the page. The data is sortable by column.
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
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:rich="http://richfaces.org/rich">
    <h:head>
        <title>Dynamic Data Table Design #1</title>
    </h:head>
    <h:body>
        <h1>
Dynamic Data Table Design #1</h1>
<p>
            The table is bound to the page backing bean, and is generated dynamically.
  
            Please use the Source and Code links to see the details.
        </p>
<h:form id="form1">
            <rich:dataTable id="dataTable" binding="#{dataTable1.dataTable}"/>
        </h:form>
        <div style="padding-top: 5px;">
            <a href="#{facesContext.externalContext.requestContextPath}">Index</a>
            <a href="#{facesContext.externalContext.requestContextPath}/source/dataTable1.xhtml">Source</a>
            <a href="#{facesContext.externalContext.requestContextPath}/source/resources/src/dataTable1.java">Code</a>
        </div>
</h:body>
</html>
As you can see the page is very simple. In fact, most of the page is plumbing and navigation. The <rich:dataTable /> is the smallest part of the page. The code to generate the table is much more complex.
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
package com.bluelotussoftware.example.richfaces;
 
import com.bluelotussoftware.example.richfaces.model.Customer;
import com.bluelotussoftware.example.richfaces.ssb.CustomerFacade;
import com.bluelotussoftware.jsf.utils.JSFUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.Application;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import org.richfaces.component.SortOrder;
import org.richfaces.component.UIColumn;
import org.richfaces.component.UIColumnGroup;
import org.richfaces.component.UICommandLink;
import org.richfaces.component.UIDataTable;
 
/**
 * <p>
Proof of Concept #1 Programmatic RichFaces data table
 * <code><rich:dataTable/></code> with server side sorting</p>
*
 * <p>
<strong>Note:</strong> Please note that the scope must be set to session
 * for the object holding a reference to the bound datatable.</p>
*
 * @author John Yeary
 * @version 1.0
 */
@Named
@SessionScoped
public class DataTable1 implements Serializable {
 
    private static final long serialVersionUID = 3733919546663290317L;
    private List<customer> customers;
    private SortOrder nameSortOrder = SortOrder.unsorted;
    private SortOrder creditSortOrder = SortOrder.unsorted;
    private SortOrder phoneSortOrder = SortOrder.unsorted;
    @EJB
    private CustomerFacade cf;
    private UIDataTable dataTable;
 
    public DataTable1() {
        customers = new ArrayList<customer>();
    }
 
    @PostConstruct
    private void init() {
        customers.addAll(cf.findAll());
 
        Class<?>[] klazz = new Class<?>[]{};
        Application application = FacesContext.getCurrentInstance().getApplication();
 
        // Create RichFaces Table <rich:dataTable/>
        dataTable = (UIDataTable) application.createComponent(UIDataTable.COMPONENT_TYPE);
        dataTable.setVar("customer");
        dataTable.setValue(customers);
 
        // Create A4J CommandLink <a4j:commandLink/> Column Headers
        UICommandLink clink = (UICommandLink) application.createComponent(UICommandLink.COMPONENT_TYPE);
        clink.setValueExpression("value", JSFUtils.createValueExpression("#{dataTable1.customerColumnHeader}", String.class));
        clink.setActionExpression(JSFUtils.createMethodExpression("#{dataTable1.nsort()}", String.class, klazz));
        clink.setRender("dataTable");
 
        UICommandLink clink1 = (UICommandLink) application.createComponent(UICommandLink.COMPONENT_TYPE);
        clink1.setValue("Phone");
        clink1.setActionExpression(JSFUtils.createMethodExpression("#{dataTable1.psort()}", String.class, klazz));
        clink1.setRender("dataTable");
 
        UICommandLink clink2 = (UICommandLink) application.createComponent(UICommandLink.COMPONENT_TYPE);
        clink2.setValue("Credit Limit");
        clink2.setActionExpression(JSFUtils.createMethodExpression("#{dataTable1.csort()}", String.class, klazz));
        clink2.setRender("dataTable");
 
        // Create Data Elements
        HtmlOutputText htmlOutputText = (HtmlOutputText) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
        htmlOutputText.setValueExpression("value", JSFUtils.createValueExpression("#{customer.name}", String.class));
 
        HtmlOutputText htmlOutputText1 = (HtmlOutputText) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
        htmlOutputText1.setValueExpression("value", JSFUtils.createValueExpression("#{customer.phone}", String.class));
 
        HtmlOutputText htmlOutputText2 = (HtmlOutputText) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
        htmlOutputText2.setValueExpression("value", JSFUtils.createValueExpression("#{dataTable1.format(customer.creditLimit)}", String.class));
        htmlOutputText2.setValueExpression("rendered", JSFUtils.createValueExpression("#{customer.creditLimit gt 25000}", Boolean.class));
 
        // Create RichFaces Columns <rich:column/>
        UIColumn column = (UIColumn) application.createComponent(UIColumn.COMPONENT_TYPE);
        column.setValueExpression("sortBy", JSFUtils.createValueExpression("#{customer.name}", String.class));
        column.setValueExpression("sortOrder", JSFUtils.createValueExpression("#{dataTable1.nameSortOrder}", SortOrder.class));
 
        UIColumn column1 = (UIColumn) application.createComponent(UIColumn.COMPONENT_TYPE);
        column1.setValueExpression("sortBy", JSFUtils.createValueExpression("#{customer.phone}", String.class));
        column1.setValueExpression("sortOrder", JSFUtils.createValueExpression("#{dataTable1.phoneSortOrder}", SortOrder.class));
        column1.setValueExpression("comparator", JSFUtils.createValueExpression("#{dataTable1.phoneComparator}", Comparator.class));
 
        UIColumn column2 = (UIColumn) application.createComponent(UIColumn.COMPONENT_TYPE);
        column2.setValueExpression("sortBy", JSFUtils.createValueExpression("#{customer.creditLimit}", String.class));
        column2.setValueExpression("sortOrder", JSFUtils.createValueExpression("#{dataTable1.creditSortOrder}", SortOrder.class));
        column2.setValueExpression("comparator", JSFUtils.createValueExpression("#{dataTable1.creditComparator}", Comparator.class));
 
        // Assemble Columns and values
        column.setHeader(clink);
        column.getChildren().add(htmlOutputText);
 
        column1.setHeader(clink1);
        column1.getChildren().add(htmlOutputText1);
 
        column2.setHeader(clink2);
        column2.getChildren().add(htmlOutputText2);
 
        // Create Table Header
        UIColumn hcolumn = (UIColumn) application.createComponent(UIColumn.COMPONENT_TYPE);
        hcolumn.setColspan(3);
        HtmlOutputText columnText = (HtmlOutputText) application.createComponent(HtmlOutputText.COMPONENT_TYPE);
        columnText.setValue("Customer Information");
        hcolumn.getChildren().add(columnText);
        UIColumnGroup columnGroup = (UIColumnGroup) application.createComponent(UIColumnGroup.COMPONENT_TYPE);
        columnGroup.getChildren().add(hcolumn);
 
        // Assemble Table
        dataTable.setHeader(columnGroup);
        dataTable.getChildren().add(column);
        dataTable.getChildren().add(column1);
        dataTable.getChildren().add(column2);
 
    }
 
    public UIDataTable getDataTable() {
        return dataTable;
    }
 
    public void setDataTable(UIDataTable dataTable) {
        this.dataTable = dataTable;
    }
 
    public List<customer> getCustomers() {
        return customers;
    }
 
    public SortOrder getNameSortOrder() {
        return nameSortOrder;
    }
 
    public SortOrder getCreditSortOrder() {
        return creditSortOrder;
    }
 
    public SortOrder getPhoneSortOrder() {
        return phoneSortOrder;
    }
 
    /**
     * Customer name sorting enumeration "three position switch":
     * default (unsorted), ascending, and descending.
     */
    public void nsort() {
        creditSortOrder = SortOrder.unsorted;
        phoneSortOrder = SortOrder.unsorted;
 
        switch (nameSortOrder) {
            case unsorted: {
                nameSortOrder = SortOrder.ascending;
                break;
            }
            case ascending: {
                nameSortOrder = SortOrder.descending;
                break;
            }
            case descending: {
                nameSortOrder = SortOrder.unsorted;
                break;
            }
        }
    }
 
    /**
     * Credit limit sorting enumeration "three position switch":
     * default (unsorted), ascending, and descending.
     */
    public void csort() {
        nameSortOrder = SortOrder.unsorted;
        phoneSortOrder = SortOrder.unsorted;
 
        switch (creditSortOrder) {
            case unsorted: {
                creditSortOrder = SortOrder.ascending;
                break;
            }
            case ascending: {
                creditSortOrder = SortOrder.descending;
                break;
            }
            case descending: {
                creditSortOrder = SortOrder.unsorted;
                break;
            }
        }
    }
 
    /**
     * Phone sorting enumeration "three position switch": default
     * (unsorted), ascending, and descending.
     */
    public void psort() {
        nameSortOrder = SortOrder.unsorted;
        creditSortOrder = SortOrder.unsorted;
 
        switch (phoneSortOrder) {
            case unsorted: {
                phoneSortOrder = SortOrder.ascending;
                break;
            }
            case ascending: {
                phoneSortOrder = SortOrder.descending;
                break;
            }
            case descending: {
                phoneSortOrder = SortOrder.unsorted;
                break;
            }
        }
    }
 
    public Comparator<customer> getPhoneComparator() {
 
        return new Comparator<customer>() {
            @Override
            public int compare(Customer o1, Customer o2) {
                return o1.getPhone().compareTo(o2.getPhone());
            }
        };
    }
 
    public Comparator<customer> getCreditComparator() {
        return new Comparator<customer>() {
            @Override
            public int compare(Customer o1, Customer o2) {
                return o1.getCreditLimit().compareTo(o2.getCreditLimit());
            }
        };
    }
 
    /**
     * This method returns the customer name header with the specified sort
     * order based on the current {@link SortOrder}.
     *
     * @return customer name header with the specified sort order.
     */
    public String getCustomerColumnHeader() {
        switch (nameSortOrder) {
            case unsorted: {
                return "Customer Name";
            }
            default: {
                return "Customer Name (" + nameSortOrder + ")";
            }
        }
    }
 
    /**
     * Generates custom formatted USD currency value based on {@code Integer}.
     *
     * @param value The value to be formatted.
     * @return $USD formatted value.
     */
    public String format(Integer value) {
        return String.format("$ %1$,d", value);
    }
}
As you can see we have traded simplicity in the page for complexity in the @ManagedBean. If you are satisfied with this technique, lets take a look at another one.

Dynamic Data Table with Sorting Revisited

Dynamic Table
This table uses the same dynamic binding as the example above on the JSF page, but uses helper utilities to create JSF components dynamically from a library that I have written. It is a separate project that you can download (Please see references). This reduces the chances for errors creating common components, but it is still a lot of code. To check our sorting, I have made a "random" data generator for the table data for the code to sort.
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
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:rich="http://richfaces.org/rich">
    <h:head>
        <title>Dynamic Data Table Design #2</title>
    </h:head>
    <h:body>
        <h1>
Dynamic Data Table Design Design #2</h1>
<p>
            The table is bound to the page backing bean, and is generated dynamically.
  
            Please use the Source and Code links to see the details.
        </p>
<h:form id="form1">
            <rich:dataTable id="dataTable" binding="#{dataTable2.dataTable}"/>
        </h:form>
        <div style="padding-top: 5px;">
            <a href="#{facesContext.externalContext.requestContextPath}">Index</a>
            <a href="#{facesContext.externalContext.requestContextPath}/source/dataTable2.xhtml">Source</a>
            <a href="#{facesContext.externalContext.requestContextPath}/source/resources/src/dataTable2.java">Code</a>
        </div>
</h:body>
</html>

The more simplified code in the @ManagedBean is shown below.
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package com.bluelotussoftware.example.richfaces;
 
import com.bluelotussoftware.jsf.utils.JSFUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.Application;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import org.richfaces.component.SortOrder;
import org.richfaces.component.UIColumn;
import org.richfaces.component.UICommandLink;
import org.richfaces.component.UIDataTable;
 
/**
 * <p>
Proof of Concept #2 Programmatic RichFaces data table
 * <code><rich:dataTable/></code> with server side sorting</p>
*
 * <p>
<strong>Note:</strong> Please note that the scope must be set to session
 * for the object holding a reference to the bound datatable.</p>
*
 * @author John Yeary
 * @version 1.0
 */
@Named
@SessionScoped
public class DataTable2 implements Serializable {
 
    private static final long serialVersionUID = -2771295649562837525L;
    private SortOrder sorting = SortOrder.unsorted;
    private static final String DATA_TABLE_NAME = "dataTable";
    private UIDataTable dataTable;
 
    public DataTable2() {
    }
 
    @PostConstruct
    private void init() {
        FacesContext context = FacesContext.getCurrentInstance();
        Application application = context.getApplication();
        // ~~~~~~~~~~~~~~~~~~~~ Data Table
        dataTable = (UIDataTable) application.createComponent(UIDataTable.COMPONENT_TYPE);
        dataTable.setVar("v");
        dataTable.setValue(getData());
        UIColumn column0 = createUIColumn(context, "#{v.get(0).value}", String.class, "#{dataTable2.sorting}");
        UIColumn column1 = createUIColumn(context, "#{v.get(1).value}", String.class, "#{dataTable2.sorting}");
        UICommandLink commandLink0 = createUICommandLink(context, "Column 0", DATA_TABLE_NAME, "#{dataTable2.sort()}");
        UICommandLink commandLink1 = createUICommandLink(context, "Column 1", DATA_TABLE_NAME, "#{dataTable2.sort()}");
        HtmlOutputText htmlOutputText0 = createHtmlOutputText(context, "#{v.get(0).value}", String.class);
        HtmlOutputText htmlOutputText1 = createHtmlOutputText(context, "#{v.get(1).value}", String.class);
        column0.setHeader(commandLink0);
        column0.getChildren().add(htmlOutputText0);
        column1.setHeader(commandLink1);
        column1.getChildren().add(htmlOutputText1);
        dataTable.getChildren().add(column0);
        dataTable.getChildren().add(column1);
    }
 
    public SortOrder getSorting() {
        return sorting;
    }
 
    public void setSorting(SortOrder sorting) {
        this.sorting = sorting;
    }
 
    public UIDataTable getDataTable() {
        return dataTable;
    }
 
    public void setDataTable(UIDataTable dataTable) {
        this.dataTable = dataTable;
    }
 
    public void sort() {
        switch (sorting) {
            case unsorted: {
                sorting = SortOrder.ascending;
                break;
            }
            case ascending: {
                sorting = SortOrder.descending;
                break;
            }
            case descending: {
                sorting = SortOrder.unsorted;
                break;
            }
        }
    }
 
    public UICommandLink createUICommandLink(final FacesContext context, final String value, final String render, final String methodExpression) {
        Class<?>[] clazz = new Class<?>[]{};
        UICommandLink link = (UICommandLink) context.getApplication().createComponent(UICommandLink.COMPONENT_TYPE);
        link.setValue(value);
        link.setRender(render);
        link.setActionExpression(JSFUtils.createMethodExpression(methodExpression, String.class, clazz));
        return link;
    }
 
    public UIColumn createUIColumn(final FacesContext context, final String sortByValueExpression, final Class<?> sortByType, final String sortOrderValueExpression) {
        UIColumn column = (UIColumn) context.getApplication().createComponent(UIColumn.COMPONENT_TYPE);
        column.setValueExpression("sortBy", JSFUtils.createValueExpression(sortByValueExpression, sortByType));
        column.setValueExpression("sortOrder", JSFUtils.createValueExpression(sortOrderValueExpression, SortOrder.class));
        return column;
    }
 
    public HtmlOutputText createHtmlOutputText(final FacesContext context, final String valueValueExpression, Class<?> valueType) {
        HtmlOutputText text = (HtmlOutputText) context.getApplication().createComponent(HtmlOutputText.COMPONENT_TYPE);
        text.setValueExpression("value", JSFUtils.createValueExpression(valueValueExpression, valueType));
        return text;
    }
 
    public List<List<valueholder>> getData() {
        List<List<valueholder>> datax = new ArrayList<List<valueholder>>();
 
        for (int i = 0; i < 10; i++) {
            List<ValueHolder> subelement = new ArrayList<valueholder>();
 
            for (int j = 0; j < 10; j++) {
                ValueHolder vh = new ValueHolder(String.format("Row %1$d Element %2$d", i, j));
 
                List<ValueHolder> sub = new ArrayList<valueholder>();
                for (int k = 0; k < 3; k++) {
                    ValueHolder sube = new ValueHolder("SubElement " + k);
 
                    List<ValueHolder> subsub = new ArrayList<valueholder>();
                    for (int l = 0; l < 10; l++) {
                        ValueHolder subx = new ValueHolder("SubSubElement " + l);
                        subsub.add(subx);
                    }
                    sube.setSubValues(subsub);
                    sub.add(sube);
                }
 
                vh.setSubValues(sub);
                subelement.add(vh);
            }
            datax.add(subelement);
        }
 
        Collections.shuffle(datax);
        return datax;
    }
}

The code above was written before I added more functionality to my jsf-utils project. The new methods would shorten this considerably, but it would still be fairly complex.

Dynamic Table using JSP/JSTL Tags with JSF

JSF/JSTL Dynamic Table
Let me start this example with a warning. If you are using JSP/JSTL tags in your JSF pages, you may encounter very bad behavior. This technique should only be used as a last resort. I will not labor a point. If you don't understand why this is a bad idea, take a look at this post for links: JSF 2.x Tip of the Day: Great Blog Posts Explaining JSTL vs. JSF.
In this example, I will generate the rows and columns using <c:forEach />. This transfers a lot of the complexity to the page and away from the @ManagedBean. Since we are using <c:forEach />, our mechanism for sorting has to change. I used Query jquery.tablesorter.js to allow sorting of the headers.
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
      xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:rich="http://richfaces.org/rich">
    <h:head>
        <title>Dynamic Data Table Design #3</title>
    </h:head>
    <h:body>
        <h1>
Dynamic Data Table Design Design #3</h1>
<p>
            The table is bound to the page backing bean, and is generated dynamically using <code><c:forEach /></code> to create the column elements.
 
            This technique is not recommended unless you understand the limitations of using JSP/JSTL tags with JSF. This is a good working example of the technique.
 
            This table also uses jQuery <i>jquery.tablesorter.js</i> to allow sorting of the headers. Click on the headers to sort.
        </p>
<p>
            Please use the Source and Code links to see the details.
        </p>
<h:form id="form1">
            <rich:dataTable id="dataTable" value="#{dataTable3.customers}" var="customer"
                            rowKeyVar="idx" styleClass="tablesorter">
                <c:forEach items="#{dataTable3.customerByIndex(idx)}" var="cx">
                    <f:facet name="header">
                        <rich:columnGroup>
                            <rich:column colspan="2">
                                <h:outputText value="Customers"/>
                            </rich:column>
                            <rich:column breakRowBefore="true">
                                <h:outputText value="Name"/>
                            </rich:column>
                            <rich:column>
                                <h:outputText value="Phone"/>
                            </rich:column>
                        </rich:columnGroup>
                    </f:facet>
                    <rich:column>
                        <h:outputText value="#{cx.name}"/>
                        <f:facet name="footer">
                            Number of Customers: #{dataTable3.customers.size()}
                        </f:facet>
                    </rich:column>
                    <rich:column>
                        <h:outputText value="#{cx.phone}"/>
                        <f:facet name="footer"/>
                    </rich:column>
                </c:forEach>
            </rich:dataTable>
            <rich:jQuery/>
            <h:outputScript name="jquery.tablesorter.js" library="js"/>
        </h:form>
        <div style="padding-top: 5px;">
            <a href="#{facesContext.externalContext.requestContextPath}">Index</a>
            <a href="#{facesContext.externalContext.requestContextPath}/source/dataTable3.xhtml">Source</a>
            <a href="#{facesContext.externalContext.requestContextPath}/source/resources/src/dataTable3.java">Code</a>
        </div>
<script type="text/javascript">
            $(document).ready(function() {
                $('#form1\\:dataTable').tablesorter();
            });
        </script>
    </h:body>
</html>
As you can see we have much simpler code in the page bean. It looks like what you would expect for a normal JSF data table.
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
54
55
package com.bluelotussoftware.example.richfaces;
 
import com.bluelotussoftware.example.richfaces.model.Customer;
import com.bluelotussoftware.example.richfaces.ssb.CustomerFacade;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
 
/**
 * Proof of Concept #3 Client Side Sorting using jQuery TableSorter 2.0
 *
 * @author John Yeary
 * @version 1.0
 */
@Named
@RequestScoped
public class DataTable3 {
 
    private static final long serialVersionUID = -1246509549850361939L;
    private List<customer> customers;
    @EJB
    private CustomerFacade customerFacade;
 
    public DataTable3() {
        customers = new ArrayList<customer>();
    }
 
    @PostConstruct
    private void init() {
        customers.addAll(customerFacade.findAll());
    }
 
    public List<customer> getCustomers() {
        return customers;
    }
 
    /**
     * This method is used by
     * <code><c:forEach/></code>
     * <code>items</code> attribute which requires a {@code Collection}, or an
     * array to iterate over;
     *
     * @param index the {@link Customer} at the index position in the customer
     * list.
     * @return a list containing a single customer at the specified index.
     */
    public List<customer> customerByIndex(final int index) {
        List<customer> customer = new ArrayList<customer>();
        customer.add(customers.get(index));
        return customer;
    }
}

Complex Data Table Design

Complex Table Design
This table has a lot of really cool features, but the code is complex in the page, and the page bean is relatively simple.
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      xmlns:a4j="http://richfaces.org/a4j"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:rich="http://richfaces.org/rich">
    <h:head>
        <title>Complex Data Table Design</title>
    </h:head>
    <h:body>
        <h:form id="form1">
            <h1>
Complex Data Table Design</h1>
<p>
                This example demonstrates how to use the <code><rich:columnGroup /></code> tag, along with Expression Language (EL)
 
                formatting of column output. It also demonstrates how to use AJAX sorting of the customer name.
            </p>
<p>
                Please use the Source and Code links to see the details.
            </p>
<rich:dataTable id="dataTable" value="#{complexDataTable.customers}" var="customer">
                <f:facet name="header">
                    <rich:columnGroup>
                        <rich:column colspan="6">
                            <h:outputText value="Customers"/>
                        </rich:column>
                        <rich:column breakRowBefore="#{true}" rowspan="2" colspan="2"/>
                        <rich:column colspan="2">
                            <h:outputText value="Address"/>
                        </rich:column>
                        <rich:column>
                            <h:outputText value="Contact Information"/>
                        </rich:column>
                        <rich:column rowspan="2">
                            <h:outputText value="Credit Limit" />
                        </rich:column>
                        <rich:column breakRowBefore="#{true}">
                            <h:outputText value="City"/>
                        </rich:column>
                        <rich:column>
                            <h:outputText value="State"/>
                        </rich:column>
                        <rich:column>
                            <h:outputText value="Phone"/>
                        </rich:column>
                    </rich:columnGroup>
                </f:facet>
                <rich:column>
                    <f:facet name="header">
                        <h:outputText value="Sales Code Word"/>
                    </f:facet>
                    <h:outputText rendered="#{customer.creditLimit eq 50000
                                              || customer.creditLimit eq 70000
                                              || customer.creditLimit eq 90000}"
                                  value="Partner"/>
                    <h:outputText rendered="#{customer.creditLimit eq 5000000}"
                                  value="Head Kahuna"/>
                    <h:outputText rendered="#{customer.creditLimit eq 100000}"
                                  value="Big Tuna"/>
                </rich:column>
                <rich:column sortBy="#{customer.name}" sortOrder="#{complexDataTable.sorting}">
                    <f:facet name="header">
                        <a4j:commandLink render="dataTable" value="#{complexDataTable.customerColumnHeader}" action="#{complexDataTable.sort()}"/>
                    </f:facet>
                    <h:outputText value="#{customer.name}"/>
                </rich:column>
                <rich:column>
                    <h:outputText value="#{customer.city}"/>
                </rich:column>
                <rich:column>
                    <h:outputText value="#{customer.state}"/>
                </rich:column>
                <rich:column>
                    <h:outputText value="#{customer.phone}"/>
                </rich:column>
                <rich:column>
                    <h:outputText value="#{complexDataTable.format(customer.creditLimit)}"/>
                </rich:column>
            </rich:dataTable>
        </h:form>
        <div style="padding-top: 5px;">
            <a href="#{facesContext.externalContext.requestContextPath}">Index</a>
            <a href="#{facesContext.externalContext.requestContextPath}/source/complexLayout.xhtml">Source</a>
            <a href="#{facesContext.externalContext.requestContextPath}/source/resources/src/complexDataTable.java">Code</a>
        </div>
</h:body>
</html>

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package com.bluelotussoftware.example.richfaces;
 
import com.bluelotussoftware.example.richfaces.model.Customer;
import com.bluelotussoftware.example.richfaces.ssb.CustomerFacade;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import org.richfaces.component.SortOrder;
 
/**
 * Proof of Concept #4 Complex layout table with server side sorting on single
 * column.
 *
 * @author John Yeary
 * @version 1.0
 */
@Named
@SessionScoped
public class ComplexDataTable implements Serializable {
 
    private static final long serialVersionUID = -5343878110987753773L;
    private List<customer> customers;
    @EJB
    private CustomerFacade customerFacade;
    private SortOrder sorting = SortOrder.unsorted;
 
    public ComplexDataTable() {
        customers = new ArrayList<customer>();
    }
 
    @PostConstruct
    private void init() {
        customers.addAll(customerFacade.findAll());
    }
 
    public List<customer> getCustomers() {
        return Collections.unmodifiableList(customers);
    }
 
    public SortOrder getSorting() {
        return sorting;
    }
 
    /**
     * Sorting enumeration "three position switch": default
     * (unsorted), ascending, and descending.
     */
    public void sort() {
        switch (sorting) {
            case unsorted: {
                sorting = SortOrder.ascending;
                break;
            }
            case ascending: {
                sorting = SortOrder.descending;
                break;
            }
            case descending: {
                sorting = SortOrder.unsorted;
                break;
            }
        }
    }
 
    /**
     * This method returns the customer name header with the specified sort
     * order based on the current {@link SortOrder}.
     *
     * @return customer name header with the specified sort order.
     */
    public String getCustomerColumnHeader() {
        switch (sorting) {
            case unsorted: {
                return "Customer Name";
            }
            default: {
                return "Customer Name (" + sorting + ")";
            }
        }
    }
 
    /**
     * Generates custom formatted USD currency value based on {@code Integer}.
     *
     * @param value The value to be formatted.
     * @return $USD formatted value.
     */
    public String format(Integer value) {
        return String.format("$ %1$,d", value);
    }
}

Conclusion

RichFaces supports complex table designs, and produces nice results. The amount of work required to create dynamic data tables depends on the technique chosen, and limitations on the data being presented. There is no "one good way" to create data tables. Suffice to say that the easiest path should be chosen.

References

0 comments :

Popular Posts