001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: PagedNavigation.java 3634 2007-01-08 21:42:24Z gbevin $
007: */
008: package com.uwyn.rife.site;
009:
010: import static java.lang.Math.ceil;
011: import static java.lang.Math.floor;
012:
013: import com.uwyn.rife.engine.ElementSupport;
014: import com.uwyn.rife.template.Template;
015:
016: /**
017: * This class provides utility methods to generate navigation for paged lists.
018: * <p>The generation of the navigation depends on a collection of block and
019: * value IDs that should be defined in a template. Following is a table of all
020: * the IDs and their purpose:
021: * <table border="1" cellpadding="3">
022: * <tr valign="top">
023: * <th>ID
024: * <th>Description
025: * <tr valign="top">
026: * <td><code><!--B 'NAV:FIRSTRANGE'--><!--/B--></code>
027: * <td>Provides the content that will be used to jump to the first range. This
028: * block has to contain an EXIT:QUERY value that will be replaced with the
029: * actual URL that will trigger the paging behaviour.
030: * <tr valign="top">
031: * <td><code><!--B 'NAV:FIRSTRANGE:DISABLED'--><!--/B--></code>
032: * <td>Provides the content that will be used when jumping to the first range
033: * is not appropriate, for instance when the first range is already the
034: * current offset.
035: * <tr valign="top">
036: * <td><code><!--B 'NAV:PREVIOUSRANGE'--><!--/B--></code>
037: * <td>Provides the content that will be used to jump to the previous range
038: * according to the current offset. This block has to contain an EXIT:QUERY
039: * value that will be replaced with the actual URL that will trigger the
040: * paging behaviour.
041: * <tr valign="top">
042: * <td><code><!--B 'NAV:PREVIOUSRANGE:DISABLED'--><!--/B--></code>
043: * <td>Provides the content that will be used when jumping to the previous
044: * range is not appropriate, for instance when the first range is the current
045: * offset.
046: * <tr valign="top">
047: * <td><code><!--B 'NAV:ABSOLUTERANGE'--><!--/B--></code>
048: * <td>Provides the content that will be used to jump directly to each
049: * individual range. This block has to contain an EXIT:QUERY value that will
050: * be replaced with the actual URL that will trigger the paging behaviour.
051: * <tr valign="top">
052: * <td><code><!--B 'NAV:ABSOLUTERANGE:DISABLED'--><!--/B--></code>
053: * <td>Provides the content that will be used when jumping directly to a
054: * specific individual range is not appropriate, for instance when that range
055: * corresponds to the current offset.
056: * <tr valign="top">
057: * <td><code><!--B 'NAV:NEXTRANGE'--><!--/B--></code>
058: * <td>Provides the content that will be used to jump to the next range
059: * according to the current offset. This block has to contain an EXIT:QUERY
060: * value that will be replaced with the actual URL that will trigger the
061: * paging behaviour.
062: * <tr valign="top">
063: * <td><code><!--B 'NAV:NEXTRANGE:DISABLED'--><!--/B--></code>
064: * <td>Provides the content that will be used when jumping to the next range
065: * is not appropriate, for instance when the last range is the current offset.
066: * <tr valign="top">
067: * <td><code><!--B 'NAV:LASTRANGE'--><!--/B--></code>
068: * <td>Provides the content that will be used to the last range. This block
069: * has to contain an EXIT:QUERY value that will be replaced with the actual
070: * URL that will trigger the paging behaviour.
071: * <tr valign="top">
072: * <td><code><!--B 'NAV:LASTRANGE:DISABLED'--><!--/B--></code>
073: * <td>Provides the content that will be used when jumping to the last range
074: * is not appropriate, for instance when the last range is already the current
075: * offset.
076: * <tr valign="top">
077: * <td><code><!--V 'NAV:RANGECOUNT'/--></code>
078: * <td>Will contain the number of ranges that are needed to display all the
079: * information that is paged. This value is optional.
080: * <tr valign="top">
081: * <td><code><!--V 'NAV:FIRSTRANGE'/--></code>
082: * <td>Will contain the content that allows to jump to the first range. This
083: * corresponds to the beginning of the paged data.
084: * <tr valign="top">
085: * <td><code><!--V 'NAV:PREVIOUSRANGE'/--></code>
086: * <td>Will contain the content that allows to jump to the previous range
087: * according to the current offset.
088: * <tr valign="top">
089: * <td><code><!--V 'NAV:ABSOLUTERANGES'/--></code>
090: * <td>Will contain the content that allows to jump directly to each
091: * individual range that is available.
092: * <tr valign="top">
093: * <td><code><!--V 'NAV:NEXTRANGE'/--></code>
094: * <td>Will contain the content that allows to jump to the next range
095: * according to the current offset.
096: * <tr valign="top">
097: * <td><code><!--V 'NAV:LASTRANGE'/--></code>
098: * <td>Will contain the content that allows to jump to the last range. This
099: * corresponds to the end of the paged data.
100: * </table>
101: * <p>Besides these template conventions, you also have to provide one exit
102: * and one output that will be used to create the links that will perform the
103: * actual paging behaviour of the navigation. By default, the
104: * <code>change_offset</code> exit and the offset <code>output</code> will be
105: * used. It's up to you to create the datalink and flowlink and to correctly
106: * handle the offset value when it changes.
107: * <p>A very basic paged navigation could for example be defined like this:
108: * <pre><!--B 'NAV:FIRSTRANGE'--><a href="[!V 'EXIT:QUERY:change_offset'/]">&lt;&lt;</a><!--/B-->
109: *<!--B 'NAV:FIRSTRANGE:DISABLED'-->&lt;&lt;<!--/B-->
110: *<!--B 'NAV:PREVIOUSRANGE'--><a href="[!V 'EXIT:QUERY:change_offset'/]">&lt;</a><!--/B-->
111: *<!--B 'NAV:PREVIOUSRANGE:DISABLED'-->&lt;<!--/B-->
112: *<!--B 'NAV:ABSOLUTERANGE'-->&nbsp;<a href="[!V 'EXIT:QUERY:change_offset'/]"><!--V 'ABSOLUTERANGE_TEXT'/--></a>&nbsp;<!--/B-->
113: *<!--B 'NAV:ABSOLUTERANGE:DISABLED'-->&nbsp;<!--V 'ABSOLUTERANGE_TEXT'/-->&nbsp;<!--/B-->
114: *<!--B 'NAV:NEXTRANGE'--><a href="[!V 'EXIT:QUERY:change_offset'/]">&gt;</a><!--/B-->
115: *<!--B 'NAV:NEXTRANGE:DISABLED'-->&gt;<!--/B-->
116: *<!--B 'NAV:LASTRANGE'--><a href="[!V 'EXIT:QUERY:change_offset'/]">&gt;&gt;</a><!--/B-->
117: *<!--B 'NAV:LASTRANGE:DISABLED'-->&gt;&gt;<!--/B-->
118: *
119: *Pages: <!--V 'NAV:RANGECOUNT'/--> ( <!--V 'NAV:FIRSTRANGE'/--> <!--V 'NAV:PREVIOUSRANGE'/--> <!--V 'NAV:NEXTRANGE'/--> <!--V 'NAV:LASTRANGE'/--> | <!--V 'NAV:ABSOLUTERANGES'/--> )</pre>
120: * <p>Which could result in the following output where all the underlined
121: * parts are clickable and will trigger the <code>change_offset</code> exit
122: * and provide a new corresponding value for the offset <code>output</code>:
123: * <p><code>Pages: 9 ( << < <u>></u> <u>>></u> | 1 <u>2</u>
124: * <u>3</u> <u>4</u> <u>5</u> <u>6</u> <u>7</u> <u>8</u> <u>9</u> )</code>
125: * <p>The element that displays the list and calls the navigation generation
126: * method could for example be like this:
127: * <pre>public class List extends Element
128: *{
129: * public final static int LIMIT = 10;
130: * public final static int SPAN = 5;
131: *
132: * public void processElement()
133: * {
134: * Template t = getHtmlTemplate("article.list");
135: * DatabaseArticles manager = DatabaseArticlesFactory.getInstance();
136: *
137: * int count = manager.countArticles();
138: * if (0 == count) t.setBlock("content", "noarticles");
139: * else
140: * {
141: * int offset = getInputInt("offset", 0);
142: *
143: * PagedNavigation.generateNavigation(this, t, count, LIMIT, offset, SPAN);
144: *
145: * Collection<Article> articles = manager.listArticles(LIMIT, offset);
146: * for (Article article : articles)
147: * {
148: * t.setBean(article);
149: * t.appendBlock("articles", "article");
150: * }
151: * }
152: *
153: * print(t);
154: * }
155: *}</pre>
156: */
157: public class PagedNavigation {
158: public static String PREFIX_NAV = "NAV:";
159:
160: public static String SUFFIX_DISABLED = ":DISABLED";
161:
162: public static String ID_RANGECOUNT = PREFIX_NAV + "RANGECOUNT";
163:
164: public static String ID_ABSOLUTERANGE_TEXT = "ABSOLUTERANGE_TEXT";
165:
166: public static String ID_FIRSTRANGE = PREFIX_NAV + "FIRSTRANGE";
167: public static String ID_PREVIOUSRANGE = PREFIX_NAV
168: + "PREVIOUSRANGE";
169: public static String ID_ABSOLUTERANGES = PREFIX_NAV
170: + "ABSOLUTERANGES";
171: public static String ID_ABSOLUTERANGE = PREFIX_NAV
172: + "ABSOLUTERANGE";
173: public static String ID_NEXTRANGE = PREFIX_NAV + "NEXTRANGE";
174: public static String ID_LASTRANGE = PREFIX_NAV + "LASTRANGE";
175:
176: public static String ID_FIRSTRANGE_DISABLED = PREFIX_NAV
177: + "FIRSTRANGE" + SUFFIX_DISABLED;
178: public static String ID_PREVIOUSRANGE_DISABLED = PREFIX_NAV
179: + "PREVIOUSRANGE" + SUFFIX_DISABLED;
180: public static String ID_ABSOLUTERANGE_DISABLED = PREFIX_NAV
181: + "ABSOLUTERANGE" + SUFFIX_DISABLED;
182: public static String ID_NEXTRANGE_DISABLED = PREFIX_NAV
183: + "NEXTRANGE" + SUFFIX_DISABLED;
184: public static String ID_LASTRANGE_DISABLED = PREFIX_NAV
185: + "LASTRANGE" + SUFFIX_DISABLED;
186:
187: public static String DEFAULT_EXIT = "change_offset";
188: public static String DEFAULT_OUTPUT = "offset";
189:
190: /**
191: * Generates the paged navigation for the given element, template and
192: * range configuration. The default exit <code>change_offset</code> and
193: * the default output <code>offset</code> will be used when generating the
194: * links.
195: *
196: * @param element The element that is populating the template. Its exit
197: * will be triggered and its output will be set.
198: * @param template The template that will be used for the generation of
199: * the navigation.
200: * @param count The total number of items that are being paged.
201: * @param limit The maximum of items that will be shown in a range on a
202: * page.
203: * @param offset The starting offset of the range that is currently
204: * visible.
205: * @param span The maximum number of ranges that will be shown as
206: * immediately accesible absolute ranges.
207: */
208: public static void generateNavigation(ElementSupport element,
209: Template template, long count, int limit, long offset,
210: int span) {
211: generateNavigation(element, template, count, limit, offset,
212: span, DEFAULT_EXIT, DEFAULT_OUTPUT);
213: }
214:
215: /**
216: * Generates the paged navigation for the given element, template and
217: * range configuration. This version allows you to provide your own names
218: * for the exit and the output that will be used when generating the
219: * links.
220: *
221: * @param element The element that is populating the template, whose exit
222: * will be triggered and whose output will be set.
223: * @param template The template that will be used for the generation of
224: * the navigation.
225: * @param count The total number of items that are being paged.
226: * @param limit The maximum of items that will be shown in a range on a
227: * page.
228: * @param offset The starting offset of the range that is currently
229: * visible.
230: * @param span The maximum number of ranges that will be shown as
231: * immediately accesible absolute ranges.
232: * @param exit The name of the exit that has to be used to trigger an
233: * offset change.
234: * @param output The name of the output that will contain the value of the
235: * new range offset when the exit is triggered.
236: */
237: public static void generateNavigation(ElementSupport element,
238: Template template, long count, int limit, long offset,
239: int span, String exit, String output) {
240: generateNavigation(element, template, count, limit, offset,
241: span, exit, output, null);
242: }
243:
244: /**
245: * Generates the paged navigation for the given element, template and
246: * range configuration. This version allows you to provide your own names
247: * for the exit and the output that will be used when generating the
248: * links.
249: *
250: * @param element The element that is populating the template, whose exit
251: * will be triggered and whose output will be set.
252: * @param template The template that will be used for the generation of
253: * the navigation.
254: * @param count The total number of items that are being paged.
255: * @param limit The maximum of items that will be shown in a range on a
256: * page.
257: * @param offset The starting offset of the range that is currently
258: * visible.
259: * @param span The maximum number of ranges that will be shown as
260: * immediately accesible absolute ranges.
261: * @param exit The name of the exit that has to be used to trigger an
262: * offset change.
263: * @param output The name of the output that will contain the value of the
264: * new range offset when the exit is triggered.
265: * @param pathInfo The pathinfo to be applied to the exit used to trigger an offset change
266: */
267: public static void generateNavigation(ElementSupport element,
268: Template template, long count, int limit, long offset,
269: int span, String exit, String output, String pathInfo) {
270: long range_count = (long) Math.ceil(((double) count) / limit);
271: if (range_count < 0) {
272: range_count = 0;
273: }
274: long max_offset = (range_count - 1) * limit;
275: if (max_offset < 0) {
276: max_offset = 0;
277: }
278: if (template.hasValueId(ID_RANGECOUNT)) {
279: template.setValue(ID_RANGECOUNT, range_count);
280: }
281:
282: if (offset < 0) {
283: offset = 0;
284: } else if (offset > max_offset) {
285: offset = max_offset;
286: } else {
287: offset = (long) (floor(offset / limit) * limit);
288: }
289:
290: String first_offset = "0";
291: String previous_offset = String.valueOf(offset - limit);
292: String next_offset = String.valueOf(offset + limit);
293: String last_offset = String.valueOf((long) floor((count - 1)
294: / limit)
295: * limit);
296:
297: if (offset <= 0) {
298: // turn first and prev off
299: template.setBlock(ID_FIRSTRANGE, ID_FIRSTRANGE_DISABLED);
300: template.setBlock(ID_PREVIOUSRANGE,
301: ID_PREVIOUSRANGE_DISABLED);
302: } else {
303: element.setExitQuery(template, exit, pathInfo,
304: new String[] { output, first_offset });
305: template.setBlock(ID_FIRSTRANGE, ID_FIRSTRANGE);
306:
307: element.setExitQuery(template, exit, pathInfo,
308: new String[] { output, previous_offset });
309: template.setBlock(ID_PREVIOUSRANGE, ID_PREVIOUSRANGE);
310: }
311:
312: if (offset + limit >= count) {
313: // turn next and last off
314: template.setBlock(ID_NEXTRANGE, ID_NEXTRANGE_DISABLED);
315: template.setBlock(ID_LASTRANGE, ID_LASTRANGE_DISABLED);
316: } else {
317: element.setExitQuery(template, exit, pathInfo,
318: new String[] { output, next_offset });
319: template.setBlock(ID_NEXTRANGE, ID_NEXTRANGE);
320:
321: element.setExitQuery(template, exit, pathInfo,
322: new String[] { output, last_offset });
323: template.setBlock(ID_LASTRANGE, ID_LASTRANGE);
324: }
325:
326: long absolute_range_end = (long) (floor(offset / limit) + span + 1);
327: long absolute_range_page = (long) ((floor(offset / limit) + 1) - span);
328: if (absolute_range_page < 1) {
329: absolute_range_page = 1;
330: }
331: long absolute_range_offset = (absolute_range_page - 1) * limit;
332:
333: template.setValue(ID_ABSOLUTERANGES, "");
334:
335: if (absolute_range_page > 1) {
336: template.setValue(ID_ABSOLUTERANGE_TEXT, "...");
337: template.setBlock(ID_ABSOLUTERANGES,
338: ID_ABSOLUTERANGE_DISABLED);
339: }
340:
341: while (absolute_range_offset < count
342: && absolute_range_page <= absolute_range_end) {
343: template.setValue(ID_ABSOLUTERANGE_TEXT,
344: absolute_range_page);
345: if (offset >= absolute_range_offset
346: && offset < absolute_range_offset + limit) {
347: template.appendBlock(ID_ABSOLUTERANGES,
348: ID_ABSOLUTERANGE_DISABLED);
349: } else {
350: String[] outputs = new String[] { output,
351: String.valueOf((int) absolute_range_offset) };
352:
353: element.setExitQuery(template, exit, pathInfo, outputs);
354:
355: template.appendBlock(ID_ABSOLUTERANGES,
356: ID_ABSOLUTERANGE);
357: }
358: absolute_range_offset += limit;
359: absolute_range_page++;
360: }
361:
362: if (absolute_range_end < ceil((double) count / limit)) {
363: template.setValue(ID_ABSOLUTERANGE_TEXT, "...");
364: template.appendBlock(ID_ABSOLUTERANGES,
365: ID_ABSOLUTERANGE_DISABLED);
366: }
367:
368: template.removeValue(ID_ABSOLUTERANGE_TEXT);
369: }
370: }
|