在《ssf入门》中,我曾经提过“虽然seam针对在jsf里进行数据分页和排序提供了解决方法,但我认为此方法过于简陋,而且不能使用richfaces的分页控件进行分页。通过ssf数据访问方式,您可以完美结合richfaces的分页控件简单地实现分页功能。”所以本文旨在给大家演示在ssf中,实现数据分页和排序是如何的方便和快捷。
效果预览
如果大家已经通过上一篇文章正确下载和配置了ssf的话,就已经可以通过菜单“管理->日志”查看分页和排序的效果,如下图所示。可能大家的环境里的日志可能还不足于超过一页的记录(10条),需要增加一些日志记录。要做到这点,只需重复几次登入、登出操作,当然为了使日志更加多样,可以在登入时尝试使用错误的密码或者不存在的用户。
实现原理
首先,我可以看一下日志页面对应的页面逻辑代码(ssf将这些代码称为action),如下清单所示。
1 package com.whatisjee.ssf.web.action;
2
3 import static com.whatisjee.ssf.web.jsf.facesutils.getservice;
4
5 import java.io.serializable;
6
7 import org.jboss.seam.scopetype;
8 import org.jboss.seam.annotations.create;
9 import org.jboss.seam.annotations.name;
10 import org.jboss.seam.annotations.scope;
11
12 import com.whatisjee.ssf.domain.entity.log;
13 import com.whatisjee.ssf.domain.service.appservice;
14 import com.whatisjee.ssf.misc.logcriteria;
15 import com.whatisjee.ssf.misc.page;
16 import com.whatisjee.ssf.misc.paginglist;
17 import com.whatisjee.ssf.web.jsf.pagingdatamodel;
18
19 @name("logsact")
20 @scope(scopetype.page)
21 public class logsaction implements serializable {
22 private static final long serialversionuid = -5252797451562495196l;
23
24 private logcriteria criteria;
25 private pagingdatamodel<log> data;
26 private string detail;
27
28 public logcriteria getcriteria() {
29 return criteria;
30 }
31
32 public pagingdatamodel<log> getdata() {
33 return data;
34 }
35
36 public string getdetail() {
37 return detail;
38 }
39
40 public void setdetail(string detail) {
41 this.detail = detail;
42 }
43
44 @create
45 public void init() {
46 criteria = new logcriteria();
47 data = new pagingdatamodel<log>("#{logsact.find}", false);
48 }
49
50 public paginglist<log> find(page page) {
51 appservice service = getservice("appservice", appservice.class);
52 return service.findlogs(criteria, page);
53 }
54
55 public void find() {
56 data.refresh();
57 }
58
59 public void showdetail() {
60 log log = (log) data.getrowdata();
61 detail = log.getdetail();
62 }
63 }
如果朋友们有看过我之前的文章《seam的页面逻辑实现》,应该对上述代码不会陌生,值得注意的是类型分别为logcriteria和pagindatamodel的两个域,前者是为了封装界面上传过来的查询条件,后者则是整个数据分页和排序的关键。只需将它绑定到richfaces的数据列表组件(如、等),即可实现分页和排序。上例中通过两个参数构造pagingdatamodel:第一个字符串参数el用来指明pagingdatamodel应该调用那个方法获得数据。它是一个el表达式,类似#{xxx.xxx}。前半部分的xxx多数情况下与action的名称相同,在本例中同为“loginact”。后半部分xxx就是数据获取方法的名称,这个方法必须有且只有一个参数而且类型为com.whatisjee.ssf.misc.page,返回值必须为com.whatisjee.ssf.misc.paginglist类型。page类包含分页和排序必不可少的信息如:从第几行开始获取数据,获取多少行,通过什么列进行排序,升序还是降序等。而paginglist则封装了总共有多少行和本页数据的信息。如本例的find方法通过将logcriteria和page传给appservice的findlogs方法,查询数据库获取数据。第二个布尔参数stateful指明pagingdatamodel是否需要自己保存状态。如果它是位于有状态的action中,即action的scope为application、session、conversation或者page,则无需自己保存状态,如本例中应为false。对于那些没有状态的action,即scope为request或event,应为true。另外,pagingdatamodel还有两个可选的构造参数:一个是page类型指明page首次加载数据时分页信息,另外一个字符串类型id仅用于在stateful为true且在一个页面上有多个pagingdatamodel时,将它们区分开来。
至于,详细的pagingdatamodel的实现大家如果有兴趣的话,可以过目一下,由于时间有限,我就不做详细解释了。
1 package com.whatisjee.ssf.web.jsf;
2
3 import static com.whatisjee.ssf.misc.page.order_asc;
4 import static com.whatisjee.ssf.misc.page.order_desc;
5 import static org.richfaces.model.ordering.ascending;
6 import static org.richfaces.model.ordering.descending;
7
8 import java.io.ioexception;
9 import java.io.serializable;
10 import java.util.collection;
11 import java.util.collections;
12 import java.util.list;
13 import java.util.map;
14 import java.util.set;
15
16 import javax.el.elcontext;
17 import javax.el.elexception;
18 import javax.el.expressionfactory;
19 import javax.el.methodexpression;
20 import javax.faces.application.application;
21 import javax.faces.component.uiviewroot;
22 import javax.faces.context.facescontext;
23
24 import org.ajax4jsf.model.datavisitor;
25 import org.ajax4jsf.model.range;
26 import org.ajax4jsf.model.sequencerange;
27 import org.ajax4jsf.model.serializabledatamodel;
28 import org.apache.commons.logging.log;
29 import org.apache.commons.logging.logfactory;
30 import org.richfaces.model.filterfield;
31 import org.richfaces.model.modifiable;
32 import org.richfaces.model.ordering;
33 import org.richfaces.model.sortfield2;
34
35 import com.whatisjee.ssf.misc.page;
36 import com.whatisjee.ssf.misc.paginglist;
37 import com.whatisjee.ssf.misc.page.order;
38
39 /**
40 * this pagingdatamodel is built to support "true" paging and sorting for richfaces's data iteration components
41 * e.g. <rich:datatable/>, <rich:datagrid/>. the "true" paging and sorting means those kinds of things can be
42 * done in database by sql.
43 * @author max yuan(max.m.yuan@gmail.com)
44 * @param a class which must be serializable.
45 */
46 public class pagingdatamodel<t extends serializable> extends serializabledatamodel implements
47 modifiable {
48 private static final long serialversionuid = -5956332259881655981l;
49 private static final log _log = logfactory.getlog(pagingdatamodel.class);
50 private static final string key_page = "page";
51 private static final string key_list = "list";
52
53 public static final int default_rows = 10;
54 public static final string default_id = "default";
55
56 private page page;
57 private paginglist<t> list;
58 private string el;
59 private string keypage;
60 private string keylist;
61 private boolean stateful;
62
63 private integer rowkey = new integer(0);
64 private boolean cached, changed, modified;
65
66 private static map<string, object> dummy_map = new map<string, object>() {
67 public void clear() {}
68
69 public boolean containskey(object key) { return false; }
70
71 public boolean containsvalue(object value) { return false; }
72
73 public set<java.util.map.entry<string, object>> entryset() { return null; }
74
75 public object get(object key) { return null; }
76
77 public boolean isempty() { return false; }
78
79 public set<string> keyset() { return null; }
80
81 public object put(string key, object value) { return null; }
82
83 public void putall(map extends string, ? extends object> m) {}
84
85 public object remove(object key) { return null; }
86
87 public int size() { return 0; }
88
89 public collection<object> values() { return null; }
90 };
91
92
93 /**
94 * this is construction method to create a pagingdatamodel which you can fully control it's behavior.
95 * @param el an el expression points to a method which must like "public paginglist xxx(page xxx)".
96 * @param page to specify the total records and initial sorting criteria.
97 * @param id use to identify this pagingdatamodel from another one.
98 * @param stateful use to specify whether to keep the state to avoid loading data every time page is loaded.
99 * if the managed bean who contains this pagingdatamodel can keep state, e.g. session scope bean, application scope bean,
100 * page scope(only available for seam) bean, then this parameter can be "false", otherwise please set it "true".
101 */
102 public pagingdatamodel(string el, page page, string id, boolean stateful) {
103 this.el = el;
104 this.keypage = key_page "$" id;
105 this.keylist = key_list "$" id;
106 this.stateful = stateful;
107
108 map<string, object> attrs = getattributes();
109 object _page = attrs.get(keypage);
110 if(_page != null) {
111 this.page = (page) _page;
112 cached = true;
113 } else {
114 this.page = page;
115 attrs.put(keypage, page);
116 }
117 }
118
119 /**
120 * this construction method create a pagingdatamodel with default amount of rows is 10.
121 * @param el an el expression points to a method which must like "public paginglist xxx(page xxx)".
122 * @param id use to identify this pagingdatamodel from another one.
123 * @param stateful use to specify whether to keep the state to avoid loading data every time page is loaded.
124 * if the managed bean which contains this pagingdatamodel can keep state, e.g. session scope bean, application scope bean,
125 * page scope(only available for seam) bean, then this parameter can be "false", otherwise please set it "true".
126 */
127 public pagingdatamodel(string el, string id, boolean stateful) {
128 this(el, new page(0, default_rows), id, stateful);
129 }
130
131 /**
132 * this construction method create a pagingdatamodel with default amount of rows is 10 and the id set to be "default"
133 * for only one pagingdatamodel in a page.
134 * @param el an el expression points to a method which must like "public paginglist xxx(page xxx)".
135 * @param stateful use to specify whether to keep the state to avoid loading data every time page is loaded.
136 * if the managed bean which contains this pagingdatamodel can keep state, e.g. session scope bean, application scope bean,
137 * page scope(only available for seam) bean, then this parameter can be "false", otherwise please set it "true".
138 */
139 public pagingdatamodel(string el, boolean stateful) {
140 this(el, new page(0, default_rows), default_id, stateful);
141 }
142
143 /**
144 * this construction method create a pagingdatamodel with default amount of rows is 10 and can keep state.
145 * @param el an el expression points to a method which must like "public paginglist xxx(page xxx)".
146 * @param id use to identify this pagingdatamodel from another one.
147 */
148 public pagingdatamodel(string el, string id) {
149 this(el, new page(0, default_rows), id, true);
150 }
151
152 /**
153 * this construction method create a pagingdatamodel with default amount of rows is 10, can keep state and
154 * the id parameter set to be "default" for only one pagingdatamodel in a page.
155 * @param el an el expression points to a method which must like "public paginglist xxx(page xxx)".
156 */
157 public pagingdatamodel(string el) {
158 this(el, new page(0, default_rows), default_id, true);
159 }
160
161 @override
162 public void update() {
163 // do nothing
164 }
165
166 @override
167 public object getrowkey() {
168 return rowkey;
169 }
170
171 @override
172 public void setrowkey(object rowkey) {
173 this.rowkey = (integer) rowkey;
174 }
175
176 private map<string, object> getattributes() {
177 map<string, object> attrs = null;
178 if(stateful){
179 uiviewroot root = facescontext.getcurrentinstance().getviewroot();
180 attrs = root.getattributes();
181 } else {
182 attrs = dummy_map;
183 }
184 return attrs;
185 }
186
187 @suppresswarnings("unchecked")
188 private paginglist<t> getlist() {
189 if(changed) {
190 refresh();
191 } else if(stateful) {
192 list = (paginglist<t>) getattributes().get(keylist);
193 }
194 return list;
195 }
196
197 @override
198 public void walk(facescontext context, datavisitor visitor, range range,
199 object argument) throws ioexception {
200 sequencerange seqrange = (sequencerange) range;
201 boolean isnew = page.getfirstrow() != seqrange.getfirstrow();
202 if (isnew) {
203 page.setfirstrow(seqrange.getfirstrow());
204 }
205 page.setrows(seqrange.getrows());
206
207 if(!cached || isnew || modified) {
208 changed = true;
209 modified = false;
210 cached = true;
211 }
212
213 int i = 0;
214 list<t> data = getlist().getdata();
215 if (data != null) {
216 for (@suppresswarnings("unused") t t : data) {
217 visitor.process(context, i, argument);
218 }
219 }
220 }
221
222 @override
223 public int getrowcount() {
224 paginglist<t> list = getlist();
225 return (list == null) ? 0 : list.getcount();
226 }
227
228 @override
229 public object getrowdata() {
230 paginglist<t> list = getlist();
231 return (list == null || list.getdata() == null || rowkey == null) ? null
232 : list.getdata().get(rowkey.intvalue());
233 }
234
235 @override
236 public int getrowindex() {
237 throw new unsupportedoperationexception();
238 }
239
240 @override
241 public object getwrappeddata() {
242 throw new unsupportedoperationexception();
243 }
244
245 @override
246 public boolean isrowavailable() {
247 paginglist<t> list = getlist();
248 return (list != null) && (list.getdata() != null) && (rowkey != null)
249 && (rowkey.intvalue() < list.getdata().size());
250 }
251
252 @override
253 public void setrowindex(int arg0) {
254 throw new unsupportedoperationexception();
255 }
256
257 @override
258 public void setwrappeddata(object arg0) {
259 throw new unsupportedoperationexception();
260 }
261
262 /**
263 * use to execute the el method to refresh data manually.
264 */
265 @suppresswarnings("unchecked")
266 public void refresh() {
267 list = (paginglist<t>) evaluateel(el, paginglist.class,
268 new class[] { page.class }, page);
269 if(list == null) {
270 list = paginglist.empty_list;
271 }
272 getattributes().put(keylist, list);
273 changed = false;
274 }
275
276 private static object evaluateel(string el, class returntype,
277 class[] paramtypes, object params) {
278 object result = null;
279 facescontext fc = facescontext.getcurrentinstance();
280 elcontext elc = fc.getelcontext();
281 application app = fc.getapplication();
282 expressionfactory ef = app.getexpressionfactory();
283 methodexpression me = ef.createmethodexpression(elc, el, returntype, paramtypes);
284 try {
285 result = me.invoke(elc, params);
286 } catch (elexception e) {
287 throwable cause = e.getcause();
288 if(cause instanceof runtimeexception) {
289 throw ((runtimeexception) cause);
290 } else {
291 if(_log.iserrorenabled()) {
292 _log.error("exception occured during evaluation of el, because ", cause);
293 }
294 }
295 }
296 return result;
297 }
298
299 @override
300 public serializabledatamodel getserializablemodel(range range) {
301 return this;
302 }
303
304 public page getpage() {
305 return page;
306 }
307
308 @suppresswarnings("unchecked")
309 public list<t> getdata() {
310 paginglist<t> list = getlist();
311 return list != null ? list.getdata() : collections.empty_list;
312 }
313
314 public void modify(list<filterfield> filterfields,
315 list<sortfield2> sortfields) {
316 if(sortfields != null && sortfields.size() > 0) {
317 sortfield2 sf = sortfields.iterator().next();
318
319 string prop = sf.getexpression().getexpressionstring();
320 string orderby = prop.substring(2, prop.length() - 1);
321 if(!orderby.equals(page.getorderby())) {
322 page.setorderby(orderby);
323 modified = true;
324 }
325
326 ordering ordering = sf.getordering();
327 order order = page.getorder();
328 if(ascending.equals(ordering) && !order_asc.equals(order)) {
329 page.setorder(order_asc);
330 modified = true;
331 } else if(descending.equals(ordering) && !order_desc.equals(order)) {
332 page.setorder(order_desc);
333 modified = true;
334 }
335 }
336 }
337
338 }
再来看看页面的xhtml代码片段,如下列表所示。
1 <rich:datatable id="dtlogs" value="#{logsact.data}" var="_log" rows="#{logsact.data.page.rows}" styleclass="list">
2 <rich:column sortby="#{loggedat}" styleclass="align-c" headerclass="align-c" selfsorted="true" sortorder="descending">
3 <f:facet name="header">
4 <h:outputtext value="#{messages['log.loggedat']}"/>
5 f:facet>
6 <h:outputtext value="#{_log.loggedat}">
7 <s:convertdatetime pattern="#{messages['cmm.shortdatetime']}" />
8 h:outputtext>
9 rich:column>
10 <rich:column sortby="#{username}" styleclass="align-l" headerclass="align-l">
11 <f:facet name="header">
12 <h:outputtext value="#{messages['log.username']}"/>
13 f:facet>
14 <h:outputtext value="#{_log.username}" />
15 rich:column>
16 <rich:column sortby="#{terminal}" styleclass="align-c" headerclass="align-c">
17 <f:facet name="header">
18 <h:outputtext value="#{messages['log.terminal']}"/>
19 f:facet>
20 <h:outputtext value="#{_log.terminal}" />
21 rich:column>
22 <rich:column sortby="#{severity}" styleclass="align-c" headerclass="align-c">
23 <f:facet name="header">
24 <h:outputtext value="#{messages['log.severity']}"/>
25 f:facet>
26 <h:outputtext value="#{messages[$c['log_labels'][_log.severity - 1][1]]}" />
27 rich:column>
28 <rich:column sortby="#{summary}" styleclass="align-l" headerclass="align-l">
29 <f:facet name="header">
30 <h:outputtext value="#{messages['log.summary']}"/>
31 f:facet>
32 <h:outputtext value="#{sf:getlogsummary(_log)}" />
33 rich:column>
34 <rich:column styleclass="align-c" headerclass="align-c">
35 <f:facet name="header">
36 <h:outputtext value="#{messages['log.detail']}"/>
37 f:facet>
38 <a4j:commandlink action="#{logsact.showdetail}" rerender="itdetail" oncomplete="richfaces.showmodalpanel('mpdetail')" rendered="#{not empty _log.detail}" styleclass="icon">
39 <h:graphicimage value="/common/image/view.gif" alt="#{messages['cmm.remove']}" />
40 a4j:commandlink>
41 rich:column>
42 rich:datatable>
43 <rich:datascroller for="dtlogs" />
这里也有几点需要注意:的rows应该如本例所示,直接使用pagingdatamodel里的page的rows域,以免产生不一至。另外,的sortby属性应为“#{xxx}”的形式,通常为数据库表的列名或hiberante的实体的域名称。
小结
本文简单地描述了ssf针对seam和jsf里的数据分页和排序问题的解决方法。只要大家在项目用到racefaces,相信上述有代码都会有所帮助。
posted on 2011-01-04 01:10
max 阅读(4421)
评论(2) 编辑 收藏 所属分类:
seam系列