2009/05/20 - Apache Shale has been retired.

For more information, please explore the Attic.

View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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             // if the index is within the window, increment by one; otherwise by 10
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 }