2009/05/20 - Apache Shale has been retired.
For more information, please explore the Attic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.shale.usecases.rolodex;
19
20 import java.io.IOException;
21 import java.math.BigDecimal;
22 import java.text.DecimalFormat;
23 import java.text.Format;
24 import java.util.Iterator;
25 import java.util.Map;
26
27 import javax.faces.component.UIComponent;
28 import javax.faces.component.UIData;
29 import javax.faces.context.FacesContext;
30 import javax.faces.context.ResponseWriter;
31 import javax.faces.render.Renderer;
32
33 import org.apache.commons.logging.Log;
34 import org.apache.commons.logging.LogFactory;
35 import org.apache.shale.util.Messages;
36
37 public class WebPagerRenderer extends Renderer {
38
39
40 /***
41 * <p>Commons logging utility object static instance.</p>
42 */
43 private static Log log;
44 static {
45 log = LogFactory.getLog(WebPagerRenderer.class);
46 }
47
48 /***
49 * <p>Localized messages for this application.</p>
50 */
51 private static Messages messages =
52 new Messages("org.apache.shale.usecases.view.Bundle");
53
54 /***
55 * <p>Constant used to create the selected page query parameter.</p>
56 */
57 private static final String PAGER_SIMPLE_NUMBERLIST = "p";
58
59 /***
60 * <p>This basic component doesn't use javascript for
61 * navigation. This means that the decode logic is in
62 * the encode/rendering phase.</p>
63 */
64 public void decode(FacesContext context, UIComponent pager) {
65 if (log.isTraceEnabled())
66 log.trace("decode(FacesContext, UIComponent)");
67 }
68
69 /***
70 * <p>Locates a parent UIData component, decodes the selected
71 * page query parameter and advances the first row of the
72 * UIData component based on the selected page.</p>
73 */
74 public void encodeEnd(FacesContext context, UIComponent pager)
75 throws IOException {
76
77 if (context == null || pager == null)
78 throw new NullPointerException();
79
80 if (!pager.isRendered())
81 return;
82
83 UIData data = this.findDataComponent(pager);
84 if (data == null)
85 throw new NullPointerException(messages.getMessage("webPager.invalid"));
86
87 int startIndex = getPageStartIndex(context, data);
88 data.setFirst(Math.max(startIndex, 0));
89
90 ResponseWriter writer = context.getResponseWriter();
91
92 writer.startElement("table",pager);
93 writer.writeAttribute("border", "0", "border");
94
95 writer.startElement("tr",pager);
96 writer.startElement("td", pager);
97 writer.writeAttribute("colspan", "3", "colspan");
98 writePageCaption(context, data, pager);
99 writer.endElement("td");
100 writer.endElement("tr");
101
102 writer.startElement("tr",pager);
103 writer.startElement("td", pager);
104 writePrevPageLink(context, data, pager);
105 writer.endElement("td");
106 writer.startElement("td", pager);
107 writePageLinks(context, data, pager);
108 writer.endElement("td");
109 writer.startElement("td", pager);
110 writeNextPageLink(context, data, pager);
111 writer.endElement("td");
112 writer.endElement("tr");
113 writer.endElement("table");
114 writePageIndex(context, data, pager);
115
116 writer = null;
117 data = null;
118 }
119
120
121 /***
122 * <p>Writes the previous page link.</p>
123 */
124 protected void writePrevPageLink(
125 FacesContext context,
126 UIData data,
127 UIComponent pager)
128 throws IOException {
129
130 ResponseWriter writer = context.getResponseWriter();
131 PageInfo p = prevPage(context, data);
132
133 String styleClass = (String) pager.getAttributes().get("prevPageStyleClass");
134
135 writer.startElement("span", pager);
136
137 if (styleClass != null) {
138 writer.writeAttribute("class", styleClass, "prevPageStyleClass");
139 }
140 if (p != null) {
141
142 Object[] params = {new Integer(p.getRowsOnPage())};
143 String prevLabel = messages.getMessage("webPager.prevTitle", params);
144
145 StringBuffer url = new StringBuffer(getActionStr(context));
146 url.append('?').append(getPageTag(context, data)).append('=');
147 url.append(p.getPage());
148
149 writer.startElement("a", pager);
150 writer.writeAttribute("href", context.getExternalContext().encodeActionURL(url.toString()), "href");
151 writer.writeAttribute("title", prevLabel, "title");
152
153 url.setLength(0);
154 url.append(getActionStr(context));
155 url.setLength(url.indexOf("/", 1));
156 String prevImage = (String) pager.getAttributes().get("prevImage");
157
158 if (prevImage == null)
159 prevImage = messages.getMessage("webPager.prevImage");
160
161 url.append(prevImage);
162 writer.startElement("img", pager);
163 writer.writeAttribute("src", context.getExternalContext().encodeActionURL(url.toString()), "scr");
164 writer.endElement("img");
165
166
167 writer.endElement("a");
168
169 url = null;
170
171 }
172
173 writer.endElement("span");
174 p = null;
175 writer = null;
176 styleClass = null;
177
178 }
179
180
181 /***
182 * <p>Renders a hidden input field that keeps track of the current page.</p>
183 */
184 protected void writePageIndex(FacesContext context, UIData data, UIComponent pager) throws IOException {
185 ResponseWriter writer = context.getResponseWriter();
186 int page = getPageIndex(context, data);
187 writer.startElement("input", pager);
188 writer.writeAttribute("name", getPageTag(context, data), "name");
189 writer.writeAttribute("type", "hidden", "type");
190 writer.writeAttribute("value", Integer.toString(page), "value");
191 writer.endElement("input");
192 }
193
194 /***
195 * <p>Writes a pager caption documenting the total model rows, current page and total pages.</p>
196 */
197 protected void writePageCaption(FacesContext context, UIData data, UIComponent pager) throws IOException {
198 ResponseWriter writer = context.getResponseWriter();
199
200 Object[] params = new Object[3];
201 params[0] = new Integer(data.getRowCount());
202 params[1] = new Integer(getPageIndex(context, data));
203 params[2] = new Integer(getPages(data));
204
205 String caption = messages.getMessage("webPager.defaultcaption", params);
206
207 String styleClass = (String) pager.getAttributes().get("captionStyleClass");
208
209 writer.startElement("span", pager);
210
211 if (styleClass != null) {
212 writer.writeAttribute("class", styleClass, "pageCaptionStyleClass");
213 }
214
215
216 writer.writeText(caption, "webPager.defaultcaption");
217
218 writer.endElement("span");
219 writer = null;
220 styleClass = null;
221
222 }
223
224 /***
225 * <p>The first ten pages are displayed as individual page links.
226 * The remainder of page links are displayed in increments of ten.</p>
227 */
228 protected void writePageLinks(FacesContext context, UIData data, UIComponent pager)
229 throws IOException {
230
231 ResponseWriter writer = context.getResponseWriter();
232 Format fmt = new DecimalFormat("#,##0");
233
234 String styleClass = (String) pager.getAttributes().get("pageLinksStyleClass");
235
236 writer.startElement("span", pager);
237
238 if (styleClass != null) {
239 writer.writeAttribute("class", styleClass, "pageLinksStyleClass");
240 }
241
242 StringBuffer url = new StringBuffer(getActionStr(context));
243 url.append('?').append(getPageTag(context, data)).append('=');
244 int urlLen = url.length();
245
246 Iterator pi = iterator(context, data);
247 while (pi.hasNext()) {
248
249 PageInfo p = (PageInfo) pi.next();
250
251 String ps = fmt.format(new Integer(p.getPage()));
252 if (p.isSelected()) {
253 writer.write(' ');
254 writer.startElement("b", pager);
255 writer.writeText(ps, "page");
256 writer.endElement("b");
257 writer.write(' ');
258 } else {
259
260 url.setLength(urlLen);
261 url.append(p.getPage());
262
263 writer.write(' ');
264 writer.startElement("a", pager);
265 writer.writeAttribute("href", context.getExternalContext().encodeActionURL(url.toString()), "href");
266 writer.writeAttribute("title",
267 messages.getMessage("webPager.linkTitle", new Object[] {new Integer(p.getPage())}), "title");
268 writer.writeText(ps, "page");
269 writer.endElement("a");
270 writer.write(' ');
271 }
272 ps = null;
273 p = null;
274 }
275
276 writer.endElement("span");
277
278 pi = null;
279 url = null;
280 fmt = null;
281 writer = null;
282 }
283
284 /***
285 * <p>Generates the next page link.</p>
286 */
287 protected void writeNextPageLink(FacesContext context, UIData data, UIComponent pager)
288 throws IOException {
289
290 ResponseWriter writer = context.getResponseWriter();
291 PageInfo p = nextPage(context, data);
292
293
294 String styleClass = (String) pager.getAttributes().get("nextPageStyleClass");
295
296 writer.startElement("span", pager);
297
298 if (styleClass != null) {
299 writer.writeAttribute("class", styleClass, "nextPageStyleClass");
300 }
301 if (p != null) {
302 Object[] params = {new Integer(p.getRowsOnPage())};
303 String nextLabel = messages.getMessage("webPager.nextTitle", params);
304
305 StringBuffer url = new StringBuffer(getActionStr(context));
306 url.append('?').append(getPageTag(context, data)).append('=');
307 url.append(p.getPage());
308
309 writer.startElement("a", pager);
310 writer.writeAttribute("href", context.getExternalContext().encodeActionURL(url.toString()), "href");
311 writer.writeAttribute("title", nextLabel, "title");
312
313 url.setLength(0);
314 url.append(getActionStr(context));
315 url.setLength(url.indexOf("/", 1));
316 String nextImage = (String) pager.getAttributes().get("nextImage");
317
318 if (nextImage == null)
319 nextImage = messages.getMessage("webPager.nextImage");
320
321 url.append(nextImage);
322 writer.startElement("img", pager);
323 writer.writeAttribute("src", context.getExternalContext().encodeActionURL(url.toString()), "scr");
324
325
326 url = null;
327
328 }
329
330 writer.endElement("span");
331 p = null;
332 writer = null;
333 styleClass = null;
334
335 }
336
337 /***
338 * <p>Determines the base URL by calling on the view handler.</p>
339 */
340 protected String getActionStr(FacesContext context) {
341 String uri = context.getViewRoot().getViewId();
342 String url =
343 context.getApplication().getViewHandler().getActionURL(
344 context,
345 uri);
346
347 return url;
348 }
349
350 /***
351 * <p>Returns the query parameter tag based on the parent UIData
352 * component.</p>
353 */
354 protected String getPageTag(FacesContext context, UIData data) {
355 StringBuffer clientId = new StringBuffer(data.getClientId(context));
356 clientId.append(PAGER_SIMPLE_NUMBERLIST);
357 return clientId.toString();
358 }
359
360 /***
361 * <p>Returns the selected page index. This would be done in the decode method if
362 * this control was not scriptless.</p>
363 */
364 protected int getPageIndex(FacesContext context, UIData data) {
365
366 int defaultPage = 1;
367 int page = 0;
368 try {
369 Map params = context.getExternalContext().getRequestParameterMap();
370 String tag = getPageTag(context, data);
371 if (params.containsKey(tag)) {
372 page = Integer.parseInt((String) params.get(tag));
373 } else {
374 page = getCurrentPage(data) + 1;
375 }
376 tag = null;
377 params = null;
378 } catch (NumberFormatException e) {
379 e.printStackTrace();
380 }
381
382 return Math.min(Math.max(page, defaultPage), getPages(data));
383 }
384
385 /***
386 * <p>Returns the current page based on the UIData component's
387 * state.</p>
388 */
389 protected int getCurrentPage(UIData uidata) {
390 if (uidata == null)
391 return 0;
392 int i = uidata.getRows();
393 if (i <= 0)
394 return 0;
395 else
396 return ((int) (uidata.getFirst() / i));
397 }
398
399 /***
400 * <p>Looks for the first data component parent.</p>
401 */
402 protected UIData findDataComponent(UIComponent uicomponent) {
403 return (UIData) findDataParent(uicomponent);
404 }
405
406 /***
407 * <p>Recursively looks up the component tree for a parent UIData component.</p>
408 */
409 private UIComponent findDataParent(UIComponent uicomponent) {
410 if (uicomponent == null)
411 return null;
412 if (uicomponent instanceof UIData)
413 return uicomponent;
414 else
415 return findDataParent(uicomponent.getParent());
416 }
417
418 /***
419 * <p>Returns the rows per page of the UIData component.
420 * The value must be five or more.</b>
421 */
422 protected int getRowsPerPage(UIData data) {
423 int rowsPerPage = Math.max(data.getRows(), 5);
424 return rowsPerPage;
425 }
426
427 /***
428 * <p>Returns the starting row within the UIData's model based
429 * on the selected page index, the total rows the model has and
430 * the rows per page.</p>
431 */
432 protected int getPageStartIndex(FacesContext context, UIData data) {
433 return getPageStartIndex(getPageIndex(context, data), context, data);
434 }
435
436 /***
437 * <p>Returns the starting row within the UIData's model based
438 * on the selected page index, the total rows the model has and
439 * the rows per page.</p>
440 */
441 protected int getPageStartIndex(
442 int page,
443 FacesContext context,
444 UIData data) {
445 int rowsPerPage = getRowsPerPage(data);
446
447 return (page - 1) * rowsPerPage;
448 }
449
450 /***
451 * <p>Returns the ending row index displayed by the UIData component.</p>
452 */
453 protected int getPageEndIndex(FacesContext context, UIData data) {
454 return getPageEndIndex(getPageIndex(context, data), context, data);
455 }
456
457 /***
458 * <p>Returns the ending row index displayed by the UIData component.</p>
459 */
460 protected int getPageEndIndex(
461 int page,
462 FacesContext context,
463 UIData data) {
464 int endPageIndex =
465 (getPageStartIndex(page, context, data) + getRowsPerPage(data)) - 1;
466 return Math.min(endPageIndex, (data.getRowCount() - 1));
467 }
468
469 /***
470 * <p>Returns the total rows on the selected page.</p>
471 */
472 protected int getRowsOnPage(int page, FacesContext context, UIData data) {
473 return (
474 getPageEndIndex(page, context, data)
475 - getPageStartIndex(page, context, data)
476 + 1);
477 }
478
479 /***
480 * <p>Returns the total number of pages based on the size of the model's set
481 * and the total rows displayed.</p>
482 */
483 protected int getPages(UIData data) {
484 int pages = 0;
485 double rowCount = data.getRowCount();
486 double rowsPerPage = getRowsPerPage(data);
487 try {
488 pages =
489 new BigDecimal(rowCount).divide(
490 new BigDecimal(rowsPerPage),
491 0,
492 BigDecimal.ROUND_UP).intValue();
493 } catch (Exception e) {
494 e.printStackTrace();
495 }
496 return pages;
497 }
498
499 /***
500 * <p>Returns PageInfo used to render the previous page links.</p>
501 */
502 protected PageInfo prevPage(FacesContext context, UIData data) {
503 int pageIndex = getPageIndex(context, data);
504
505 if (pageIndex > 1) {
506 int rowsOnPage = getRowsOnPage(pageIndex - 1, context, data);
507 return new PageInfo(pageIndex - 1, pageIndex, rowsOnPage);
508 }
509 return null;
510 }
511
512 /***
513 * <p>Returns a PageInfo Iterator used to render the individual page
514 * links.</p>
515 */
516 protected Iterator iterator(FacesContext context, UIData data) {
517 return new PageInfoIterator(
518 getPageIndex(context, data),
519 getPages(data));
520 }
521
522 /***
523 * <p>Returns PageInfo that defines the next link.</p>
524 */
525 protected PageInfo nextPage(FacesContext context, UIData data) {
526 int pageIndex = getPageIndex(context, data);
527 if (data.getRowCount() > data.getRows() && getPages(data) > pageIndex) {
528 int rowsOnPage = getRowsOnPage(pageIndex + 1, context, data);
529 return new PageInfo(pageIndex + 1, pageIndex, rowsOnPage);
530 }
531 return null;
532 }
533
534 private class PageInfo {
535
536 private int page = 0;
537 private boolean selected = false;
538 private int rows = 0;
539
540 public PageInfo(int nextIndex, int pageIndex, int rowsOnPage) {
541 this(nextIndex, pageIndex);
542 rows = rowsOnPage;
543 }
544
545 public PageInfo(int nextIndex, int pageIndex) {
546 page = nextIndex;
547 selected = (pageIndex == nextIndex);
548 }
549
550 public int getPage() {
551 return page;
552 }
553
554 public boolean isSelected() {
555 return selected;
556 }
557
558 public int getRowsOnPage() {
559 return rows;
560 }
561
562 }
563
564 private class PageInfoIterator implements Iterator {
565
566 private int pages = 0;
567 private int pageIndex = 0;
568 private int startWindow = 0;
569 private int endWindow = 0;
570 private int index = 0;
571
572 public PageInfoIterator(int pageIndex, int pages) {
573 this.pages = pages;
574 this.pageIndex = Math.min(pageIndex, pages);
575 startWindow = (int) ((int) (.1 * (this.pageIndex - 1)) * 10);
576 endWindow = Math.min(startWindow + 10, pages);
577
578 }
579
580 public boolean hasNext() {
581 return (pages > 1) && (index < pages);
582 }
583
584 public Object next() {
585
586
587 if ((index >= startWindow) && (index < endWindow))
588 index++;
589 else
590 index += 10;
591
592 return new WebPagerRenderer.PageInfo(index, pageIndex);
593 }
594
595 public void remove() {
596 }
597
598 }
599
600 /***
601 * <p>Delegates to the super implementation.</p>
602 */
603 public void encodeBegin(FacesContext context, UIComponent uicomponent)
604 throws IOException {
605 super.encodeBegin(context, uicomponent);
606 }
607
608 }