001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/jsf/tags/sakai_2-4-1/widgets/src/java/org/sakaiproject/jsf/renderer/PagerRenderer.java $
003: * $Id: PagerRenderer.java 29886 2007-05-02 21:43:37Z ajpoland@iupui.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.jsf.renderer;
021:
022: import java.io.IOException;
023: import java.text.MessageFormat;
024: import java.util.Map;
025: import java.util.MissingResourceException;
026:
027: import javax.faces.component.EditableValueHolder;
028: import javax.faces.component.UIComponent;
029: import javax.faces.context.FacesContext;
030: import javax.faces.context.ResponseWriter;
031: import javax.faces.render.Renderer;
032:
033: import org.apache.commons.logging.Log;
034: import org.apache.commons.logging.LogFactory;
035: import org.sakaiproject.jsf.util.LocaleUtil;
036: import org.sakaiproject.jsf.util.RendererUtil;
037:
038: public class PagerRenderer extends Renderer {
039: private static final Log log = LogFactory
040: .getLog(PagerRenderer.class);
041: private static final String BUNDLE_NAME = "org.sakaiproject.jsf.bundle.pager";
042:
043: public void encodeBegin(FacesContext context, UIComponent component)
044: throws IOException {
045: if (!component.isRendered())
046: return;
047:
048: // get state
049:
050: ResponseWriter out = context.getResponseWriter();
051: String clientId = component.getClientId(context);
052: //String formId = getFormId(context, component);
053:
054: int pageSize = getInt(context, component, "pageSize", 0);
055: int totalItems = getInt(context, component, "totalItems", 0);
056: int firstItem = getInt(context, component, "firstItem", 0);
057: int lastItem = getInt(context, component, "lastItem", -1);
058: if (log.isDebugEnabled())
059: log.debug("encodeBegin: firstItem=" + firstItem
060: + ", pageSize=" + pageSize + ", value="
061: + getString(context, component, "value", null));
062:
063: // in case we are rendering before decode()ing we need to adjust the states
064: adjustState(context, component, firstItem, lastItem, pageSize,
065: totalItems, firstItem, lastItem, pageSize);
066:
067: pageSize = getInt(context, component, "pageSize", 0);
068: totalItems = getInt(context, component, "totalItems", 0);
069: firstItem = getInt(context, component, "firstItem", 0);
070: lastItem = getInt(context, component, "lastItem", -1);
071:
072: // get stuff for pageing buttons
073: String idFirst = clientId + "_first";
074: String idPrev = clientId + "_prev";
075: String idNext = clientId + "_next";
076: String idLast = clientId + "_last";
077: String idPastItem = clientId + "_pastItem";
078: boolean renderFirst = getBoolean(context, component,
079: "renderFirst", true);
080: boolean renderPrev = getBoolean(context, component,
081: "renderPrev", true);
082: boolean renderNext = getBoolean(context, component,
083: "renderNext", true);
084: boolean renderLast = getBoolean(context, component,
085: "renderLast", true);
086: boolean renderPageSize = getBoolean(context, component,
087: "renderPageSize", true);
088: String labelFirst = getString(context, component, "textFirst",
089: "|<");
090: String labelPrev = getString(context, component, "textPrev",
091: "<");
092: String labelNext = getString(context, component, "textNext",
093: ">");
094: String labelLast = getString(context, component, "textLast",
095: ">|");
096: String textItem = getString(context, component, "textItem",
097: "items");
098: String titleFirst = MessageFormat.format(getString(context,
099: component, "titleFirst", "First {0} {1}"), pageSize,
100: textItem);
101: String titlePrev = MessageFormat.format(getString(context,
102: component, "titlePrev", "Previous {0} {1}"), pageSize,
103: textItem);
104: String titleNext = MessageFormat.format(getString(context,
105: component, "titleNext", "Next {0} {1}"), pageSize,
106: textItem);
107: String titleLast = MessageFormat.format(getString(context,
108: component, "titleLast", "Last {0} {1}"), pageSize,
109: textItem);
110:
111: // TODO: Do this elsewhere? (component vs renderer)
112: boolean disabledFirst = (firstItem == 0);
113: boolean disabledPrev = (firstItem == 0);
114: boolean disabledNext = (pageSize == 0)
115: || (firstItem + pageSize >= totalItems);
116: boolean disabledLast = disabledNext;
117: boolean accesskeys = getBoolean(context, component,
118: "accesskeys", false);
119: String accesskeyFirst = (accesskeys) ? "f" : null;
120: String accesskeyPrev = (accesskeys) ? "p" : null;
121: String accesskeyNext = (accesskeys) ? "n" : null;
122: String accesskeyLast = (accesskeys) ? "l" : null;
123:
124: // get stuff for page size selection and display
125:
126: String textPageSize = getString(context, component,
127: "textPageSize", "Show {0}");
128: String textPageSizeAll = getString(context, component,
129: "textPageSizeAll", "all");
130: String pageSizesStr = getString(context, component,
131: "pageSizes", "5,10,20,50,100");
132: String[] pageSizes = pageSizesStr.split(",");
133: String idSelect = clientId + "_pageSize";
134:
135: String textStatus;
136: if (totalItems > 0) {
137: textStatus = getString(context, component, "textStatus",
138: "Viewing {0} to {1} of {2} {3}");
139: } else {
140: textStatus = getString(context, component,
141: "textStatusZeroItems", "Viewing 0 {3}");
142: }
143:
144: Object[] args = new Object[] { String.valueOf(firstItem + 1),
145: String.valueOf(lastItem), String.valueOf(totalItems),
146: textItem };
147: textStatus = MessageFormat.format(textStatus, args);
148:
149: // prepare the dropdown for selecting the
150: // TODO: Probably need to cache this for performance
151: String onchangeHandler = "javascript:this.form.submit(); return false;";
152: String selectedValue = String.valueOf(pageSize);
153: String[] optionTexts = new String[pageSizes.length + 1];
154: String[] optionValues = new String[pageSizes.length + 1];
155: for (int i = 0; i < pageSizes.length; i++) {
156: optionTexts[i] = MessageFormat.format(textPageSize,
157: new Object[] { pageSizes[i] });
158: optionValues[i] = pageSizes[i];
159: }
160: optionTexts[pageSizes.length] = MessageFormat.format(
161: textPageSize, new Object[] { textPageSizeAll });
162: optionValues[pageSizes.length] = "0";
163:
164: // Output HTML
165:
166: out.startElement("div", null);
167: out.writeAttribute("class", "listNav", null);
168:
169: writeStatus(out, textStatus);
170: writeButton(out, renderFirst, idFirst, labelFirst,
171: disabledFirst, titleFirst, accesskeyFirst);
172: writeButton(out, renderPrev, idPrev, labelPrev, disabledPrev,
173: titlePrev, accesskeyPrev);
174: writeSelect(out, renderPageSize, idSelect, optionTexts,
175: optionValues, selectedValue, onchangeHandler);
176: writeButton(out, renderNext, idNext, labelNext, disabledNext,
177: titleNext, accesskeyNext);
178: writeButton(out, renderLast, idLast, labelLast, disabledLast,
179: titleLast, accesskeyLast);
180:
181: // hidden state that prevents browser reloads from re-performing actions
182: // for example, if the user presses the button for the next page of items,
183: // and then reloads the browser window.
184: out.startElement("input", null);
185: out.writeAttribute("type", "hidden", null);
186: out.writeAttribute("name", idPastItem, null);
187: out.writeAttribute("value", String.valueOf(firstItem), null);
188: out.endElement("input");
189:
190: out.endElement("div");
191: }
192:
193: /** Output status display */
194: private static void writeStatus(ResponseWriter out, String status)
195: throws IOException {
196: out.startElement("div", null);
197: out.writeAttribute("class", "instruction", null);
198: out.writeText(status, null);
199: out.endElement("div");
200: }
201:
202: /** Output an HTML button */
203: private static void writeButton(ResponseWriter out, boolean render,
204: String name, String label, boolean disabled, String title,
205: String accesskey) throws IOException {
206: if (!render)
207: return;
208:
209: out.startElement("input", null);
210: out.writeAttribute("type", "submit", null);
211: out.writeAttribute("name", name, null);
212: out.writeAttribute("value", label, null);
213: // TODO: i18n
214: if (!disabled) {
215: out.writeAttribute("title", title, null);
216: if (accesskey != null)
217: out.writeAttribute("accesskey", accesskey, null);
218: //out.writeAttribute("onclick", "javascript:this.form.submit(); return false;", null);
219: } else {
220: out.writeAttribute("disabled", "disabled", null);
221: }
222: out.endElement("input");
223: out.write("\n");
224: }
225:
226: /** Output an HTML drop-down select */
227: private static void writeSelect(ResponseWriter out, boolean render,
228: String selectId, String[] optionTexts,
229: String[] optionValues, String selectedValue,
230: String onchangeHandler) throws IOException {
231: if (!render)
232: return;
233:
234: out.startElement("select", null);
235: out.writeAttribute("name", selectId, null);
236: out.writeAttribute("id", selectId, null);
237: out.writeAttribute("onchange", onchangeHandler, null);
238: out.write("\n");
239: for (int i = 0; i < optionValues.length; i++) {
240: String optionText = optionTexts[i];
241: String optionValue = optionValues[i];
242: out.startElement("option", null);
243: if (optionValue.equals(selectedValue))
244: out.writeAttribute("selected", "selected", null);
245: out.writeAttribute("value", optionValue, null);
246: out.writeText(optionText, null);
247: out.endElement("option");
248: out.write("\n");
249: }
250: out.endElement("select");
251: out.write("\n");
252: }
253:
254: public void decode(FacesContext context, UIComponent component) {
255: Map req = context.getExternalContext().getRequestParameterMap();
256:
257: String clientId = component.getClientId(context);
258: String idFirst = clientId + "_first";
259: String idPrev = clientId + "_prev";
260: String idNext = clientId + "_next";
261: String idLast = clientId + "_last";
262: String idSelect = clientId + "_pageSize";
263: String idPastItem = clientId + "_pastItem";
264:
265: int firstItem = getInt(context, component, "firstItem", 0);
266: int lastItem = getInt(context, component, "lastItem", 0);
267: int pageSize = getInt(context, component, "pageSize", 0);
268: int totalItems = getInt(context, component, "totalItems", 0);
269: if (log.isDebugEnabled())
270: log.debug("decode: firstItem=" + firstItem + ", pageSize="
271: + pageSize + ", value="
272: + getString(context, component, "value", null));
273:
274: int newFirstItem = firstItem;
275: int newLastItem = lastItem;
276: int newPageSize = pageSize;
277:
278: String str = (String) req.get(idPastItem);
279: // only perform actions if the current firstItem from the
280: // request matches the current firstItem state stored on the server.
281: // Prevents browser reloads from performing the same action again.
282: if (str != null && firstItem == Integer.valueOf(str).intValue()) {
283: // TODO: Seperate decoding from calculations (renderer vs component)
284: // check which button was pressed
285: if (req.containsKey(idFirst)) {
286: newFirstItem = 0;
287: } else if (req.containsKey(idPrev)) {
288: newFirstItem = Math.max(firstItem - pageSize, 0);
289: } else if (req.containsKey(idNext)) {
290: newFirstItem = Math.min(firstItem + pageSize,
291: totalItems - 1);
292: } else if (req.containsKey(idLast)) {
293: int lastPage = (totalItems - 1) / pageSize;
294: newFirstItem = lastPage * pageSize;
295: } else if (req.containsKey(idSelect)) {
296: newPageSize = Integer.parseInt((String) req
297: .get(idSelect));
298: }
299: }
300:
301: adjustState(context, component, firstItem, lastItem, pageSize,
302: totalItems, newFirstItem, newLastItem, newPageSize);
303: }
304:
305: private static String formatValue(int firstItem, int pageSize) {
306: return firstItem + "," + pageSize;
307: }
308:
309: /**
310: * Save the new paging state back to the given component (adjusting firstItem and lastItem first if necessary)
311: */
312: private static void adjustState(FacesContext context,
313: UIComponent component, int firstItem, int lastItem,
314: int pageSize, int totalItems, int newFirstItem,
315: int newLastItem, int newPageSize) {
316: // recalculate last item
317: newLastItem = Math.min(newFirstItem + newPageSize, totalItems);
318: if (newPageSize <= 0) {
319: // if displaying all items
320: newFirstItem = 0;
321: newLastItem = totalItems;
322: }
323:
324: // we don't count lastItem changing as a full state change (value of this component doesn't change)
325: if (newLastItem != lastItem)
326: RendererUtil.setAttribute(context, component, "lastItem",
327: new Integer(newLastItem));
328:
329: // send the newly changed values where they need to go
330: if (newPageSize != pageSize)
331: RendererUtil.setAttribute(context, component, "pageSize",
332: new Integer(newPageSize));
333: if (newFirstItem != firstItem)
334: RendererUtil.setAttribute(context, component, "firstItem",
335: new Integer(newFirstItem));
336:
337: // Set value, which causes registered valueChangeListener to be called
338: EditableValueHolder evh = (EditableValueHolder) component;
339: String newValue = formatValue(newFirstItem, newPageSize);
340: Object oldValue = (String) evh.getValue();
341: if (!newValue.equals(oldValue)) {
342: if (oldValue != null) {
343: evh.setSubmittedValue(newValue);
344: evh.setValid(true);
345: } else {
346: // Need to initialize value string based on initial parameters.
347: if (log.isDebugEnabled())
348: log.debug("initializing value to " + newValue);
349: evh.setValue(newValue);
350: }
351: }
352: }
353:
354: /**
355: * Retrieve an integer value from the component (or widget's
356: * resource bundle if not set on the component).
357: */
358: private static int getInt(FacesContext context,
359: UIComponent component, String attrName, int def) {
360: Object ret = getFromAttributeOrBundle(context, component,
361: attrName);
362:
363: if (ret instanceof Integer)
364: return ((Integer) ret).intValue();
365: if (ret instanceof String)
366: return Integer.valueOf((String) ret).intValue();
367: return def;
368: }
369:
370: /**
371: * Retrieve an boolean value from the component (or widget's
372: * resource bundle if not set on the component).
373: */
374: private static boolean getBoolean(FacesContext context,
375: UIComponent component, String attrName, boolean def) {
376: Object ret = getFromAttributeOrBundle(context, component,
377: attrName);
378: if (ret instanceof Boolean)
379: return ((Boolean) ret).booleanValue();
380: if (ret instanceof String)
381: return Boolean.valueOf((String) ret).booleanValue();
382: return def;
383: }
384:
385: /**
386: * Get a named attribute from the component or the widget resource bundle.
387: * @return The attribute value if it exists in the given component,
388: * or the attribute value from this widget's resource bundle, or
389: * the default if none of those exists.
390: */
391: private static String getString(FacesContext context,
392: UIComponent component, String attrName, String def) {
393: String ret = (String) getFromAttributeOrBundle(context,
394: component, attrName);
395: if (ret != null)
396: return ret;
397:
398: // otherwise, return the default
399: return def;
400: }
401:
402: /**
403: * Return the attribute value; whether from plain attributes,
404: * ValueBinding, or the widget resource bundle.
405: */
406: private static Object getFromAttributeOrBundle(
407: FacesContext context, UIComponent component, String name) {
408: // first try the attributes and value bindings
409: Object ret = RendererUtil
410: .getAttribute(context, component, name);
411: if (ret != null)
412: return ret;
413:
414: // next try the widget resource bundle
415: String str = null;
416: try {
417: str = LocaleUtil.getLocalizedString(context, BUNDLE_NAME,
418: "pager_" + name);
419: } catch (MissingResourceException e) {
420: // Returning null is fine here.
421: // TODO Distinguish between the dynamic variables we expect to find as an
422: // attribute and the static settings we expect to find in a resource bundle,
423: // rather than hiding which is which.
424: }
425: if (str != null && str.length() > 0)
426: return str;
427:
428: return null;
429: }
430: }
|