0001: /* Grid.java
0002:
0003: {{IS_NOTE
0004: Purpose:
0005:
0006: Description:
0007:
0008: History:
0009: Tue Oct 25 15:40:35 2005, Created by tomyeh
0010: }}IS_NOTE
0011:
0012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
0013:
0014: {{IS_RIGHT
0015: This program is distributed under GPL Version 2.0 in the hope that
0016: it will be useful, but WITHOUT ANY WARRANTY.
0017: }}IS_RIGHT
0018: */
0019: package org.zkoss.zul;
0020:
0021: import java.util.Collection;
0022: import java.util.AbstractCollection;
0023: import java.util.List;
0024: import java.util.ArrayList;
0025: import java.util.LinkedList;
0026: import java.util.ListIterator;
0027: import java.util.Iterator;
0028: import java.util.Set;
0029:
0030: import org.zkoss.lang.D;
0031: import org.zkoss.lang.Objects;
0032: import org.zkoss.lang.Classes;
0033: import org.zkoss.lang.Exceptions;
0034: import org.zkoss.util.logging.Log;
0035: import org.zkoss.xml.HTMLs;
0036:
0037: import org.zkoss.zk.ui.Component;
0038: import org.zkoss.zk.ui.UiException;
0039: import org.zkoss.zk.ui.ext.client.RenderOnDemand;
0040: import org.zkoss.zk.ui.ext.client.InnerWidth;
0041: import org.zkoss.zk.ui.event.Event;
0042: import org.zkoss.zk.ui.event.EventListener;
0043: import org.zkoss.zk.ui.event.Events;
0044:
0045: import org.zkoss.zul.impl.XulElement;
0046: import org.zkoss.zul.ext.Paginal;
0047: import org.zkoss.zul.event.ListDataEvent;
0048: import org.zkoss.zul.event.ListDataListener;
0049: import org.zkoss.zul.event.ZulEvents;
0050: import org.zkoss.zul.event.PagingEvent;
0051:
0052: /**
0053: * A grid is an element that contains both rows and columns elements.
0054: * It is used to create a grid of elements.
0055: * Both the rows and columns are displayed at once although only one will
0056: * typically contain content, while the other may provide size information.
0057: *
0058: * <p>Besides creating {@link Row} programmingly, you can assign
0059: * a data model (a {@link ListModel} instance) to a grid via
0060: * {@link #setModel} and then the grid will retrieve data
0061: * by calling {@link ListModel#getElementAt} when necessary.
0062: *
0063: * <p>Besides assign a list model, you could assign a renderer
0064: * (a {@link RowRenderer} instance) to a grid, such that
0065: * the grid will use this
0066: * renderer to render the data returned by {@link ListModel#getElementAt}.
0067: * If not assigned, the default renderer, which assumes a label per row,
0068: * is used.
0069: * In other words, the default renderer adds a label to
0070: * a row by calling toString against the object returned
0071: * by {@link ListModel#getElementAt}
0072: *
0073: * <p>There are two ways to handle long content: scrolling and paging.
0074: * If {@link #getMold} is "default", scrolling is used if {@link #setHeight}
0075: * is called and too much content to display.
0076: * If {@link #getMold} is "paging", paging is used if two or more pages are
0077: * required. To control the number of rows to display in a page, use
0078: * {@link #setPageSize}.
0079: *
0080: * <p>If paging is used, the page controller is either created automatically
0081: * or assigned explicity by {@link #setPaginal}.
0082: * The paging controller specified explicitly by {@link #setPaginal} is called
0083: * the external page controller. It is useful if you want to put the paging
0084: * controller at different location (other than as a child component), or
0085: * you want to use the same controller to control multiple grids.
0086: *
0087: * <p>Default {@link #getSclass}: grid.
0088: *
0089: * <p>To have a grid without stripping, you can specify a non-existent
0090: * style class to {@link #setOddRowSclass}.
0091: * If you want to disable all striping, you can specify the style:
0092: * <pre><code>
0093: tr.odd td.gc {
0094: background: white;
0095: }
0096: * </code></pre>
0097: *
0098: * @author tomyeh
0099: * @see ListModel
0100: * @see RowRenderer
0101: * @see RowRendererExt
0102: */
0103: public class Grid extends XulElement {
0104: private static final Log log = Log.lookup(Grid.class);
0105:
0106: private transient Rows _rows;
0107: private transient Columns _cols;
0108: private transient Foot _foot;
0109: private transient Collection _heads;
0110: private String _align;
0111: private ListModel _model;
0112: private RowRenderer _renderer;
0113: private transient ListDataListener _dataListener;
0114: /** The paging controller, used only if mold = "paging". */
0115: private transient Paginal _pgi;
0116: /** The paging controller, used only if mold = "paging" and user
0117: * doesn't assign a controller via {@link #setPaginal}.
0118: * If exists, it is the last child.
0119: */
0120: private transient Paging _paging;
0121: private transient EventListener _pgListener, _pgImpListener;
0122: /** The style class of the odd row. */
0123: private String _scOddRow = "odd";
0124: /** the # of rows to preload. */
0125: private int _preloadsz = 7;
0126: private String _innerWidth = "100%";
0127:
0128: public Grid() {
0129: setSclass("grid");
0130: init();
0131: }
0132:
0133: private void init() {
0134: _heads = new AbstractCollection() {
0135: public int size() {
0136: int sz = getChildren().size();
0137: if (_rows != null)
0138: --sz;
0139: if (_foot != null)
0140: --sz;
0141: if (_paging != null)
0142: --sz;
0143: return sz;
0144: }
0145:
0146: public Iterator iterator() {
0147: return new Iter();
0148: }
0149: };
0150: }
0151:
0152: /** Returns the rows.
0153: */
0154: public Rows getRows() {
0155: return _rows;
0156: }
0157:
0158: /** Returns the columns.
0159: */
0160: public Columns getColumns() {
0161: return _cols;
0162: }
0163:
0164: /** Returns the foot.
0165: */
0166: public Foot getFoot() {
0167: return _foot;
0168: }
0169:
0170: /** Returns a collection of heads, including {@link #getColumns}
0171: * and auxiliary heads ({@link Auxhead}) (never null).
0172: *
0173: * @since 3.0.0
0174: */
0175: public Collection getHeads() {
0176: return _heads;
0177: }
0178:
0179: /** Returns the specified cell, or null if not available.
0180: * @param row which row to fetch (starting at 0).
0181: * @param col which column to fetch (starting at 0).
0182: */
0183: public Component getCell(int row, int col) {
0184: final Rows rows = getRows();
0185: if (rows == null)
0186: return null;
0187:
0188: List children = rows.getChildren();
0189: if (children.size() <= row)
0190: return null;
0191:
0192: children = ((Row) children.get(row)).getChildren();
0193: return children.size() <= col ? null : (Component) children
0194: .get(col);
0195: }
0196:
0197: /** Returns the horizontal alignment of the whole grid.
0198: * <p>Default: null (system default: left unless CSS specified).
0199: */
0200: public String getAlign() {
0201: return _align;
0202: }
0203:
0204: /** Sets the horizontal alignment of the whole grid.
0205: * <p>Allowed: "left", "center", "right"
0206: */
0207: public void setAlign(String align) {
0208: if (!Objects.equals(_align, align)) {
0209: _align = align;
0210: smartUpdate("align", _align);
0211: }
0212: }
0213:
0214: //--Paging--//
0215: /** Returns the paging controller, or null if not available.
0216: * Note: the paging controller is used only if {@link #getMold} is "paging".
0217: *
0218: * <p>If mold is "paging", this method never returns null, because
0219: * a child paging controller is created automcatically (if not specified
0220: * by developers with {@link #setPaginal}).
0221: *
0222: * <p>If a paging controller is specified (either by {@link #setPaginal},
0223: * or by {@link #setMold} with "paging"),
0224: * the grid will rely on the paging controller to handle long-content
0225: * instead of scrolling.
0226: */
0227: public Paginal getPaginal() {
0228: return _pgi;
0229: }
0230:
0231: /* Specifies the paging controller.
0232: * Note: the paging controller is used only if {@link #getMold} is "paging".
0233: *
0234: * <p>It is OK, though without any effect, to specify a paging controller
0235: * even if mold is not "paging".
0236: *
0237: * @param pgi the paging controller. If null and {@link #getMold} is "paging",
0238: * a paging controller is created automatically as a child component
0239: * (see {@link #getPaging}).
0240: */
0241: public void setPaginal(Paginal pgi) {
0242: if (!Objects.equals(pgi, _pgi)) {
0243: final Paginal old = _pgi;
0244: _pgi = pgi;
0245:
0246: if (inPagingMold()) {
0247: if (old != null)
0248: removePagingListener(old);
0249: if (_pgi == null) {
0250: if (_paging != null)
0251: _pgi = _paging;
0252: else
0253: newInternalPaging();
0254: } else { //_pgi != null
0255: if (_pgi != _paging) {
0256: if (_paging != null)
0257: _paging.detach();
0258: _pgi.setTotalSize(_rows != null ? _rows
0259: .getChildren().size() : 0);
0260: addPagingListener(_pgi);
0261: }
0262: }
0263: }
0264: }
0265: }
0266:
0267: /** Creates the internal paging component.
0268: */
0269: private void newInternalPaging() {
0270: assert D.OFF || inPagingMold() : "paging mold only";
0271: assert D.OFF || (_paging == null && _pgi == null);
0272:
0273: final Paging paging = new Paging();
0274: paging.setAutohide(true);
0275: paging.setDetailed(true);
0276: paging.setTotalSize(_rows != null ? _rows.getChildren().size()
0277: : 0);
0278: paging.setParent(this );
0279: addPagingListener(_pgi);
0280: }
0281:
0282: /** Adds the event listener for the onPaging event. */
0283: private void addPagingListener(Paginal pgi) {
0284: if (_pgListener == null)
0285: _pgListener = new EventListener() {
0286: public void onEvent(Event event) {
0287: final PagingEvent evt = (PagingEvent) event;
0288: Events.postEvent(new PagingEvent(evt.getName(),
0289: Grid.this , evt.getPageable(), evt
0290: .getActivePage()));
0291: }
0292: };
0293: pgi.addEventListener(ZulEvents.ON_PAGING, _pgListener);
0294:
0295: if (_pgImpListener == null)
0296: _pgImpListener = new EventListener() {
0297: public void onEvent(Event event) {
0298: if (_rows != null && _model != null
0299: && inPagingMold()) {
0300: //theorectically, _rows shall not be null if _model is not null when
0301: //this method is called. But, just in case -- if sent manually
0302: final Renderer renderer = new Renderer();
0303: try {
0304: final Paginal pgi = getPaginal();
0305: int pgsz = pgi.getPageSize();
0306: final int ofs = pgi.getActivePage() * pgsz;
0307: for (final Iterator it = _rows
0308: .getChildren().listIterator(ofs); --pgsz >= 0
0309: && it.hasNext();)
0310: renderer.render((Row) it.next());
0311: } catch (Throwable ex) {
0312: renderer.doCatch(ex);
0313: } finally {
0314: renderer.doFinally();
0315: }
0316: }
0317:
0318: if (_rows != null)
0319: _rows.invalidate();
0320: }
0321: };
0322: pgi.addEventListener("onPagingImpl", _pgImpListener);
0323: }
0324:
0325: /** Removes the event listener for the onPaging event. */
0326: private void removePagingListener(Paginal pgi) {
0327: pgi.removeEventListener(ZulEvents.ON_PAGING, _pgListener);
0328: pgi.removeEventListener("onPagingImpl", _pgImpListener);
0329: }
0330:
0331: /** Returns the child paging controller that is created automatically,
0332: * or null if mold is not "paging", or the controller is specified externally
0333: * by {@link #setPaginal}.
0334: */
0335: public Paging getPaging() {
0336: return _paging;
0337: }
0338:
0339: /** Returns the page size, aka., the number rows per page.
0340: * @exception IllegalStateException if {@link #getPaginal} returns null,
0341: * i.e., mold is not "paging" and no external controller is specified.
0342: */
0343: public int getPageSize() {
0344: if (_pgi == null)
0345: throw new IllegalStateException(
0346: "Available only the paging mold");
0347: return _pgi.getPageSize();
0348: }
0349:
0350: /** Sets the page size, aka., the number rows per page.
0351: * @exception IllegalStateException if {@link #getPaginal} returns null,
0352: * i.e., mold is not "paging" and no external controller is specified.
0353: */
0354: public void setPageSize(int pgsz) {
0355: if (_pgi == null)
0356: throw new IllegalStateException(
0357: "Available only the paging mold");
0358: _pgi.setPageSize(pgsz);
0359: }
0360:
0361: /** Returns whether this grid is in the paging mold.
0362: */
0363: /*package*/boolean inPagingMold() {
0364: return "paging".equals(getMold());
0365: }
0366:
0367: //-- ListModel dependent codes --//
0368: /** Returns the list model associated with this grid, or null
0369: * if this grid is not associated with any list data model.
0370: */
0371: public ListModel getModel() {
0372: return _model;
0373: }
0374:
0375: /** Sets the list model associated with this grid.
0376: * If a non-null model is assigned, no matter whether it is the same as
0377: * the previous, it will always cause re-render.
0378: *
0379: * @param model the list model to associate, or null to dis-associate
0380: * any previous model.
0381: * @exception UiException if failed to initialize with the model
0382: */
0383: public void setModel(ListModel model) {
0384: if (model != null) {
0385: if (_model != model) {
0386: if (_model != null) {
0387: _model.removeListDataListener(_dataListener);
0388: } else {
0389: if (_rows != null)
0390: _rows.getChildren().clear(); //Bug 1807414
0391: smartUpdate("z.model", "true");
0392: }
0393:
0394: initDataListener();
0395: _model = model;
0396: _model.addListDataListener(_dataListener);
0397: }
0398:
0399: //Always syncModel because it is easier for user to enfore reload
0400: syncModel(-1, -1); //create rows if necessary
0401: postOnInitRender();
0402: //Since user might setModel and setRender separately or repeatedly,
0403: //we don't handle it right now until the event processing phase
0404: //such that we won't render the same set of data twice
0405: //--
0406: //For better performance, we shall load the first few row now
0407: //(to save a roundtrip)
0408: } else if (_model != null) {
0409: _model.removeListDataListener(_dataListener);
0410: _model = null;
0411: if (_rows != null)
0412: _rows.getChildren().clear();
0413: smartUpdate("z.model", null);
0414: }
0415: }
0416:
0417: private void initDataListener() {
0418: if (_dataListener == null)
0419: _dataListener = new ListDataListener() {
0420: public void onChange(ListDataEvent event) {
0421: onListDataChange(event);
0422: }
0423: };
0424: }
0425:
0426: /** Returns the renderer to render each row, or null if the default
0427: * renderer is used.
0428: */
0429: public RowRenderer getRowRenderer() {
0430: return _renderer;
0431: }
0432:
0433: /** Sets the renderer which is used to render each row
0434: * if {@link #getModel} is not null.
0435: *
0436: * <p>Note: changing a render will not cause the grid to re-render.
0437: * If you want it to re-render, you could assign the same model again
0438: * (i.e., setModel(getModel())), or fire an {@link ListDataEvent} event.
0439: *
0440: * @param renderer the renderer, or null to use the default.
0441: * @exception UiException if failed to initialize with the model
0442: */
0443: public void setRowRenderer(RowRenderer renderer) {
0444: _renderer = renderer;
0445: }
0446:
0447: /** Sets the renderer by use of a class name.
0448: * It creates an instance automatically.
0449: */
0450: public void setRowRenderer(String clsnm)
0451: throws ClassNotFoundException, NoSuchMethodException,
0452: IllegalAccessException, InstantiationException,
0453: java.lang.reflect.InvocationTargetException {
0454: if (clsnm != null)
0455: setRowRenderer((RowRenderer) Classes
0456: .newInstanceByThread(clsnm));
0457: }
0458:
0459: /** Returns the number of rows to preload when receiving
0460: * the rendering request from the client.
0461: *
0462: * <p>Default: 7.
0463: *
0464: * <p>It is used only if live data ({@link #setModel} and
0465: * not paging ({@link #getPaging}.
0466: *
0467: * @since 2.4.1
0468: */
0469: public int getPreloadSize() {
0470: return _preloadsz;
0471: }
0472:
0473: /** Sets the number of rows to preload when receiving
0474: * the rendering request from the client.
0475: * <p>It is used only if live data ({@link #setModel} and
0476: * not paging ({@link #getPaging}.
0477: *
0478: * @param sz the number of rows to preload. If zero, no preload
0479: * at all.
0480: * @exception UiException if sz is negative
0481: * @since 2.4.1
0482: */
0483: public void setPreloadSize(int sz) {
0484: if (sz < 0)
0485: throw new UiException("nonnegative is required: " + sz);
0486: _preloadsz = sz;
0487: }
0488:
0489: /**
0490: * Sets the inner width of this component.
0491: * The inner width is the width of the inner table.
0492: * By default, it is 100%. That is, it is the same as the width
0493: * of this component. However, it is changed when the user
0494: * is sizing the column's width.
0495: *
0496: * <p>Application developers rarely call this method, unless
0497: * they want to preserve the widths of sizable columns
0498: * changed by the user.
0499: * To preserve the widths, the developer have to store the widths of
0500: * all columns and the inner width ({@link #getInnerWidth}),
0501: * and then restore them when re-creating this component.
0502: *
0503: * @param innerWidth the inner width. If null, "100%" is assumed.
0504: * @since 3.0.0
0505: */
0506: public void setInnerWidth(String innerWidth) {
0507: if (innerWidth == null)
0508: innerWidth = "100%";
0509: if (!_innerWidth.equals(innerWidth)) {
0510: _innerWidth = innerWidth;
0511: smartUpdate("z.innerWidth", innerWidth);
0512: }
0513: }
0514:
0515: /**
0516: * Returns the inner width of this component.
0517: * The inner width is the width of the inner table.
0518: * <p>Default: "100%"
0519: * @see #setInnerWidth
0520: * @since 3.0.0
0521: */
0522: public String getInnerWidth() {
0523: return _innerWidth;
0524: }
0525:
0526: /** Synchronizes the grid to be consistent with the specified model.
0527: *
0528: * @param min the lower index that a range of invalidated rows
0529: * @param max the higher index that a range of invalidated rows
0530: */
0531: private void syncModel(int min, int max) {
0532: RowRenderer renderer = null;
0533: final int newsz = _model.getSize();
0534: final int oldsz = _rows != null ? _rows.getChildren().size()
0535: : 0;
0536: if (oldsz > 0) {
0537: if (newsz > 0 && min < oldsz) {
0538: if (max < 0 || max >= oldsz)
0539: max = oldsz - 1;
0540: if (max >= newsz)
0541: max = newsz - 1;
0542: if (min < 0)
0543: min = 0;
0544:
0545: //unloadRow() might detach and insert row into _rows, must make a copy for iterate.
0546: for (Iterator it = new ArrayList(_rows.getChildren())
0547: .listIterator(min); min <= max && it.hasNext(); ++min) {
0548: final Row row = (Row) it.next();
0549: if (row.isLoaded()) {
0550: if (renderer == null)
0551: renderer = getRealRenderer();
0552: unloadRow(renderer, row);
0553: }
0554: }
0555: }
0556:
0557: //detach and remove
0558: if (oldsz > newsz) {
0559: for (Iterator it = _rows.getChildren().listIterator(
0560: newsz); it.hasNext();) {
0561: it.next();
0562: it.remove();
0563: }
0564: }
0565: }
0566:
0567: //auto create but it means <grid model="xx"><rows/>... will fail
0568: if (_rows == null)
0569: new Rows().setParent(this );
0570:
0571: for (int j = oldsz; j < newsz; ++j) {
0572: if (renderer == null)
0573: renderer = getRealRenderer();
0574: newUnloadedRow(renderer).setParent(_rows);
0575: }
0576: }
0577:
0578: /** Creates an new and unloaded row. */
0579: private final Row newUnloadedRow(RowRenderer renderer) {
0580: Row row = null;
0581: if (renderer instanceof RowRendererExt)
0582: row = ((RowRendererExt) renderer).newRow(this );
0583:
0584: if (row == null) {
0585: row = new Row();
0586: row.applyProperties();
0587: }
0588: row.setLoaded(false);
0589:
0590: newUnloadedCell(renderer, row);
0591: return row;
0592: }
0593:
0594: private Component newUnloadedCell(RowRenderer renderer, Row row) {
0595: Component cell = null;
0596: if (renderer instanceof RowRendererExt)
0597: cell = ((RowRendererExt) renderer).newCell(row);
0598:
0599: if (cell == null) {
0600: cell = newRenderLabel(null);
0601: cell.applyProperties();
0602: }
0603: cell.setParent(row);
0604: return cell;
0605: }
0606:
0607: /** Returns the label for the cell generated by the default renderer.
0608: */
0609: private static Label newRenderLabel(String value) {
0610: final Label label = new Label(value != null
0611: && value.length() > 0 ? value : " ");
0612: label.setPre(true); //to make sure is generated, and then occupies some space
0613: return label;
0614: }
0615:
0616: /** Clears a row as if it is not loaded. */
0617: private final void unloadRow(RowRenderer renderer, Row row) {
0618: if (!(renderer instanceof RowRendererExt)
0619: || (((RowRendererExt) renderer).getControls() & RowRendererExt.DETACH_ON_UNLOAD) == 0) { //re-use (default)
0620: final List cells = row.getChildren();
0621: boolean bNewCell = cells.isEmpty();
0622: if (!bNewCell) {
0623: //detach and remove all but the first cell
0624: for (Iterator it = cells.listIterator(1); it.hasNext();) {
0625: it.next();
0626: it.remove();
0627: }
0628:
0629: final Component cell = (Component) cells.get(0);
0630: bNewCell = !(cell instanceof Label);
0631: if (bNewCell) {
0632: cell.detach();
0633: } else {
0634: ((Label) cell).setValue("");
0635: }
0636: }
0637:
0638: if (bNewCell)
0639: newUnloadedCell(renderer, row);
0640: row.setLoaded(false);
0641: } else { //detach
0642: _rows.insertBefore(newUnloadedRow(renderer), row);
0643: row.detach();
0644: }
0645: }
0646:
0647: /** Handles a private event, onInitRender. It is used only for
0648: * implementation, and you rarely need to invoke it explicitly.
0649: */
0650: public void onInitRender() {
0651: final Renderer renderer = new Renderer();
0652: try {
0653: int pgsz, ofs;
0654: if (inPagingMold()) {
0655: pgsz = _pgi.getPageSize();
0656: ofs = _pgi.getActivePage() * pgsz;
0657: final int cnt = _rows.getChildren().size();
0658: if (ofs >= cnt) { //not possible; just in case
0659: ofs = cnt - pgsz;
0660: if (ofs < 0)
0661: ofs = 0;
0662: }
0663: } else {
0664: pgsz = 20;
0665: ofs = 0;
0666: //we don't know # of visible rows, so a 'smart' guess
0667: //It is OK since client will send back request if not enough
0668: }
0669:
0670: int j = 0;
0671: for (Iterator it = _rows.getChildren().listIterator(ofs); j < pgsz
0672: && it.hasNext(); ++j)
0673: renderer.render((Row) it.next());
0674:
0675: if (!inPagingMold()
0676: && getRows().getChildren().size() > pgsz)
0677: ((Row) getRows().getChildren().get(pgsz)).setAttribute(
0678: Attributes.SKIP_SIBLING, Boolean.TRUE);
0679: } catch (Throwable ex) {
0680: renderer.doCatch(ex);
0681: } finally {
0682: renderer.doFinally();
0683: }
0684: }
0685:
0686: private void postOnInitRender() {
0687: Events.postEvent("onInitRender", this , null);
0688: smartUpdate("z.render", true);
0689: }
0690:
0691: /** Handles when the list model's content changed.
0692: */
0693: private void onListDataChange(ListDataEvent event) {
0694: //when this is called _model is never null
0695: final int newsz = _model.getSize(), oldsz = _rows.getChildren()
0696: .size();
0697: int min = event.getIndex0(), max = event.getIndex1();
0698: if (min < 0)
0699: min = 0;
0700:
0701: boolean done = false;
0702: switch (event.getType()) {
0703: case ListDataEvent.INTERVAL_ADDED:
0704: if (max < 0)
0705: max = newsz - 1;
0706: if ((max - min + 1) != (newsz - oldsz)) {
0707: log
0708: .warning("Conflict event: number of added rows not matched: "
0709: + event);
0710: break; //handle it as CONTENTS_CHANGED
0711: }
0712:
0713: RowRenderer renderer = null;
0714: final Row before = min < oldsz ? (Row) _rows.getChildren()
0715: .get(min) : null;
0716: for (int j = min; j <= max; ++j) {
0717: if (renderer == null)
0718: renderer = getRealRenderer();
0719: _rows.insertBefore(newUnloadedRow(renderer), before);
0720: }
0721:
0722: done = true;
0723: break;
0724:
0725: case ListDataEvent.INTERVAL_REMOVED:
0726: if (max < 0)
0727: max = oldsz - 1;
0728: int cnt = max - min + 1;
0729: if (cnt != (oldsz - newsz)) {
0730: log
0731: .warning("Conflict event: number of removed rows not matched: "
0732: + event);
0733: break; //handle it as CONTENTS_CHANGED
0734: }
0735:
0736: //detach and remove
0737: for (Iterator it = _rows.getChildren().listIterator(min); --cnt >= 0
0738: && it.hasNext();) {
0739: it.next();
0740: it.remove();
0741: }
0742:
0743: done = true;
0744: break;
0745: }
0746:
0747: if (!done) //CONTENTS_CHANGED
0748: syncModel(min, max);
0749:
0750: postOnInitRender(); //to improve performance
0751: }
0752:
0753: private static final RowRenderer getDefaultRowRenderer() {
0754: return _defRend;
0755: }
0756:
0757: private static final RowRenderer _defRend = new RowRenderer() {
0758: public void render(Row row, Object data) {
0759: final Label label = newRenderLabel(Objects.toString(data));
0760: label.applyProperties();
0761: label.setParent(row);
0762: row.setValue(data);
0763: }
0764: };
0765:
0766: /** Returns the renderer used to render rows.
0767: */
0768: private RowRenderer getRealRenderer() {
0769: return _renderer != null ? _renderer : getDefaultRowRenderer();
0770: }
0771:
0772: /** Used to render row if _model is specified. */
0773: private class Renderer implements java.io.Serializable {
0774: private final RowRenderer _renderer;
0775: private boolean _rendered, _ctrled;
0776:
0777: private Renderer() {
0778: _renderer = getRealRenderer();
0779: }
0780:
0781: private void render(Row row) throws Throwable {
0782: if (row.isLoaded())
0783: return; //nothing to do
0784:
0785: if (!_rendered && (_renderer instanceof RendererCtrl)) {
0786: ((RendererCtrl) _renderer).doTry();
0787: _ctrled = true;
0788: }
0789:
0790: final Component cell = row.getFirstChild();
0791: if (!(_renderer instanceof RowRendererExt)
0792: || (((RowRendererExt) _renderer).getControls() & RowRendererExt.DETACH_ON_RENDER) != 0) { //detach (default)
0793: cell.detach();
0794: }
0795:
0796: try {
0797: _renderer.render(row, _model.getElementAt(row
0798: .getIndex()));
0799: } catch (Throwable ex) {
0800: try {
0801: final Label label = newRenderLabel(Exceptions
0802: .getMessage(ex));
0803: label.applyProperties();
0804: label.setParent(row);
0805: } catch (Throwable t) {
0806: log.error(t);
0807: }
0808: row.setLoaded(true);
0809: throw ex;
0810: } finally {
0811: if (row.getChildren().isEmpty())
0812: cell.setParent(row);
0813: }
0814:
0815: row.setLoaded(true);
0816: _rendered = true;
0817: }
0818:
0819: private void doCatch(Throwable ex) {
0820: if (_ctrled) {
0821: try {
0822: ((RendererCtrl) _renderer).doCatch(ex);
0823: } catch (Throwable t) {
0824: throw UiException.Aide.wrap(t);
0825: }
0826: } else {
0827: throw UiException.Aide.wrap(ex);
0828: }
0829: }
0830:
0831: private void doFinally() {
0832: if (_ctrled)
0833: ((RendererCtrl) _renderer).doFinally();
0834: }
0835: }
0836:
0837: /** Renders the specified {@link Row} if not loaded yet,
0838: * with {@link #getRowRenderer}.
0839: *
0840: * <p>It does nothing if {@link #getModel} returns null.
0841: * In other words, it is meaningful only if live data model is used.
0842: */
0843: public void renderRow(Row row) {
0844: if (_model == null)
0845: return;
0846:
0847: final Renderer renderer = new Renderer();
0848: try {
0849: renderer.render(row);
0850: } catch (Throwable ex) {
0851: renderer.doCatch(ex);
0852: } finally {
0853: renderer.doFinally();
0854: }
0855: }
0856:
0857: /** Renders all {@link Row} if not loaded yet,
0858: * with {@link #getRowRenderer}.
0859: */
0860: public void renderAll() {
0861: if (_model == null)
0862: return;
0863:
0864: final Renderer renderer = new Renderer();
0865: try {
0866: for (Iterator it = _rows.getChildren().iterator(); it
0867: .hasNext();)
0868: renderer.render((Row) it.next());
0869: } catch (Throwable ex) {
0870: renderer.doCatch(ex);
0871: } finally {
0872: renderer.doFinally();
0873: }
0874: }
0875:
0876: /** Renders a set of specified rows.
0877: * It is the same as {@link #renderItems}.
0878: */
0879: public void renderRows(Set rows) {
0880: renderItems(rows);
0881: }
0882:
0883: public void renderItems(Set rows) {
0884: if (_model == null) { //just in case that app dev might change it
0885: if (log.debugable())
0886: log.debug("No model no render");
0887: return;
0888: }
0889:
0890: if (rows.isEmpty())
0891: return; //nothing to do
0892:
0893: final Renderer renderer = new Renderer();
0894: try {
0895: for (Iterator it = rows.iterator(); it.hasNext();)
0896: renderer.render((Row) it.next());
0897: } catch (Throwable ex) {
0898: renderer.doCatch(ex);
0899: } finally {
0900: renderer.doFinally();
0901: }
0902: }
0903:
0904: /** Returns the style class for the odd rows.
0905: *
0906: * <p>Default: odd.
0907: *
0908: * @since 3.0.0
0909: */
0910: public String getOddRowSclass() {
0911: return _scOddRow;
0912: }
0913:
0914: /** Sets the style class for the odd rows.
0915: * If the style class doesn't exist, the striping effect disappears.
0916: * You can provide different effects by providing the proper style
0917: * classes.
0918: * @since 3.0.0
0919: */
0920: public void setOddRowSclass(String scls) {
0921: if (scls != null && scls.length() == 0)
0922: scls = null;
0923: if (!Objects.equals(_scOddRow, scls)) {
0924: _scOddRow = scls;
0925: smartUpdate("z.scOddRow", scls);
0926: }
0927: }
0928:
0929: //-- super --//
0930: public void setMold(String mold) {
0931: final String old = getMold();
0932: if (!Objects.equals(old, mold)) {
0933: super .setMold(mold);
0934:
0935: if ("paging".equals(old)) { //change from paging
0936: if (_paging != null) {
0937: removePagingListener(_paging);
0938: _paging.detach();
0939: } else if (_pgi != null) {
0940: removePagingListener(_pgi);
0941: }
0942: } else if (inPagingMold()) { //change to paging
0943: if (_pgi != null)
0944: addPagingListener(_pgi);
0945: else
0946: newInternalPaging();
0947: }
0948: }
0949: }
0950:
0951: public String getOuterAttrs() {
0952: final StringBuffer sb = new StringBuffer(80).append(super
0953: .getOuterAttrs());
0954: if (_align != null)
0955: HTMLs.appendAttribute(sb, "align", _align);
0956: if (_model != null) {
0957: HTMLs.appendAttribute(sb, "z.model", true);
0958: final List rows = getRows().getChildren();
0959: int index = rows.size();
0960: for (final ListIterator it = rows.listIterator(index); it
0961: .hasPrevious(); --index)
0962: if (((Row) it.previous()).isLoaded())
0963: break;
0964: HTMLs.appendAttribute(sb, "z.lastLoadIdx", index);
0965: }
0966: if (_scOddRow != null)
0967: HTMLs.appendAttribute(sb, "z.scOddRow", _scOddRow);
0968: return sb.toString();
0969: }
0970:
0971: //-- Component --//
0972: public boolean insertBefore(Component newChild, Component refChild) {
0973: if (newChild instanceof Rows) {
0974: if (_rows != null && _rows != newChild)
0975: throw new UiException(
0976: "Only one rows child is allowed: "
0977: + this
0978: + "\nNote: rows is created automatically if live data");
0979: _rows = (Rows) newChild;
0980: } else if (newChild instanceof Columns) {
0981: if (_cols != null && _cols != newChild)
0982: throw new UiException(
0983: "Only one columns child is allowed: " + this );
0984: _cols = (Columns) newChild;
0985: } else if (newChild instanceof Foot) {
0986: if (_foot != null && _foot != newChild)
0987: throw new UiException(
0988: "Only one foot child is allowed: " + this );
0989: _foot = (Foot) newChild;
0990: } else if (newChild instanceof Paging) {
0991: if (_pgi != null)
0992: throw new UiException(
0993: "External paging cannot coexist with child paging");
0994: if (_paging != null && _paging != newChild)
0995: throw new UiException("Only one paging is allowed: "
0996: + this );
0997: if (!inPagingMold())
0998: throw new UiException(
0999: "The child paging is allowed only in the paging mold");
1000: _pgi = _paging = (Paging) newChild;
1001: } else if (!(newChild instanceof Auxhead)) {
1002: throw new UiException("Unsupported child for grid: "
1003: + newChild);
1004: }
1005:
1006: if (super .insertBefore(newChild, refChild)) {
1007: //not need to invalidate since auxhead visible only with _cols
1008: if (!(newChild instanceof Auxhead))
1009: invalidate();
1010: return true;
1011: }
1012: return false;
1013: }
1014:
1015: public boolean removeChild(Component child) {
1016: if (!super .removeChild(child))
1017: return false;
1018:
1019: if (_rows == child)
1020: _rows = null;
1021: else if (_cols == child)
1022: _cols = null;
1023: else if (_foot == child)
1024: _foot = null;
1025: else if (_paging == child) {
1026: _paging = null;
1027: if (_pgi == child)
1028: _pgi = null;
1029: }
1030: invalidate();
1031: return true;
1032: }
1033:
1034: //Cloneable//
1035: public Object clone() {
1036: final Grid clone = (Grid) super .clone();
1037: clone.init();
1038:
1039: int cnt = 0;
1040: if (clone._rows != null)
1041: ++cnt;
1042: if (clone._cols != null)
1043: ++cnt;
1044: if (clone._foot != null)
1045: ++cnt;
1046: if (clone._paging != null)
1047: ++cnt;
1048: if (cnt > 0)
1049: clone.afterUnmarshal(cnt);
1050:
1051: return clone;
1052: }
1053:
1054: /** @param cnt # of children that need special handling (used for optimization).
1055: * -1 means process all of them
1056: */
1057: private void afterUnmarshal(int cnt) {
1058: for (Iterator it = getChildren().iterator(); it.hasNext();) {
1059: final Object child = it.next();
1060: if (child instanceof Rows) {
1061: _rows = (Rows) child;
1062: if (--cnt == 0)
1063: break;
1064: } else if (child instanceof Columns) {
1065: _cols = (Columns) child;
1066: if (--cnt == 0)
1067: break;
1068: } else if (child instanceof Foot) {
1069: _foot = (Foot) child;
1070: if (--cnt == 0)
1071: break;
1072: } else if (child instanceof Paging) {
1073: _pgi = _paging = (Paging) child;
1074: if (--cnt == 0)
1075: break;
1076: }
1077: }
1078: }
1079:
1080: //Serializable//
1081: private synchronized void readObject(java.io.ObjectInputStream s)
1082: throws java.io.IOException, ClassNotFoundException {
1083: s.defaultReadObject();
1084: init();
1085: afterUnmarshal(-1);
1086: //TODO: how to marshal _pgi if _pgi != _paging
1087: //TODO: re-register event listener for onPaging
1088:
1089: if (_model != null)
1090: initDataListener();
1091: }
1092:
1093: //-- ComponentCtrl --//
1094: protected Object newExtraCtrl() {
1095: return new ExtraCtrl();
1096: }
1097:
1098: /** A utility class to implement {@link #getExtraCtrl}.
1099: * It is used only by component developers.
1100: */
1101: protected class ExtraCtrl extends XulElement.ExtraCtrl implements
1102: InnerWidth, RenderOnDemand {
1103: //InnerWidth//
1104: public void setInnerWidthByClient(String width) {
1105: _innerWidth = width == null ? "100%" : width;
1106: }
1107:
1108: //RenderOnDemand//
1109: public void renderItems(Set items) {
1110: int cnt = items.size();
1111: if (cnt == 0)
1112: return; //nothing to do
1113: cnt = 20 - cnt;
1114: if (cnt > 0 && _preloadsz > 0) { //Feature 1740072: pre-load
1115: if (cnt > _preloadsz)
1116: cnt = _preloadsz;
1117:
1118: //1. locate the first item found in items
1119: final List toload = new LinkedList();
1120: Iterator it = getRows().getChildren().iterator();
1121: while (it.hasNext()) {
1122: final Row row = (Row) it.next();
1123: if (items.contains(row)) //found
1124: break;
1125: if (!row.isLoaded())
1126: toload.add(0, row); //reverse order
1127: }
1128:
1129: //2. add unload items before the found one
1130: if (!toload.isEmpty()) {
1131: int bfcnt = cnt / 3;
1132: for (Iterator e = toload.iterator(); bfcnt > 0
1133: && e.hasNext(); --bfcnt, --cnt) {
1134: items.add(e.next());
1135: }
1136: }
1137:
1138: //3. add unloaded after the found one
1139: while (cnt > 0 && it.hasNext()) {
1140: final Row row = (Row) it.next();
1141: if (!row.isLoaded() && items.add(row))
1142: --cnt;
1143: }
1144: }
1145:
1146: Grid.this .renderItems(items);
1147: }
1148: }
1149:
1150: /** An iterator used by _heads.
1151: */
1152: private class Iter implements Iterator {
1153: private final ListIterator _it = getChildren().listIterator();
1154:
1155: public boolean hasNext() {
1156: while (_it.hasNext()) {
1157: Object o = _it.next();
1158: if (o instanceof Columns || o instanceof Auxhead) {
1159: _it.previous();
1160: return true;
1161: }
1162: }
1163: return false;
1164: }
1165:
1166: public Object next() {
1167: for (;;) {
1168: Object o = _it.next();
1169: if (o instanceof Columns || o instanceof Auxhead)
1170: return o;
1171: }
1172: }
1173:
1174: public void remove() {
1175: throw new UnsupportedOperationException();
1176: }
1177: }
1178: }
|