001: /* Column.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Tue Oct 25 16:02:36 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.Comparator;
023: import java.util.HashMap;
024:
025: import org.zkoss.lang.Objects;
026: import org.zkoss.lang.Classes;
027: import org.zkoss.xml.HTMLs;
028:
029: import org.zkoss.zk.ui.Page;
030: import org.zkoss.zk.ui.Component;
031: import org.zkoss.zk.ui.Components;
032: import org.zkoss.zk.ui.UiException;
033: import org.zkoss.zk.ui.WrongValueException;
034: import org.zkoss.zk.scripting.Namespace;
035: import org.zkoss.zk.scripting.Namespaces;
036:
037: import org.zkoss.zul.impl.HeaderElement;
038:
039: /**
040: * A single column in a {@link Columns} element.
041: * Each child of the {@link Column} element is placed in each successive
042: * cell of the grid.
043: * The column with the most child elements determines the number of rows
044: * in each column.
045: *
046: * <p>The use of column is mainly to define attributes for each cell
047: * in the grid.
048: *
049: * @author tomyeh
050: */
051: public class Column extends HeaderElement {
052: private String _sortDir = "natural";
053: private Comparator _sortAsc, _sortDsc;
054:
055: public Column() {
056: }
057:
058: public Column(String label) {
059: setLabel(label);
060: }
061:
062: public Column(String label, String src) {
063: setLabel(label);
064: setImage(src);
065: }
066:
067: /** Returns the grid that contains this column. */
068: public Grid getGrid() {
069: final Component parent = getParent();
070: return parent != null ? (Grid) parent.getParent() : null;
071: }
072:
073: /** Returns the sort direction.
074: * <p>Default: "natural".
075: */
076: public String getSortDirection() {
077: return _sortDir;
078: }
079:
080: /** Sets the sort direction. This does not sort the data, it only serves
081: * as an indicator as to how the grid is sorted.
082: *
083: * <p>If you use {@link #sort(boolean)} to sort rows ({@link Row}),
084: * the sort direction is maintained automatically.
085: * If you want to sort it in customized way, you have to set the
086: * sort direction manaully.
087: *
088: * @param sortDir one of "ascending", "descending" and "natural"
089: */
090: public void setSortDirection(String sortDir)
091: throws WrongValueException {
092: if (sortDir == null
093: || (!"ascending".equals(sortDir)
094: && !"descending".equals(sortDir) && !"natural"
095: .equals(sortDir)))
096: throw new WrongValueException("Unknown sort direction: "
097: + sortDir);
098: if (!Objects.equals(_sortDir, sortDir)) {
099: _sortDir = sortDir;
100: smartUpdate("z.sort", _sortDir); //don't use null because sel.js assumes it
101: }
102: }
103:
104: /** Returns the ascending sorter, or null if not available.
105: */
106: public Comparator getSortAscending() {
107: return _sortAsc;
108: }
109:
110: /** Sets the ascending sorter, or null for no sorter for
111: * the ascending order.
112: */
113: public void setSortAscending(Comparator sorter) {
114: if (!Objects.equals(_sortAsc, sorter)) {
115: if (sorter == null)
116: smartUpdate("z.asc", null);
117: else if (_sortAsc == null)
118: smartUpdate("z.asc", "true");
119: _sortAsc = sorter;
120: }
121: }
122:
123: /** Sets the ascending sorter with the class name, or null for
124: * no sorter for the ascending order.
125: */
126: public void setSortAscending(String clsnm)
127: throws ClassNotFoundException, InstantiationException,
128: IllegalAccessException {
129: setSortAscending(toComparator(clsnm));
130: }
131:
132: /** Returns the descending sorter, or null if not available.
133: */
134: public Comparator getSortDescending() {
135: return _sortDsc;
136: }
137:
138: /** Sets the descending sorter, or null for no sorter for the
139: * descending order.
140: */
141: public void setSortDescending(Comparator sorter) {
142: if (!Objects.equals(_sortDsc, sorter)) {
143: if (sorter == null)
144: smartUpdate("z.dsc", null);
145: else if (_sortDsc == null)
146: smartUpdate("z.dsc", "true");
147: _sortDsc = sorter;
148: }
149: }
150:
151: /** Sets the descending sorter with the class name, or null for
152: * no sorter for the descending order.
153: */
154: public void setSortDescending(String clsnm)
155: throws ClassNotFoundException, InstantiationException,
156: IllegalAccessException {
157: setSortDescending(toComparator(clsnm));
158: }
159:
160: private Comparator toComparator(String clsnm)
161: throws ClassNotFoundException, InstantiationException,
162: IllegalAccessException {
163: if (clsnm == null || clsnm.length() == 0)
164: return null;
165:
166: final Page page = getPage();
167: final Class cls = page != null ? page.getZScriptClass(clsnm)
168: : Classes.forNameByThread(clsnm);
169: if (cls == null)
170: throw new ClassNotFoundException(clsnm);
171: if (!Comparator.class.isAssignableFrom(cls))
172: throw new UiException("Comparator must be implemented: "
173: + clsnm);
174: return (Comparator) cls.newInstance();
175: }
176:
177: /** Sorts the rows ({@link Row}) based on {@link #getSortAscending}
178: * and {@link #getSortDescending}, if {@link #getSortDirection} doesn't
179: * matches the ascending argument.
180: *
181: * <p>It checks {@link #getSortDirection} to see whether sorting
182: * is required, and update {@link #setSortDirection} after sorted.
183: * For example, if {@link #getSortDirection} returns "ascending" and
184: * the ascending argument is false, nothing happens.
185: * To enforce the sorting, you can invoke {@link #setSortDirection}
186: * with "natural" before invoking this method.
187: * Alternatively, you can invoke {@link #sort(boolean, boolean)} instead.
188: *
189: * <p>It sorts the rows by use of {@link Components#sort}, if not live
190: * data (i.e., {@link Grid#getModel} is null).
191: *
192: * <p>On the other hand, it invokes {@link ListModelExt#sort} to sort
193: * the rows, if live data (i.e., {@link Grid#getModel} is not null).
194: * In other words, if you use the live data, you have to implement
195: * {@link ListModelExt} to sort the live data explicitly.
196: *
197: * @param ascending whether to use {@link #getSortAscending}.
198: * If the corresponding comparator is not set, it returns false
199: * and does nothing.
200: * @return whether the rows are sorted.
201: * @exception UiException if {@link Grid#getModel} is not
202: * null but {@link ListModelExt} is not implemented.
203: */
204: public boolean sort(boolean ascending) {
205: final String dir = getSortDirection();
206: if (ascending) {
207: if ("ascending".equals(dir))
208: return false;
209: } else {
210: if ("descending".equals(dir))
211: return false;
212: }
213:
214: final Comparator cmpr = ascending ? _sortAsc : _sortDsc;
215: if (cmpr == null)
216: return false;
217:
218: final Grid grid = getGrid();
219: if (grid == null)
220: return false;
221:
222: //comparator might be zscript
223: final HashMap backup = new HashMap();
224: final Namespace ns = Namespaces.beforeInterpret(backup, this ,
225: true);
226: try {
227: final ListModel model = grid.getModel();
228: if (model != null) { //live data
229: if (!(model instanceof ListModelExt))
230: throw new UiException(
231: "ListModelExt must be implemented in "
232: + model.getClass().getName());
233: ((ListModelExt) model).sort(cmpr, ascending);
234: } else { //not live data
235: Components.sort(grid.getRows().getChildren(), cmpr);
236: }
237: } finally {
238: Namespaces.afterInterpret(backup, ns, true);
239: }
240:
241: //maintain
242: for (Iterator it = grid.getColumns().getChildren().iterator(); it
243: .hasNext();) {
244: final Column hd = (Column) it.next();
245: hd.setSortDirection(hd != this ? "natural"
246: : ascending ? "ascending" : "descending");
247: }
248: return true;
249: }
250:
251: /** Sorts the rows ({@link Row}) based on {@link #getSortAscending}
252: * and {@link #getSortDescending}.
253: *
254: * @param ascending whether to use {@link #getSortAscending}.
255: * If the corresponding comparator is not set, it returns false
256: * and does nothing.
257: * @param force whether to enforce the sorting no matter what the sort
258: * direction ({@link #getSortDirection}) is.
259: * If false, this method is the same as {@link #sort(boolean)}.
260: * @return whether the rows are sorted.
261: */
262: public boolean sort(boolean ascending, boolean force) {
263: if (force)
264: setSortDirection("natural");
265: return sort(ascending);
266: }
267:
268: //-- event listener --//
269: /** It invokes {@link #sort(boolean)} to sort list items and maintain
270: * {@link #getSortDirection}.
271: */
272: public void onSort() {
273: final String dir = getSortDirection();
274: if ("ascending".equals(dir))
275: sort(false);
276: else if ("descending".equals(dir))
277: sort(true);
278: else if (!sort(true))
279: sort(false);
280: }
281:
282: /** Returns the style class.
283: * If the style class is not defined ({@link #setSclass} is not called
284: * or called with null or empty), it returns "sort" if sortable,
285: * or null if not sortable.
286: * <p>By sortable we mean that {@link #setSortAscending}
287: * or {@link #setSortDescending}
288: * was called with a non-null comparator
289: */
290: public String getSclass() {
291: final String scls = super .getSclass();
292: if (scls != null)
293: return scls;
294: return _sortAsc != null || _sortDsc != null ? "sort" : null;
295: }
296:
297: public String getOuterAttrs() {
298: final StringBuffer sb = new StringBuffer(80);
299: if (_sortAsc != null)
300: sb.append(" z.asc=\"true\"");
301: if (_sortDsc != null)
302: sb.append(" z.dsc=\"true\"");
303:
304: if (!"natural".equals(_sortDir))
305: HTMLs.appendAttribute(sb, "z.sort", _sortDir);
306:
307: final String clkattrs = getAllOnClickAttrs(false);
308: if (clkattrs != null)
309: sb.append(clkattrs);
310:
311: final String attrs = super .getOuterAttrs();
312: if (sb.length() == 0)
313: return attrs;
314: return sb.insert(0, attrs).toString();
315: }
316:
317: /** Invalidates the whole grid. */
318: protected void invalidateWhole() {
319: final Grid grid = getGrid();
320: if (grid != null)
321: grid.invalidate();
322: }
323:
324: //-- Component --//
325: public void setParent(Component parent) {
326: if (parent != null && !(parent instanceof Columns))
327: throw new UiException("Unsupported parent for column: "
328: + parent);
329: super.setParent(parent);
330: }
331: }
|