001: /* Listheader.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Fri Aug 5 13:06:59 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zul;
020:
021: import java.util.Iterator;
022: import java.util.List;
023: import java.util.Comparator;
024: import java.util.HashMap;
025:
026: import org.zkoss.lang.Objects;
027: import org.zkoss.lang.Classes;
028: import org.zkoss.xml.HTMLs;
029:
030: import org.zkoss.zk.ui.Page;
031: import org.zkoss.zk.ui.Component;
032: import org.zkoss.zk.ui.Components;
033: import org.zkoss.zk.ui.UiException;
034: import org.zkoss.zk.ui.WrongValueException;
035: import org.zkoss.zk.scripting.Namespace;
036: import org.zkoss.zk.scripting.Namespaces;
037:
038: import org.zkoss.zul.impl.HeaderElement;
039:
040: /**
041: * The list header which defines the attributes and header of a columen
042: * of a list box.
043: * Its parent must be {@link Listhead}.
044: *
045: * <p>Difference from XUL:
046: * <ol>
047: * <li>There is no listcol in ZUL because it is merged into {@link Listheader}.
048: * Reason: easier to write Listbox.</li>
049: * </ol>
050: *
051: * @author tomyeh
052: */
053: public class Listheader extends HeaderElement {
054: private static final long serialVersionUID = 20060731L;
055:
056: private String _sortDir = "natural";
057: private transient Comparator _sortAsc, _sortDsc;
058: private int _maxlength;
059:
060: public Listheader() {
061: }
062:
063: public Listheader(String label) {
064: setLabel(label);
065: }
066:
067: public Listheader(String label, String src) {
068: setLabel(label);
069: setImage(src);
070: }
071:
072: /** Returns the listbox that this belongs to.
073: */
074: public Listbox getListbox() {
075: final Component comp = getParent();
076: return comp != null ? (Listbox) comp.getParent() : null;
077: }
078:
079: /** Returns the listhead that this belongs to.
080: * @deprecated As of release 2.4.1, due to confusion
081: */
082: public Listhead getListhead() {
083: return (Listhead) getParent();
084: }
085:
086: /** Returns the sort direction.
087: * <p>Default: "natural".
088: */
089: public String getSortDirection() {
090: return _sortDir;
091: }
092:
093: /** Sets the sort direction. This does not sort the data, it only serves
094: * as an indicator as to how the list is sorted.
095: *
096: * <p>If you use {@link #sort(boolean)} to sort list items,
097: * the sort direction is maintained automatically.
098: * If you want to sort it in customized way, you have to set the
099: * sort direction manaully.
100: *
101: * @param sortDir one of "ascending", "descending" and "natural"
102: */
103: public void setSortDirection(String sortDir)
104: throws WrongValueException {
105: if (sortDir == null
106: || (!"ascending".equals(sortDir)
107: && !"descending".equals(sortDir) && !"natural"
108: .equals(sortDir)))
109: throw new WrongValueException("Unknown sort direction: "
110: + sortDir);
111: if (!Objects.equals(_sortDir, sortDir)) {
112: _sortDir = sortDir;
113: smartUpdate("z.sort", _sortDir); //don't use null because sel.js assumes it
114: }
115: }
116:
117: /** Sets the type of the sorter.
118: * You might specify either "auto" or "none".
119: *
120: * <p>If "auto" is specified, it will call
121: * {@link #setSortAscending} and/or {@link #setSortDescending}
122: * are called with {@link ListitemComparator}, if
123: * {@link #getSortDescending} and/or {@link #getSortAscending} are null.
124: * If you assigned a comparator to them, it won't be affected.
125: * The auto created comparator is case-insensitive.
126: *
127: * <p>If "none" is specified, both {@link #setSortAscending} and
128: * {@link #setSortDescending} are called with null.
129: * Therefore, no more sorting is available to users for this column.
130: */
131: public void setSort(String type) {
132: if ("auto".equals(type)) {
133: if (getSortAscending() == null)
134: setSortAscending(new ListitemComparator(this , true,
135: true));
136: if (getSortDescending() == null)
137: setSortDescending(new ListitemComparator(this , false,
138: true));
139: } else if ("none".equals(type)) {
140: setSortAscending((Comparator) null);
141: setSortDescending((Comparator) null);
142: }
143: }
144:
145: /** Returns the ascending sorter, or null if not available.
146: */
147: public Comparator getSortAscending() {
148: return _sortAsc;
149: }
150:
151: /** Sets the ascending sorter, or null for no sorter for
152: * the ascending order.
153: */
154: public void setSortAscending(Comparator sorter) {
155: if (!Objects.equals(_sortAsc, sorter)) {
156: if (sorter == null)
157: smartUpdate("z.asc", null);
158: else if (_sortAsc == null)
159: smartUpdate("z.asc", "true");
160: _sortAsc = sorter;
161: }
162: }
163:
164: /** Sets the ascending sorter with the class name, or null for
165: * no sorter for the ascending order.
166: */
167: public void setSortAscending(String clsnm)
168: throws ClassNotFoundException, InstantiationException,
169: IllegalAccessException {
170: setSortAscending(toComparator(clsnm));
171: }
172:
173: /** Returns the descending sorter, or null if not available.
174: */
175: public Comparator getSortDescending() {
176: return _sortDsc;
177: }
178:
179: /** Sets the descending sorter, or null for no sorter for the
180: * descending order.
181: */
182: public void setSortDescending(Comparator sorter) {
183: if (!Objects.equals(_sortDsc, sorter)) {
184: if (sorter == null)
185: smartUpdate("z.dsc", null);
186: else if (_sortDsc == null)
187: smartUpdate("z.dsc", "true");
188: _sortDsc = sorter;
189: }
190: }
191:
192: /** Sets the descending sorter with the class name, or null for
193: * no sorter for the descending order.
194: */
195: public void setSortDescending(String clsnm)
196: throws ClassNotFoundException, InstantiationException,
197: IllegalAccessException {
198: setSortDescending(toComparator(clsnm));
199: }
200:
201: private Comparator toComparator(String clsnm)
202: throws ClassNotFoundException, InstantiationException,
203: IllegalAccessException {
204: if (clsnm == null || clsnm.length() == 0)
205: return null;
206:
207: final Page page = getPage();
208: final Class cls = page != null ? page.getZScriptClass(clsnm)
209: : Classes.forNameByThread(clsnm);
210: if (cls == null)
211: throw new ClassNotFoundException(clsnm);
212: if (!Comparator.class.isAssignableFrom(cls))
213: throw new UiException("Comparator must be implemented: "
214: + clsnm);
215: return (Comparator) cls.newInstance();
216: }
217:
218: /** Returns the maximal length of each item's label.
219: * Default: 0 (no limit).
220: */
221: public int getMaxlength() {
222: return _maxlength;
223: }
224:
225: /** Sets the maximal length of each item's label.
226: */
227: public void setMaxlength(int maxlength) {
228: if (maxlength < 0)
229: maxlength = 0;
230: if (_maxlength != maxlength) {
231: _maxlength = maxlength;
232: invalidateCells();
233: }
234: }
235:
236: /** Returns the column index, starting from 0.
237: */
238: public int getColumnIndex() {
239: int j = 0;
240: for (Iterator it = getParent().getChildren().iterator(); it
241: .hasNext(); ++j)
242: if (it.next() == this )
243: break;
244: return j;
245: }
246:
247: /** Invalidates the relevant cells. */
248: private void invalidateCells() {
249: final Listbox listbox = getListbox();
250: if (listbox == null || listbox.inSelectMold())
251: return;
252:
253: final int jcol = getColumnIndex();
254: for (Iterator it = listbox.getItems().iterator(); it.hasNext();) {
255: final Listitem li = (Listitem) it.next();
256: final List chs = li.getChildren();
257: if (jcol < chs.size())
258: ((Component) chs.get(jcol)).invalidate();
259: }
260: }
261:
262: /** Sorts the list items based on {@link #getSortAscending}
263: * and {@link #getSortDescending}, if {@link #getSortDirection} doesn't
264: * matches the ascending argument.
265: *
266: * <p>It checks {@link #getSortDirection} to see whether sorting
267: * is required, and update {@link #setSortDirection} after sorted.
268: * For example, if {@link #getSortDirection} returns "ascending" and
269: * the ascending argument is false, nothing happens.
270: * To enforce the sorting, you can invoke {@link #setSortDirection}
271: * with "natural" before invoking this method.
272: * Alternatively, you can invoke {@link #sort(boolean, boolean)} instead.
273: *
274: * <p>It sorts the listitem by use of {@link Components#sort}
275: * data (i.e., {@link Grid#getModel} is null).
276: *
277: * <p>On the other hand, it invokes {@link ListModelExt#sort} to sort
278: * the list item, if live data (i.e., {@link Listbox#getModel} is not null).
279: * In other words, if you use the live data, you have to implement
280: * {@link ListModelExt} to sort the live data explicitly.
281: *
282: * @param ascending whether to use {@link #getSortAscending}.
283: * If the corresponding comparator is not set, it returns false
284: * and does nothing.
285: * @return whether the list items are sorted.
286: * @exception UiException if {@link Listbox#getModel} is not
287: * null but {@link ListModelExt} is not implemented.
288: */
289: public boolean sort(boolean ascending) {
290: final String dir = getSortDirection();
291: if (ascending) {
292: if ("ascending".equals(dir))
293: return false;
294: } else {
295: if ("descending".equals(dir))
296: return false;
297: }
298:
299: final Comparator cmpr = ascending ? _sortAsc : _sortDsc;
300: if (cmpr == null)
301: return false;
302:
303: final Listbox box = getListbox();
304: if (box == null)
305: return false;
306:
307: //comparator might be zscript
308: final HashMap backup = new HashMap();
309: final Namespace ns = Namespaces.beforeInterpret(backup, this ,
310: true);
311: try {
312: final ListModel model = box.getModel();
313: if (model != null) { //live data
314: if (!(model instanceof ListModelExt))
315: throw new UiException(
316: "ListModelExt must be implemented in "
317: + model.getClass().getName());
318: ((ListModelExt) model).sort(cmpr, ascending);
319: } else { //not live data
320: Components.sort(box.getItems(), cmpr);
321: }
322: } finally {
323: Namespaces.afterInterpret(backup, ns, true);
324: }
325:
326: //maintain
327: for (Iterator it = box.getListhead().getChildren().iterator(); it
328: .hasNext();) {
329: final Listheader hd = (Listheader) it.next();
330: hd.setSortDirection(hd != this ? "natural"
331: : ascending ? "ascending" : "descending");
332: }
333: return true;
334: }
335:
336: /** Sorts the list items based on {@link #getSortAscending}
337: * and {@link #getSortDescending}.
338: *
339: * @param ascending whether to use {@link #getSortAscending}.
340: * If the corresponding comparator is not set, it returns false
341: * and does nothing.
342: * @param force whether to enforce the sorting no matter what the sort
343: * direction ({@link #getSortDirection}) is.
344: * If false, this method is the same as {@link #sort(boolean)}.
345: * @return whether the rows are sorted.
346: */
347: public boolean sort(boolean ascending, boolean force) {
348: if (force)
349: setSortDirection("natural");
350: return sort(ascending);
351: }
352:
353: //-- event listener --//
354: /** It invokes {@link #sort(boolean)} to sort list items and maintain
355: * {@link #getSortDirection}.
356: */
357: public void onSort() {
358: final String dir = getSortDirection();
359: if ("ascending".equals(dir))
360: sort(false);
361: else if ("descending".equals(dir))
362: sort(true);
363: else if (!sort(true))
364: sort(false);
365: }
366:
367: //-- super --//
368: /** Returns the style class.
369: * If the style class is not defined ({@link #setSclass} is not called
370: * or called with null or empty), it returns "sort" if sortable,
371: * or null if not sortable.
372: * <p>By sortable we mean that {@link #setSortAscending}
373: * or {@link #setSortDescending}
374: * was called with a non-null comparator
375: */
376: public String getSclass() {
377: final String scls = super .getSclass();
378: if (scls != null)
379: return scls;
380: return _sortAsc != null || _sortDsc != null ? "sort" : null;
381: }
382:
383: public String getOuterAttrs() {
384: final StringBuffer sb = new StringBuffer(80);
385: if (_sortAsc != null)
386: sb.append(" z.asc=\"true\"");
387: if (_sortDsc != null)
388: sb.append(" z.dsc=\"true\"");
389:
390: if (!"natural".equals(_sortDir))
391: HTMLs.appendAttribute(sb, "z.sort", _sortDir);
392:
393: final String clkattrs = getAllOnClickAttrs(false);
394: if (clkattrs != null)
395: sb.append(clkattrs);
396:
397: final String attrs = super .getOuterAttrs();
398: if (sb.length() == 0)
399: return attrs;
400: return sb.insert(0, attrs).toString();
401: }
402:
403: //-- Internal use only --//
404: /** Returns the prefix of the first column (in HTML tags), null if this
405: * is not first column. Called only by listheader.dsp.
406: * @since 3.0.1
407: */
408: public String getColumnHtmlPrefix() {
409: final Listbox listbox = getListbox();
410: if (listbox != null && getParent().getFirstChild() == this
411: && listbox.isCheckmark() && listbox.isMultiple()) {
412: final StringBuffer sb = new StringBuffer(64);
413: sb.append("<input type=\"checkbox\"");
414: sb.append(" id=\"").append(getUuid()).append(
415: "!cm\" z.type=\"Lhfc\"/>");
416: return sb.toString();
417: }
418: return null;
419: }
420:
421: /** Invalidates the whole box. */
422: protected void invalidateWhole() {
423: final Listbox box = getListbox();
424: if (box != null)
425: box.invalidate();
426: }
427:
428: //-- Component --//
429: public void setParent(Component parent) {
430: if (parent != null && !(parent instanceof Listhead))
431: throw new UiException("Wrong parent: " + parent);
432: super .setParent(parent);
433: }
434:
435: //Cloneable//
436: public Object clone() {
437: final Listheader clone = (Listheader) super .clone();
438: clone.fixClone();
439: return clone;
440: }
441:
442: private void fixClone() {
443: if (_sortAsc instanceof ListitemComparator) {
444: final ListitemComparator c = (ListitemComparator) _sortAsc;
445: if (c.getListheader() == this && c.isAscending())
446: _sortAsc = new ListitemComparator(this , true, c
447: .shallIgnoreCase());
448: }
449: if (_sortDsc instanceof ListitemComparator) {
450: final ListitemComparator c = (ListitemComparator) _sortDsc;
451: if (c.getListheader() == this && !c.isAscending())
452: _sortDsc = new ListitemComparator(this , false, c
453: .shallIgnoreCase());
454: }
455: }
456:
457: //Serializable//
458: //NOTE: they must be declared as private
459: private synchronized void writeObject(java.io.ObjectOutputStream s)
460: throws java.io.IOException {
461: s.defaultWriteObject();
462:
463: boolean written = false;
464: if (_sortAsc instanceof ListitemComparator) {
465: final ListitemComparator c = (ListitemComparator) _sortAsc;
466: if (c.getListheader() == this && c.isAscending()) {
467: s.writeBoolean(true);
468: s.writeBoolean(c.shallIgnoreCase());
469: s.writeBoolean(c.byValue());
470: written = true;
471: }
472: }
473: if (!written) {
474: s.writeBoolean(false);
475: s.writeObject(_sortAsc);
476: }
477:
478: written = false;
479: if (_sortDsc instanceof ListitemComparator) {
480: final ListitemComparator c = (ListitemComparator) _sortDsc;
481: if (c.getListheader() == this && !c.isAscending()) {
482: s.writeBoolean(true);
483: s.writeBoolean(c.shallIgnoreCase());
484: s.writeBoolean(c.byValue());
485: written = true;
486: }
487: }
488: if (!written) {
489: s.writeBoolean(false);
490: s.writeObject(_sortDsc);
491: }
492: }
493:
494: private synchronized void readObject(java.io.ObjectInputStream s)
495: throws java.io.IOException, ClassNotFoundException {
496: s.defaultReadObject();
497:
498: boolean b = s.readBoolean();
499: if (b) {
500: final boolean igcs = s.readBoolean();
501: final boolean byval = s.readBoolean();
502: _sortAsc = new ListitemComparator(this , true, igcs, byval);
503: } else {
504: _sortAsc = (ListitemComparator) s.readObject();
505: }
506:
507: b = s.readBoolean();
508: if (b) {
509: final boolean igcs = s.readBoolean();
510: final boolean byval = s.readBoolean();
511: _sortDsc = new ListitemComparator(this , false, igcs, byval);
512: } else {
513: _sortDsc = (ListitemComparator) s.readObject();
514: }
515: }
516: }
|