0001: /* Listbox.java
0002:
0003: {{IS_NOTE
0004: Purpose:
0005:
0006: Description:
0007:
0008: History:
0009: Wed Jun 15 17:25:00 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.List;
0022: import java.util.AbstractSequentialList;
0023: import java.util.LinkedList;
0024: import java.util.ArrayList;
0025: import java.util.Set;
0026: import java.util.HashSet;
0027: import java.util.LinkedHashSet;
0028: import java.util.Iterator;
0029: import java.util.ListIterator;
0030: import java.util.Collection;
0031: import java.util.Collections;
0032: import java.util.AbstractCollection;
0033: import java.util.NoSuchElementException;
0034:
0035: import org.zkoss.lang.D;
0036: import org.zkoss.lang.Classes;
0037: import org.zkoss.lang.Objects;
0038: import org.zkoss.lang.Strings;
0039: import org.zkoss.lang.Exceptions;
0040: import org.zkoss.util.logging.Log;
0041: import org.zkoss.xml.HTMLs;
0042:
0043: import org.zkoss.zk.ui.Page;
0044: import org.zkoss.zk.ui.Component;
0045: import org.zkoss.zk.ui.UiException;
0046: import org.zkoss.zk.ui.WrongValueException;
0047: import org.zkoss.zk.ui.ext.client.RenderOnDemand;
0048: import org.zkoss.zk.ui.ext.client.Selectable;
0049: import org.zkoss.zk.ui.ext.client.InnerWidth;
0050: import org.zkoss.zk.ui.ext.render.Cropper;
0051: import org.zkoss.zk.ui.event.Event;
0052: import org.zkoss.zk.ui.event.EventListener;
0053: import org.zkoss.zk.ui.event.Events;
0054:
0055: import org.zkoss.zul.impl.XulElement;
0056: import org.zkoss.zul.event.ListDataEvent;
0057: import org.zkoss.zul.event.ListDataListener;
0058: import org.zkoss.zul.ext.Paginal;
0059: import org.zkoss.zul.event.ZulEvents;
0060: import org.zkoss.zul.event.PagingEvent;
0061:
0062: /**
0063: * A listbox.
0064: *
0065: * <p>Event:
0066: * <ol>
0067: * <li>org.zkoss.zk.ui.event.SelectEvent is sent when user changes
0068: * the selection.</li>
0069: * </ol>
0070: *
0071: * <p>See <a href="package-summary.html">Specification</a>.</p>
0072: *
0073: * <p>Besides creating {@link Listitem} programmingly, you could assign
0074: * a data model (a {@link ListModel} instance) to a listbox
0075: * via {@link #setModel} and then
0076: * the listbox will retrieve data via {@link ListModel#getElementAt} when
0077: * necessary.
0078: *
0079: * <p>Besides assign a list model, you could assign a renderer
0080: * (a {@link ListitemRenderer} instance) to a listbox, such that
0081: * the listbox will use this
0082: * renderer to render the data returned by {@link ListModel#getElementAt}.
0083: * If not assigned, the default renderer, which assumes a label per
0084: * list item, is used.
0085: * In other words, the default renderer adds a label to
0086: * a row by calling toString against the object returned
0087: * by {@link ListModel#getElementAt}
0088: *
0089: * <p>There are two ways to handle long content: scrolling and paging.
0090: * If {@link #getMold} is "default", scrolling is used if {@link #setHeight}
0091: * is called and too much content to display.
0092: * If {@link #getMold} is "paging", paging is used if two or more pages are
0093: * required. To control the number of items to display in a page, use
0094: * {@link #setPageSize}.
0095: *
0096: * <p>If paging is used, the page controller is either created automatically
0097: * or assigned explicity by {@link #setPaginal}.
0098: * The paging controller specified explicitly by {@link #setPaginal} is called
0099: * the external page controller. It is useful if you want to put the paging
0100: * controller at different location (other than as a child component), or
0101: * you want to use the same controller to control multiple listboxes.
0102: *
0103: * <p>Default {@link #getSclass}: listbox.
0104: *
0105: * <p>To have a list box without stripping, you can specify a non-existent
0106: * style class to {@link #setOddRowSclass}.
0107: * If you want to disable all striping, you can specify the style:
0108: * <pre><code>
0109: tr.odd {
0110: background: white;
0111: }
0112: * </code></pre>
0113: *
0114: * @author tomyeh
0115: * @see ListModel
0116: * @see ListitemRenderer
0117: * @see ListitemRendererExt
0118: */
0119: public class Listbox extends XulElement {
0120: private static final Log log = Log.lookup(Listbox.class);
0121:
0122: private transient List _items;
0123: /** A list of selected items. */
0124: private transient Set _selItems;
0125: /** A readonly copy of {@link #_selItems}. */
0126: private transient Set _roSelItems;
0127: private int _maxlength;
0128: private int _rows, _jsel = -1;
0129: private transient Listhead _listhead;
0130: private transient Listfoot _listfoot;
0131: private ListModel _model;
0132: private ListitemRenderer _renderer;
0133: private transient ListDataListener _dataListener;
0134: private transient Collection _heads;
0135: private int _hdcnt;
0136: private String _innerWidth = "100%";
0137: /** The name. */
0138: private String _name;
0139: /** The paging controller, used only if mold = "paging". */
0140: private transient Paginal _pgi;
0141: /** The paging controller, used only if mold = "paging" and user
0142: * doesn't assign a controller via {@link #setPaginal}.
0143: * If exists, it is the last child
0144: */
0145: private transient Paging _paging;
0146: private transient EventListener _pgListener, _pgImpListener;
0147: /** The style class of the odd row. */
0148: private String _scOddRow = "odd";
0149: private int _tabindex = -1;
0150: /** the # of rows to preload. */
0151: private int _preloadsz = 7;
0152: private boolean _multiple;
0153: private boolean _disabled, _checkmark;
0154: private boolean _vflex;
0155: /** disable smartUpdate; usually caused by the client. */
0156: private boolean _noSmartUpdate;
0157:
0158: public Listbox() {
0159: setSclass("listbox");
0160: init();
0161: }
0162:
0163: private void init() {
0164: _items = new AbstractSequentialList() {
0165: public ListIterator listIterator(int index) {
0166: return new ItemIter(index);
0167: }
0168:
0169: public Object get(int j) {
0170: final Object o = Listbox.this .getChildren().get(
0171: j + _hdcnt);
0172: if (!(o instanceof Listitem))
0173: throw new IndexOutOfBoundsException("Wrong index: "
0174: + j);
0175: return o;
0176: }
0177:
0178: public int size() {
0179: int sz = getChildren().size() - _hdcnt;
0180: if (_listfoot != null)
0181: --sz;
0182: if (_paging != null)
0183: --sz;
0184: return sz;
0185: }
0186: };
0187: _selItems = new LinkedHashSet(5);
0188: _roSelItems = Collections.unmodifiableSet(_selItems);
0189:
0190: _heads = new AbstractCollection() {
0191: public int size() {
0192: return _hdcnt;
0193: }
0194:
0195: public Iterator iterator() {
0196: return new Iter();
0197: }
0198: };
0199: }
0200:
0201: /** Initializes _dataListener and register the listener to the model
0202: */
0203: private void initDataListener() {
0204: if (_dataListener == null)
0205: _dataListener = new ListDataListener() {
0206: public void onChange(ListDataEvent event) {
0207: onListDataChange(event);
0208: }
0209: };
0210:
0211: _model.addListDataListener(_dataListener);
0212: }
0213:
0214: /** Returns {@link Listhead} belonging to this listbox, or null
0215: * if no list headers at all.
0216: */
0217: public Listhead getListhead() {
0218: return _listhead;
0219: }
0220:
0221: /** Returns {@link Listfoot} belonging to this listbox, or null
0222: * if no list footers at all.
0223: */
0224: public Listfoot getListfoot() {
0225: return _listfoot;
0226: }
0227:
0228: /** Returns a collection of heads, including {@link #getListhead}
0229: * and auxiliary heads ({@link Auxhead}) (never null).
0230: *
0231: * @since 3.0.0
0232: */
0233: public Collection getHeads() {
0234: return _heads;
0235: }
0236:
0237: /** Returns whether the HTML's select tag is used.
0238: */
0239: /*package*/final boolean inSelectMold() {
0240: return "select".equals(getMold());
0241: }
0242:
0243: /** Returns whether the check mark shall be displayed in front
0244: * of each item.
0245: * <p>Default: false.
0246: */
0247: public final boolean isCheckmark() {
0248: return _checkmark;
0249: }
0250:
0251: /** Sets whether the check mark shall be displayed in front
0252: * of each item.
0253: * <p>The check mark is a checkbox if {@link #isMultiple} returns
0254: * true. It is a radio button if {@link #isMultiple} returns false.
0255: */
0256: public void setCheckmark(boolean checkmark) {
0257: if (_checkmark != checkmark) {
0258: _checkmark = checkmark;
0259: if (!inSelectMold())
0260: invalidate();
0261: }
0262: }
0263:
0264: /**
0265: * Sets the inner width of this component.
0266: * The inner width is the width of the inner table.
0267: * By default, it is 100%. That is, it is the same as the width
0268: * of this component. However, it is changed when the user
0269: * is sizing the column's width.
0270: *
0271: * <p>Application developers rarely call this method, unless
0272: * they want to preserve the widths of sizable columns
0273: * changed by the user.
0274: * To preserve the widths, the developer have to store the widths of
0275: * all columns and the inner width ({@link #getInnerWidth}),
0276: * and then restore them when re-creating this component.
0277: *
0278: * @param innerWidth the inner width. If null, "100%" is assumed.
0279: * @since 3.0.0
0280: */
0281: public void setInnerWidth(String innerWidth) {
0282: if (innerWidth == null)
0283: innerWidth = "100%";
0284: if (!_innerWidth.equals(innerWidth)) {
0285: _innerWidth = innerWidth;
0286: smartUpdate("z.innerWidth", innerWidth);
0287: }
0288: }
0289:
0290: /**
0291: * Returns the inner width of this component.
0292: * The inner width is the width of the inner table.
0293: * <p>Default: "100%"
0294: * @see #setInnerWidth
0295: * @since 3.0.0
0296: */
0297: public String getInnerWidth() {
0298: return _innerWidth;
0299: }
0300:
0301: /** Returns whether to grow and shrink vertical to fit their given space,
0302: * so called vertial flexibility.
0303: *
0304: * <p>Note: this attribute is ignored if {@link #setRows} is specified
0305: *
0306: * <p>Default: false.
0307: */
0308: public final boolean isVflex() {
0309: return _vflex;
0310: }
0311:
0312: /** Sets whether to grow and shrink vertical to fit their given space,
0313: * so called vertial flexibility.
0314: *
0315: * <p>Note: this attribute is ignored if {@link #setRows} is specified
0316: */
0317: public void setVflex(boolean vflex) {
0318: if (_vflex != vflex) {
0319: _vflex = vflex;
0320: if (!inSelectMold())
0321: smartUpdate("z.flex", _vflex);
0322: }
0323: }
0324:
0325: /** Returns whether it is disabled.
0326: * <p>Default: false.
0327: */
0328: public final boolean isDisabled() {
0329: return _disabled;
0330: }
0331:
0332: /** Sets whether it is disabled.
0333: */
0334: public void setDisabled(boolean disabled) {
0335: if (_disabled != disabled) {
0336: _disabled = disabled;
0337: if (inSelectMold()) {
0338: smartUpdate("disabled", _disabled);
0339: } else {
0340: smartUpdate("z.disabled", _disabled);
0341: }
0342: }
0343: }
0344:
0345: /** Returns the tab order of this component.
0346: * <p>Currently, only the "select" mold supports this property.
0347: * <p>Default: -1 (means the same as browser's default).
0348: */
0349: public int getTabindex() {
0350: return _tabindex;
0351: }
0352:
0353: /** Sets the tab order of this component.
0354: * <p>Currently, only the "select" mold supports this property.
0355: */
0356: public void setTabindex(int tabindex) throws WrongValueException {
0357: if (_tabindex != tabindex) {
0358: _tabindex = tabindex;
0359: if (tabindex < 0)
0360: smartUpdate("tabindex", null);
0361: else
0362: smartUpdate("tabindex", Integer.toString(_tabindex));
0363: }
0364: }
0365:
0366: /** Returns the rows. Zero means no limitation.
0367: * <p>Default: 0.
0368: */
0369: public int getRows() {
0370: return _rows;
0371: }
0372:
0373: /** Sets the rows.
0374: * <p>Note: if both {@link #setHeight} is specified with non-empty,
0375: * {@link #setRows} is ignored
0376: */
0377: public void setRows(int rows) throws WrongValueException {
0378: if (rows < 0)
0379: throw new WrongValueException("Illegal rows: " + rows);
0380:
0381: if (_rows != rows) {
0382: _rows = rows;
0383:
0384: if (inSelectMold()) {
0385: smartUpdate("size", _rows > 0 ? Integer.toString(_rows)
0386: : null);
0387: } else {
0388: smartUpdate("z.size", Integer.toString(_rows));
0389: }
0390: }
0391: }
0392:
0393: /** Returns the seltype.
0394: * <p>Default: "single".
0395: */
0396: public String getSeltype() {
0397: return _multiple ? "multiple" : "single";
0398: }
0399:
0400: /** Sets the seltype.
0401: */
0402: public void setSeltype(String seltype) throws WrongValueException {
0403: if ("single".equals(seltype))
0404: setMultiple(false);
0405: else if ("multiple".equals(seltype))
0406: setMultiple(true);
0407: else
0408: throw new WrongValueException("Unknown seltype: " + seltype);
0409: }
0410:
0411: /** Returns whether multiple selections are allowed.
0412: * <p>Default: false.
0413: */
0414: public boolean isMultiple() {
0415: return _multiple;
0416: }
0417:
0418: /** Sets whether multiple selections are allowed.
0419: */
0420: public void setMultiple(boolean multiple) {
0421: if (_multiple != multiple) {
0422: _multiple = multiple;
0423: if (!_multiple && _selItems.size() > 1) {
0424: final Listitem item = getSelectedItem();
0425: for (Iterator it = _selItems.iterator(); it.hasNext();) {
0426: final Listitem li = (Listitem) it.next();
0427: if (li != item) {
0428: li.setSelectedDirectly(false);
0429: it.remove();
0430: }
0431: }
0432: //No need to update z.selId because z.multiple will do the job
0433: }
0434:
0435: if (inSelectMold())
0436: smartUpdate("multiple", _multiple);
0437: else if (isCheckmark())
0438: invalidate(); //change check mark
0439: else
0440: smartUpdate("z.multiple", _multiple);
0441: //No need to use response because such info is carried on tags
0442: }
0443: }
0444:
0445: /** Returns the ID of the selected item (it is stored as the z.selId
0446: * attribute of the listbox).
0447: */
0448: private String getSelectedId() {
0449: final Listitem sel = getSelectedItem();
0450: return sel != null ? sel.getUuid() : "zk_n_a";
0451: }
0452:
0453: /** Returns the maximal length of each item's label.
0454: */
0455: public int getMaxlength() {
0456: return _maxlength;
0457: }
0458:
0459: /** Sets the maximal length of each item's label.
0460: */
0461: public void setMaxlength(int maxlength) {
0462: if (maxlength < 0)
0463: maxlength = 0;
0464: if (_maxlength != maxlength) {
0465: _maxlength = maxlength;
0466: if (inSelectMold()) //affects only the HTML-select listbox
0467: invalidate();
0468: //Both IE and Mozilla are buggy if we insert options by innerHTML
0469: }
0470: }
0471:
0472: /** Returns the name of this component.
0473: * <p>Default: null.
0474: * <p>The name is used only to work with "legacy" Web application that
0475: * handles user's request by servlets.
0476: * It works only with HTTP/HTML-based browsers. It doesn't work
0477: * with other kind of clients.
0478: * <p>Don't use this method if your application is purely based
0479: * on ZK's event-driven model.
0480: */
0481: public String getName() {
0482: return _name;
0483: }
0484:
0485: /** Sets the name of this component.
0486: * <p>The name is used only to work with "legacy" Web application that
0487: * handles user's request by servlets.
0488: * It works only with HTTP/HTML-based browsers. It doesn't work
0489: * with other kind of clients.
0490: * <p>Don't use this method if your application is purely based
0491: * on ZK's event-driven model.
0492: *
0493: * @param name the name of this component.
0494: */
0495: public void setName(String name) {
0496: if (name != null && name.length() == 0)
0497: name = null;
0498: if (!Objects.equals(_name, name)) {
0499: _name = name;
0500: if (inSelectMold())
0501: smartUpdate("name", _name);
0502: else if (_name != null)
0503: smartUpdate("z.name", _name);
0504: else
0505: invalidate(); //1) generate _value; 2) add submit listener
0506: }
0507: }
0508:
0509: /** Returns a live list of all {@link Listitem}.
0510: * By live we mean you can add or remove them directly with
0511: * the List interface. In other words, you could add or remove
0512: * an item by manipulating the returned list directly.
0513: */
0514: public List getItems() {
0515: return _items;
0516: }
0517:
0518: /** Returns the number of items.
0519: */
0520: public int getItemCount() {
0521: return _items.size();
0522: }
0523:
0524: /** Returns the item at the specified index.
0525: *
0526: * <p>Note: if live data is used ({@link #getModel} is not null),
0527: * the returned item might NOT be loaded yet.
0528: * To ensure it is loaded, you have to invoke {@link #renderItem}.
0529: */
0530: public Listitem getItemAtIndex(int index) {
0531: return (Listitem) _items.get(index);
0532: }
0533:
0534: /** Returns the index of the specified item, or -1 if not found.
0535: */
0536: public int getIndexOfItem(Listitem item) {
0537: return item == null ? -1 : item.getIndex();
0538: }
0539:
0540: /** Returns the index of the selected item (-1 if no one is selected).
0541: */
0542: public int getSelectedIndex() {
0543: return _jsel;
0544: }
0545:
0546: /** Deselects all of the currently selected items and selects
0547: * the item with the given index.
0548: */
0549: public void setSelectedIndex(int jsel) {
0550: if (jsel >= _items.size())
0551: throw new UiException("Out of bound: " + jsel
0552: + " while size=" + _items.size());
0553:
0554: if (jsel < -1)
0555: jsel = -1;
0556: if (jsel < 0) { //unselct all
0557: clearSelection();
0558: } else if (jsel != _jsel || (_multiple && _selItems.size() > 1)) {
0559: for (Iterator it = _selItems.iterator(); it.hasNext();) {
0560: final Listitem item = (Listitem) it.next();
0561: item.setSelectedDirectly(false);
0562: }
0563: _selItems.clear();
0564:
0565: _jsel = jsel;
0566: final Listitem item = getItemAtIndex(_jsel);
0567: item.setSelectedDirectly(true);
0568: _selItems.add(item);
0569: if (inSelectMold())
0570: smartUpdate("selectedIndex", Integer.toString(_jsel));
0571: else
0572: smartUpdate("select", item.getUuid());
0573: //Bug 1734950: don't count on index (since it may change)
0574: //On the other hand, it is OK with select-mold since
0575: //it invalidates if items are added or removed
0576: }
0577: }
0578:
0579: /** Deselects all of the currently selected items and selects
0580: * the given item.
0581: * <p>It is the same as {@link #setSelectedItem}.
0582: * @param item the item to select. If null, all items are deselected.
0583: */
0584: public void selectItem(Listitem item) {
0585: if (item == null) {
0586: setSelectedIndex(-1);
0587: } else {
0588: if (item.getParent() != this )
0589: throw new UiException("Not a child: " + item);
0590: if (_multiple || !item.isSelected())
0591: setSelectedIndex(item.getIndex());
0592: }
0593: }
0594:
0595: /** Selects the given item, without deselecting any other items
0596: * that are already selected..
0597: */
0598: public void addItemToSelection(Listitem item) {
0599: if (item.getParent() != this )
0600: throw new UiException("Not a child: " + item);
0601:
0602: if (!item.isSelected()) {
0603: if (!_multiple) {
0604: selectItem(item);
0605: } else {
0606: if (item.getIndex() < _jsel || _jsel < 0) {
0607: _jsel = item.getIndex();
0608: if (!inSelectMold())
0609: smartUpdate("z.selId", getSelectedId());
0610: }
0611: item.setSelectedDirectly(true);
0612: _selItems.add(item);
0613: if (inSelectMold()) {
0614: item.smartUpdate("selected", true);
0615: } else {
0616: smartUpdateSelection();
0617: }
0618: }
0619: }
0620: }
0621:
0622: /** Deselects the given item without deselecting other items.
0623: */
0624: public void removeItemFromSelection(Listitem item) {
0625: if (item.getParent() != this )
0626: throw new UiException("Not a child: " + item);
0627:
0628: if (item.isSelected()) {
0629: if (!_multiple) {
0630: clearSelection();
0631: } else {
0632: final int oldSel = _jsel;
0633: item.setSelectedDirectly(false);
0634: _selItems.remove(item);
0635: fixSelectedIndex(0);
0636: if (inSelectMold()) {
0637: item.smartUpdate("selected", false);
0638: } else {
0639: smartUpdateSelection();
0640: if (oldSel != _jsel)
0641: smartUpdate("z.selId", getSelectedId());
0642: }
0643: }
0644: }
0645: }
0646:
0647: /** Note: we have to update all selection at once, since addItemToSelection
0648: * and removeItemFromSelection might be called interchangeably.
0649: */
0650: private void smartUpdateSelection() {
0651: final StringBuffer sb = new StringBuffer(80);
0652: for (Iterator it = _selItems.iterator(); it.hasNext();) {
0653: if (sb.length() > 0)
0654: sb.append(',');
0655: sb.append(((Listitem) it.next()).getUuid());
0656: }
0657: smartUpdate("chgSel", sb.toString());
0658: }
0659:
0660: /** If the specified item is selected, it is deselected.
0661: * If it is not selected, it is selected. Other items in the list box
0662: * that are selected are not affected, and retain their selected state.
0663: */
0664: public void toggleItemSelection(Listitem item) {
0665: if (item.isSelected())
0666: removeItemFromSelection(item);
0667: else
0668: addItemToSelection(item);
0669: }
0670:
0671: /** Clears the selection.
0672: */
0673: public void clearSelection() {
0674: if (!_selItems.isEmpty()) {
0675: for (Iterator it = _selItems.iterator(); it.hasNext();) {
0676: final Listitem item = (Listitem) it.next();
0677: item.setSelectedDirectly(false);
0678: }
0679: _selItems.clear();
0680: _jsel = -1;
0681: if (inSelectMold())
0682: smartUpdate("selectedIndex", "-1");
0683: else
0684: smartUpdate("select", "");
0685: //Bug 1734950: don't count on index (since it may change)
0686: }
0687: }
0688:
0689: /** Selects all items.
0690: */
0691: public void selectAll() {
0692: if (!_multiple)
0693: throw new UiException(
0694: "Appliable only to the multiple seltype: " + this );
0695:
0696: if (_items.size() != _selItems.size()) {
0697: for (Iterator it = _items.iterator(); it.hasNext();) {
0698: final Listitem item = (Listitem) it.next();
0699: _selItems.add(item);
0700: item.setSelectedDirectly(true);
0701: }
0702: _jsel = _items.isEmpty() ? -1 : 0;
0703: smartUpdate("selectAll", "true");
0704: }
0705: }
0706:
0707: /** Returns the selected item.
0708: *
0709: * <p>Note: if live data is used ({@link #getModel} is not null),
0710: * the returned item might NOT be loaded yet.
0711: * To ensure it is loaded, you have to invoke {@link #renderItem}.
0712: */
0713: public Listitem getSelectedItem() {
0714: return _jsel >= 0 ? _jsel > 0 && _selItems.size() == 1 ? //optimize for performance
0715: (Listitem) _selItems.iterator().next()
0716: : getItemAtIndex(_jsel) : null;
0717: }
0718:
0719: /** Deselects all of the currently selected items and selects
0720: * the given item.
0721: * <p>It is the same as {@link #selectItem}.
0722: */
0723: public void setSelectedItem(Listitem item) {
0724: selectItem(item);
0725: }
0726:
0727: /** Returns all selected items.
0728: *
0729: * <p>Note: if live data is used ({@link #getModel} is not null),
0730: * the returned item might NOT be loaded yet.
0731: * To ensure it is loaded, you have to invoke {@link #renderItem}.
0732: */
0733: public Set getSelectedItems() {
0734: return _roSelItems;
0735: }
0736:
0737: /** Returns the number of items being selected.
0738: */
0739: public int getSelectedCount() {
0740: return _selItems.size();
0741: }
0742:
0743: /** Appends an item.
0744: *
0745: * <p>Note: if live data is used ({@link #getModel} is not null),
0746: * the returned item might NOT be loaded yet.
0747: * To ensure it is loaded, you have to invoke {@link #renderItem}.
0748: */
0749: public Listitem appendItem(String label, String value) {
0750: final Listitem item = new Listitem(label, value);
0751: item.applyProperties();
0752: item.setParent(this );
0753: return item;
0754: }
0755:
0756: /** Removes the child item in the list box at the given index.
0757: *
0758: * <p>Note: if live data is used ({@link #getModel} is not null),
0759: * the returned item might NOT be loaded yet.
0760: * To ensure it is loaded, you have to invoke {@link #renderItem}.
0761: *
0762: * @return the removed item.
0763: */
0764: public Listitem removeItemAt(int index) {
0765: final Listitem item = getItemAtIndex(index);
0766: removeChild(item);
0767: return item;
0768: }
0769:
0770: //--Paging--//
0771: /** Returns the paging controller, or null if not available.
0772: * Note: the paging controller is used only if {@link #getMold} is "paging".
0773: *
0774: * <p>If mold is "paging", this method never returns null, because
0775: * a child paging controller is created automcatically (if not specified
0776: * by developers with {@link #setPaginal}).
0777: *
0778: * <p>If a paging controller is specified (either by {@link #setPaginal},
0779: * or by {@link #setMold} with "paging"),
0780: * the listbox will rely on the paging controller to handle long-content
0781: * instead of scrolling.
0782: */
0783: public Paginal getPaginal() {
0784: return _pgi;
0785: }
0786:
0787: /* Specifies the paging controller.
0788: * Note: the paging controller is used only if {@link #getMold} is "paging".
0789: *
0790: * <p>It is OK, though without any effect, to specify a paging controller
0791: * even if mold is not "paging".
0792: *
0793: * @param pgi the paging controller. If null and {@link #getMold} is "paging",
0794: * a paging controller is created automatically as a child component
0795: * (see {@link #getPaging}).
0796: */
0797: public void setPaginal(Paginal pgi) {
0798: if (!Objects.equals(pgi, _pgi)) {
0799: final Paginal old = _pgi;
0800: _pgi = pgi;
0801:
0802: if (inPagingMold()) {
0803: if (old != null)
0804: removePagingListener(old);
0805: if (_pgi == null) {
0806: if (_paging != null)
0807: _pgi = _paging;
0808: else
0809: newInternalPaging();
0810: } else { //_pgi != null
0811: if (_pgi != _paging) {
0812: if (_paging != null)
0813: _paging.detach();
0814: _pgi.setTotalSize(getItemCount());
0815: addPagingListener(_pgi);
0816: }
0817: }
0818: }
0819: }
0820: }
0821:
0822: /** Creates the internal paging component.
0823: */
0824: private void newInternalPaging() {
0825: assert D.OFF || inPagingMold() : "paging mold only";
0826: assert D.OFF || (_paging == null && _pgi == null);
0827:
0828: final Paging paging = new Paging();
0829: paging.setAutohide(true);
0830: paging.setDetailed(true);
0831: paging.setTotalSize(getItemCount());
0832: paging.setParent(this );
0833: addPagingListener(_pgi);
0834: }
0835:
0836: /** Adds the event listener for the onPaging event. */
0837: private void addPagingListener(Paginal pgi) {
0838: if (_pgListener == null)
0839: _pgListener = new EventListener() {
0840: public void onEvent(Event event) {
0841: final PagingEvent evt = (PagingEvent) event;
0842: Events.postEvent(new PagingEvent(evt.getName(),
0843: Listbox.this , evt.getPageable(), evt
0844: .getActivePage()));
0845: }
0846: };
0847: pgi.addEventListener(ZulEvents.ON_PAGING, _pgListener);
0848:
0849: if (_pgImpListener == null)
0850: _pgImpListener = new EventListener() {
0851: public void onEvent(Event event) {
0852: if (_model != null && inPagingMold()) {
0853: final Renderer renderer = new Renderer();
0854: try {
0855: final Paginal pgi = getPaginal();
0856: int pgsz = pgi.getPageSize();
0857: final int ofs = pgi.getActivePage() * pgsz;
0858: for (final Iterator it = getItems()
0859: .listIterator(ofs); --pgsz >= 0
0860: && it.hasNext();)
0861: renderer.render((Listitem) it.next());
0862: } catch (Throwable ex) {
0863: renderer.doCatch(ex);
0864: } finally {
0865: renderer.doFinally();
0866: }
0867: }
0868: invalidate();
0869: }
0870: };
0871: pgi.addEventListener("onPagingImpl", _pgImpListener);
0872: }
0873:
0874: /** Removes the event listener for the onPaging event. */
0875: private void removePagingListener(Paginal pgi) {
0876: pgi.removeEventListener(ZulEvents.ON_PAGING, _pgListener);
0877: pgi.removeEventListener("onPagingImpl", _pgImpListener);
0878: }
0879:
0880: /** Returns the child paging controller that is created automatically,
0881: * or null if mold is not "paging", or the controller is specified externally
0882: * by {@link #setPaginal}.
0883: */
0884: public Paging getPaging() {
0885: return _paging;
0886: }
0887:
0888: /** Returns the page size, aka., the number items per page.
0889: * @exception IllegalStateException if {@link #getPaginal} returns null,
0890: * i.e., mold is not "paging" and no external controller is specified.
0891: */
0892: public int getPageSize() {
0893: if (_pgi == null)
0894: throw new IllegalStateException(
0895: "Available only the paging mold");
0896: return _pgi.getPageSize();
0897: }
0898:
0899: /** Sets the page size, aka., the number items per page.
0900: * @exception IllegalStateException if {@link #getPaginal} returns null,
0901: * i.e., mold is not "paging" and no external controller is specified.
0902: */
0903: public void setPageSize(int pgsz) {
0904: if (_pgi == null)
0905: throw new IllegalStateException(
0906: "Available only the paging mold");
0907: _pgi.setPageSize(pgsz);
0908: }
0909:
0910: /** Returns whether this listbox is in the paging mold.
0911: */
0912: /*package*/boolean inPagingMold() {
0913: return "paging".equals(getMold());
0914: }
0915:
0916: /** Returns the index of the first visible child.
0917: * <p>Used only for component development, not for application developers.
0918: */
0919: public int getVisibleBegin() {
0920: if (!inPagingMold())
0921: return 0;
0922: final Paginal pgi = getPaginal();
0923: return pgi.getActivePage() * pgi.getPageSize();
0924: }
0925:
0926: /** Returns the index of the last visible child.
0927: * <p>Used only for component development, not for application developers.
0928: */
0929: public int getVisibleEnd() {
0930: if (!inPagingMold())
0931: return Integer.MAX_VALUE;
0932: final Paginal pgi = getPaginal();
0933: return (pgi.getActivePage() + 1) * pgi.getPageSize() - 1; //inclusive
0934: }
0935:
0936: /** Returns the style class for the odd rows.
0937: * <p>Default: odd.
0938: * @since 3.0.0
0939: */
0940: public String getOddRowSclass() {
0941: return _scOddRow;
0942: }
0943:
0944: /** Sets the style class for the odd rows.
0945: * If the style class doesn't exist, the striping effect disappears.
0946: * You can provide different effects by providing the proper style
0947: * classes.
0948: * @since 3.0.0
0949: */
0950: public void setOddRowSclass(String scls) {
0951: if (scls != null && scls.length() == 0)
0952: scls = null;
0953: if (!Objects.equals(_scOddRow, scls)) {
0954: _scOddRow = scls;
0955: smartUpdate("z.scOddRow", scls);
0956: }
0957: }
0958:
0959: //-- Component --//
0960: public void smartUpdate(String attr, String value) {
0961: if (!_noSmartUpdate)
0962: super .smartUpdate(attr, value);
0963: }
0964:
0965: public void onChildAdded(Component child) {
0966: super .onChildAdded(child);
0967: if (inSelectMold())
0968: invalidate();
0969: //Both IE and Mozilla are buggy if we insert options by innerHTML
0970: else if (inPagingMold() && (child instanceof Listitem))
0971: _pgi.setTotalSize(getItemCount());
0972: }
0973:
0974: public void onChildRemoved(Component child) {
0975: super .onChildRemoved(child);
0976: if (inSelectMold())
0977: invalidate();
0978: //Both IE and Mozilla are buggy if we remove options by outerHTML
0979: //CONSIDER: use special command to remove items
0980: //Cons: if user remove a lot of items it is slower
0981: else if (inPagingMold() && (child instanceof Listitem))
0982: _pgi.setTotalSize(getItemCount());
0983: }
0984:
0985: public boolean insertBefore(Component newChild, Component refChild) {
0986: if (newChild instanceof Listitem) {
0987: //first: listhead or auxhead
0988: //last two: listfoot and paging
0989: if (refChild != null && refChild.getParent() != this )
0990: refChild = null; //Bug 1649625: it becomes the last child
0991: if (refChild != null
0992: && (refChild == _listhead || refChild instanceof Auxhead))
0993: refChild = getChildren().size() > _hdcnt ? (Component) getChildren()
0994: .get(_hdcnt)
0995: : null;
0996:
0997: refChild = fixRefChildBeforeFoot(refChild);
0998: final Listitem newItem = (Listitem) newChild;
0999: final int jfrom = newItem.getParent() == this ? newItem
1000: .getIndex() : -1;
1001:
1002: if (super .insertBefore(newChild, refChild)) {
1003: /** final List children = getChildren(); //Feature #1830886
1004: if (_hdcnt > 0 && children.get(_hdcnt) == newChild)
1005: invalidate();*/
1006: //we place listhead/auxhead and treeitem at different div, so
1007: //this case requires invalidate (because we use insert-after)
1008: //Maintain _items
1009: final int jto = refChild instanceof Listitem ? ((Listitem) refChild)
1010: .getIndex()
1011: : -1, fixFrom = jfrom < 0
1012: || (jto >= 0 && jfrom > jto) ? jto : jfrom;
1013: //jfrom < 0: use jto
1014: //jto < 0: use jfrom
1015: //otherwise: use min(jfrom, jto)
1016: if (fixFrom < 0)
1017: newItem.setIndexDirectly(_items.size() - 1);
1018: else
1019: fixItemIndices(fixFrom);
1020:
1021: //Maintain selected
1022: final int newIndex = newItem.getIndex();
1023: if (newItem.isSelected()) {
1024: if (_jsel < 0) {
1025: _jsel = newIndex;
1026: if (!inSelectMold())
1027: smartUpdate("z.selId", getSelectedId());
1028: _selItems.add(newItem);
1029: } else if (_multiple) {
1030: if (_jsel > newIndex) {
1031: _jsel = newIndex;
1032: if (!inSelectMold())
1033: smartUpdate("z.selId", getSelectedId());
1034: }
1035: _selItems.add(newItem);
1036: } else { //deselect
1037: newItem.setSelectedDirectly(false);
1038: }
1039: } else {
1040: final int oldjsel = _jsel;
1041: if (jfrom < 0) { //no existent child
1042: if (_jsel >= newIndex)
1043: ++_jsel;
1044: } else if (_jsel >= 0) { //any selected
1045: if (jfrom > _jsel) { //from below
1046: if (jto >= 0 && jto <= _jsel)
1047: ++_jsel;
1048: } else { //from above
1049: if (jto < 0 || jto > _jsel)
1050: --_jsel;
1051: }
1052: }
1053:
1054: if (oldjsel != _jsel && !inSelectMold())
1055: smartUpdate("z.selId", getSelectedId());
1056: }
1057: return true;
1058: }
1059: return false;
1060: } else if (newChild instanceof Listhead) {
1061: if (_listhead != null && _listhead != newChild)
1062: throw new UiException("Only one listhead is allowed: "
1063: + this );
1064:
1065: final boolean added = _listhead == null;
1066: refChild = fixRefChildForHeader(refChild);
1067: _listhead = (Listhead) newChild;
1068: if (super .insertBefore(newChild, refChild)) {
1069: if (added)
1070: ++_hdcnt; //it may be moved, not inserted
1071: invalidate(); //required since it might be the first child
1072: return true;
1073: }
1074: return false;
1075: } else if (newChild instanceof Auxhead) {
1076: final boolean added = newChild.getParent() != this ;
1077: refChild = fixRefChildForHeader(refChild);
1078: if (super .insertBefore(newChild, refChild)) {
1079: if (added)
1080: ++_hdcnt; //it may be moved, not inserted
1081: //not need to invalidate since auxhead visible only
1082: //with _listhead
1083: return true;
1084: }
1085: return false;
1086: } else if (newChild instanceof Listfoot) {
1087: if (_listfoot != null && _listfoot != newChild)
1088: throw new UiException("Only one listfoot is allowed: "
1089: + this );
1090:
1091: if (inSelectMold())
1092: log.warning("Mold select ignores listfoot");
1093: invalidate();
1094: //we place listfoot and treeitem at different div, so...
1095: _listfoot = (Listfoot) newChild;
1096: refChild = _paging; //the last two: listfoot and paging
1097: return super .insertBefore(newChild, refChild);
1098: } else if (newChild instanceof Paging) {
1099: if (_paging != null && _paging != newChild)
1100: throw new UiException("Only one paging is allowed: "
1101: + this );
1102: if (_pgi != null)
1103: throw new UiException(
1104: "External paging cannot coexist with child paging");
1105: if (!inPagingMold())
1106: throw new UiException(
1107: "The child paging is allowed only in the paging mold");
1108:
1109: invalidate();
1110: _pgi = _paging = (Paging) newChild;
1111: refChild = null; //the last: paging
1112: return super .insertBefore(newChild, refChild);
1113: } else {
1114: throw new UiException("Unsupported child for Listbox: "
1115: + newChild);
1116: }
1117: }
1118:
1119: private Component fixRefChildForHeader(Component refChild) {
1120: if (refChild != null && refChild.getParent() != this )
1121: refChild = null;
1122:
1123: //try the first listitem
1124: if (refChild == null
1125: || (refChild != _listhead && !(refChild instanceof Auxhead)))
1126: refChild = getChildren().size() > _hdcnt ? (Component) getChildren()
1127: .get(_hdcnt)
1128: : null;
1129:
1130: //try listfoot or paging if no listem
1131: refChild = fixRefChildBeforeFoot(refChild);
1132: return refChild;
1133: }
1134:
1135: private Component fixRefChildBeforeFoot(Component refChild) {
1136: if (refChild == null) {
1137: if (_listfoot != null)
1138: refChild = _listfoot;
1139: else
1140: refChild = _paging;
1141: } else if (refChild == _paging && _listfoot != null) {
1142: refChild = _listfoot;
1143: }
1144: return refChild;
1145: }
1146:
1147: public boolean removeChild(Component child) {
1148: if (!super .removeChild(child))
1149: return false;
1150:
1151: if (_listhead == child) {
1152: _listhead = null;
1153: --_hdcnt;
1154: } else if (_listfoot == child) {
1155: _listfoot = null;
1156: } else if (child instanceof Listitem) {
1157: //maintain items
1158: final Listitem item = (Listitem) child;
1159: final int index = item.getIndex();
1160: item.setIndexDirectly(-1); //mark
1161: fixItemIndices(index);
1162:
1163: //Maintain selected
1164: if (item.isSelected()) {
1165: _selItems.remove(item);
1166: if (_jsel == index) {
1167: fixSelectedIndex(index);
1168: if (!inSelectMold())
1169: smartUpdate("z.selId", getSelectedId());
1170: }
1171: } else {
1172: if (_jsel >= index) {
1173: --_jsel;
1174: if (!inSelectMold())
1175: smartUpdate("z.selId", getSelectedId());
1176: }
1177: }
1178: return true;
1179: } else if (_paging == child) {
1180: _paging = null;
1181: if (_pgi == child)
1182: _pgi = null;
1183: } else if (child instanceof Auxhead) {
1184: --_hdcnt;
1185: }
1186: invalidate();
1187: return true;
1188: }
1189:
1190: /** Fix the selected index, _jsel, assuming there are no selected one
1191: * before (and excludes) j-the item.
1192: */
1193: private void fixSelectedIndex(int j) {
1194: if (!_selItems.isEmpty()) {
1195: for (Iterator it = _items.listIterator(j); it.hasNext(); ++j) {
1196: final Listitem item = (Listitem) it.next();
1197: if (item.isSelected()) {
1198: _jsel = j;
1199: return;
1200: }
1201: }
1202: }
1203: _jsel = -1;
1204: }
1205:
1206: /** Fix Childitem._index since j-th item. */
1207: private void fixItemIndices(int j) {
1208: for (Iterator it = _items.listIterator(j); it.hasNext(); ++j)
1209: ((Listitem) it.next()).setIndexDirectly(j);
1210: }
1211:
1212: //-- ListModel dependent codes --//
1213: /** Returns the list model associated with this listbox, or null
1214: * if this listbox is not associated with any list data model.
1215: */
1216: public ListModel getModel() {
1217: return _model;
1218: }
1219:
1220: /** Sets the list model associated with this listbox.
1221: * If a non-null model is assigned, no matter whether it is the same as
1222: * the previous, it will always cause re-render.
1223: *
1224: * @param model the list model to associate, or null to dis-associate
1225: * any previous model.
1226: * @exception UiException if failed to initialize with the model
1227: */
1228: public void setModel(ListModel model) {
1229: if (model != null) {
1230: if (_model != model) {
1231: if (_model != null) {
1232: _model.removeListDataListener(_dataListener);
1233: } else {
1234: getItems().clear(); //Bug 1807414
1235: if (!inSelectMold())
1236: smartUpdate("z.model", "true");
1237: }
1238:
1239: _model = model;
1240: initDataListener();
1241: }
1242:
1243: //Always syncModel because it is easier for user to enfore reload
1244: syncModel(-1, -1);
1245: postOnInitRender();
1246: //Since user might setModel and setRender separately or repeatedly,
1247: //we don't handle it right now until the event processing phase
1248: //such that we won't render the same set of data twice
1249: //--
1250: //For better performance, we shall load the first few row now
1251: //(to save a roundtrip)
1252: } else if (_model != null) {
1253: _model.removeListDataListener(_dataListener);
1254: _model = null;
1255: getItems().clear();
1256: if (!inSelectMold())
1257: smartUpdate("z.model", null);
1258: }
1259: }
1260:
1261: /** Returns the renderer to render each item, or null if the default
1262: * renderer is used.
1263: */
1264: public ListitemRenderer getItemRenderer() {
1265: return _renderer;
1266: }
1267:
1268: /** Sets the renderer which is used to render each item
1269: * if {@link #getModel} is not null.
1270: *
1271: * <p>Note: changing a render will not cause the listbox to re-render.
1272: * If you want it to re-render, you could assign the same model again
1273: * (i.e., setModel(getModel())), or fire an {@link ListDataEvent} event.
1274: *
1275: * @param renderer the renderer, or null to use the default.
1276: * @exception UiException if failed to initialize with the model
1277: */
1278: public void setItemRenderer(ListitemRenderer renderer) {
1279: _renderer = renderer;
1280: }
1281:
1282: /** Sets the renderer by use of a class name.
1283: * It creates an instance automatically.
1284: */
1285: public void setItemRenderer(String clsnm)
1286: throws ClassNotFoundException, NoSuchMethodException,
1287: IllegalAccessException, InstantiationException,
1288: java.lang.reflect.InvocationTargetException {
1289: if (clsnm != null)
1290: setItemRenderer((ListitemRenderer) Classes
1291: .newInstanceByThread(clsnm));
1292: }
1293:
1294: /** Returns the number of items to preload when receiving
1295: * the rendering request from the client.
1296: *
1297: * <p>Default: 7.
1298: *
1299: * <p>It is used only if live data ({@link #setModel} and
1300: * not paging ({@link #getPaging}.
1301: *
1302: * @since 2.4.1
1303: */
1304: public int getPreloadSize() {
1305: return _preloadsz;
1306: }
1307:
1308: /** Sets the number of items to preload when receiving
1309: * the rendering request from the client.
1310: * <p>It is used only if live data ({@link #setModel} and
1311: * not paging ({@link #getPaging}.
1312: *
1313: * @param sz the number of items to preload. If zero, no preload
1314: * at all.
1315: * @exception UiException if sz is negative
1316: * @since 2.4.1
1317: */
1318: public void setPreloadSize(int sz) {
1319: if (sz < 0)
1320: throw new UiException("nonnegative is required: " + sz);
1321: _preloadsz = sz;
1322: }
1323:
1324: /** Synchronizes the listbox to be consistent with the specified model.
1325: * @param min the lower index that a range of invalidated items
1326: * @param max the higher index that a range of invalidated items
1327: */
1328: private void syncModel(int min, int max) {
1329: ListitemRenderer renderer = null;
1330: final int newsz = _model.getSize();
1331: final int oldsz = getItemCount();
1332: if (oldsz > 0) {
1333: if (newsz > 0 && min < oldsz) {
1334: if (max < 0 || max >= oldsz)
1335: max = oldsz - 1;
1336: if (max >= newsz)
1337: max = newsz - 1;
1338: if (min < 0)
1339: min = 0;
1340:
1341: //unloadItem() might detach item and add new item, _items must make a copy first
1342: for (Iterator it = new ArrayList(_items)
1343: .listIterator(min); min <= max && it.hasNext(); ++min) {
1344: final Listitem item = (Listitem) it.next();
1345: if (item.isLoaded()) {
1346: if (renderer == null)
1347: renderer = getRealRenderer();
1348: unloadItem(renderer, item);
1349: }
1350: }
1351: }
1352:
1353: for (int j = newsz; j < oldsz; ++j)
1354: getItemAtIndex(newsz).detach(); //detach and remove
1355: }
1356:
1357: for (int j = oldsz; j < newsz; ++j) {
1358: if (renderer == null)
1359: renderer = getRealRenderer();
1360: newUnloadedItem(renderer).setParent(this );
1361: }
1362: }
1363:
1364: /** Creates an new and unloaded listitem. */
1365: private final Listitem newUnloadedItem(ListitemRenderer renderer) {
1366: Listitem item = null;
1367: if (renderer instanceof ListitemRendererExt)
1368: item = ((ListitemRendererExt) renderer).newListitem(this );
1369:
1370: if (item == null) {
1371: item = new Listitem();
1372: item.applyProperties();
1373: }
1374: item.setLoaded(false);
1375:
1376: newUnloadedCell(renderer, item);
1377: return item;
1378: }
1379:
1380: private Listcell newUnloadedCell(ListitemRenderer renderer,
1381: Listitem item) {
1382: Listcell cell = null;
1383: if (renderer instanceof ListitemRendererExt)
1384: cell = ((ListitemRendererExt) renderer).newListcell(item);
1385:
1386: if (cell == null) {
1387: cell = new Listcell();
1388: cell.applyProperties();
1389: }
1390: cell.setParent(item);
1391: return cell;
1392: }
1393:
1394: /** Clears a listitem as if it is not loaded. */
1395: private final void unloadItem(ListitemRenderer renderer,
1396: Listitem item) {
1397: if (!(renderer instanceof ListitemRendererExt)
1398: || (((ListitemRendererExt) renderer).getControls() & ListitemRendererExt.DETACH_ON_UNLOAD) == 0) { //re-use (default)
1399: final List cells = item.getChildren();
1400: if (cells.isEmpty()) {
1401: newUnloadedCell(renderer, item);
1402: } else {
1403: //detach and remove all but the first cell
1404: for (Iterator it = cells.listIterator(1); it.hasNext();) {
1405: it.next();
1406: it.remove();
1407: }
1408:
1409: final Listcell listcell = (Listcell) cells.get(0);
1410: listcell.getChildren().clear(); //another renderer might do diff
1411: listcell.setLabel(null);
1412: listcell.setImage(null);
1413: }
1414: item.setLoaded(false);
1415: } else { //detach
1416: item.getParent().insertBefore(newUnloadedItem(renderer),
1417: item);
1418: item.detach();
1419: }
1420: }
1421:
1422: /** Handles a private event, onInitRender. It is used only for
1423: * implementation, and you rarely need to invoke it explicitly.
1424: */
1425: public void onInitRender() {
1426: final Renderer renderer = new Renderer();
1427: try {
1428: int pgsz, ofs;
1429: if (inPagingMold()) {
1430: pgsz = _pgi.getPageSize();
1431: ofs = _pgi.getActivePage() * pgsz;
1432: final int cnt = getItemCount();
1433: if (ofs >= cnt) { //not possible; just in case
1434: ofs = cnt - pgsz;
1435: if (ofs < 0)
1436: ofs = 0;
1437: }
1438: } else {
1439: pgsz = inSelectMold() ? getItemCount()
1440: : _rows > 0 ? _rows + 5 : 20;
1441: ofs = 0;
1442: //we don't know # of visible rows, so a 'smart' guess
1443: //It is OK since client will send back request if not enough
1444: }
1445:
1446: int j = 0;
1447: for (Iterator it = getItems().listIterator(ofs); j < pgsz
1448: && it.hasNext(); ++j)
1449: renderer.render((Listitem) it.next());
1450: if (!inPagingMold() && getItemCount() > pgsz)
1451: getItemAtIndex(pgsz).setAttribute(
1452: Attributes.SKIP_SIBLING, Boolean.TRUE);
1453: } catch (Throwable ex) {
1454: renderer.doCatch(ex);
1455: } finally {
1456: renderer.doFinally();
1457: }
1458: }
1459:
1460: private void postOnInitRender() {
1461: Events.postEvent("onInitRender", this , null);
1462: smartUpdate("z.render", true);
1463: }
1464:
1465: /** Handles when the list model's content changed.
1466: */
1467: private void onListDataChange(ListDataEvent event) {
1468: if (inSelectMold()) {
1469: invalidate();
1470: syncModel(-1, -1);
1471: postOnInitRender();
1472: return;
1473: }
1474:
1475: //when this is called _model is never null
1476: final int newsz = _model.getSize(), oldsz = getItemCount();
1477: int min = event.getIndex0(), max = event.getIndex1();
1478: if (min < 0)
1479: min = 0;
1480:
1481: boolean done = false;
1482: switch (event.getType()) {
1483: case ListDataEvent.INTERVAL_ADDED:
1484: if (max < 0)
1485: max = newsz - 1;
1486: if ((max - min + 1) != (newsz - oldsz)) {
1487: log
1488: .warning("Conflict event: number of added items not matched: "
1489: + event);
1490: break; //handle it as CONTENTS_CHANGED
1491: }
1492:
1493: ListitemRenderer renderer = null;
1494: final Listitem before = min < oldsz ? getItemAtIndex(min)
1495: : null;
1496: for (int j = min; j <= max; ++j) {
1497: if (renderer == null)
1498: renderer = getRealRenderer();
1499: insertBefore(newUnloadedItem(renderer), before);
1500: }
1501: done = true;
1502: break;
1503:
1504: case ListDataEvent.INTERVAL_REMOVED:
1505: if (max < 0)
1506: max = oldsz - 1;
1507: int cnt = max - min + 1;
1508: if (cnt != (oldsz - newsz)) {
1509: log
1510: .warning("Conflict event: number of removed items not matched: "
1511: + event);
1512: break; //handle it as CONTENTS_CHANGED
1513: }
1514:
1515: //detach and remove
1516: for (Iterator it = getItems().listIterator(min); --cnt >= 0
1517: && it.hasNext();) {
1518: it.next();
1519: it.remove();
1520: }
1521:
1522: done = true;
1523: break;
1524: }
1525:
1526: if (!done) //CONTENTS_CHANGED
1527: syncModel(min, max);
1528:
1529: postOnInitRender();
1530: //Bug 1823236: though fixed in JS, it improves performance
1531: //to save one roundtrip
1532: }
1533:
1534: private static final ListitemRenderer getDefaultItemRenderer() {
1535: return _defRend;
1536: }
1537:
1538: private static final ListitemRenderer _defRend = new ListitemRenderer() {
1539: public void render(Listitem item, Object data) {
1540: item.setLabel(Objects.toString(data));
1541: item.setValue(data);
1542: }
1543: };
1544:
1545: /** Returns the renderer used to render items.
1546: */
1547: private ListitemRenderer getRealRenderer() {
1548: return _renderer != null ? _renderer : getDefaultItemRenderer();
1549: }
1550:
1551: /** Used to render listitem if _model is specified. */
1552: private class Renderer implements java.io.Serializable {
1553: private final ListitemRenderer _renderer;
1554: private boolean _rendered, _ctrled;
1555:
1556: private Renderer() {
1557: _renderer = getRealRenderer();
1558: }
1559:
1560: private void render(Listitem item) throws Throwable {
1561: if (item.isLoaded())
1562: return; //nothing to do
1563:
1564: if (!_rendered && (_renderer instanceof RendererCtrl)) {
1565: ((RendererCtrl) _renderer).doTry();
1566: _ctrled = true;
1567: }
1568:
1569: final Listcell cell = (Listcell) item.getFirstChild();
1570: if (!(_renderer instanceof ListitemRendererExt)
1571: || (((ListitemRendererExt) _renderer).getControls() & ListitemRendererExt.DETACH_ON_RENDER) != 0) { //detach (default)
1572: cell.detach();
1573: }
1574:
1575: try {
1576: _renderer.render(item, _model.getElementAt(item
1577: .getIndex()));
1578: } catch (Throwable ex) {
1579: try {
1580: item.setLabel(Exceptions.getMessage(ex));
1581: } catch (Throwable t) {
1582: log.error(t);
1583: }
1584: item.setLoaded(true);
1585: throw ex;
1586: } finally {
1587: if (item.getChildren().isEmpty())
1588: cell.setParent(item);
1589: }
1590:
1591: item.setLoaded(true);
1592: _rendered = true;
1593: }
1594:
1595: private void doCatch(Throwable ex) {
1596: if (_ctrled) {
1597: try {
1598: ((RendererCtrl) _renderer).doCatch(ex);
1599: } catch (Throwable t) {
1600: throw UiException.Aide.wrap(t);
1601: }
1602: } else {
1603: throw UiException.Aide.wrap(ex);
1604: }
1605: }
1606:
1607: private void doFinally() {
1608: if (_ctrled)
1609: ((RendererCtrl) _renderer).doFinally();
1610: }
1611: }
1612:
1613: /** Renders the specified {@link Listitem} if not loaded yet,
1614: * with {@link #getItemRenderer}.
1615: *
1616: * <p>It does nothing if {@link #getModel} returns null.
1617: * In other words, it is meaningful only if live data model is used.
1618: *
1619: * @see #renderItems
1620: * @see #renderAll
1621: * @return the list item being passed to this method
1622: */
1623: public Listitem renderItem(Listitem li) {
1624: if (_model != null && !li.isLoaded()) {
1625: final Renderer renderer = new Renderer();
1626: try {
1627: renderer.render(li);
1628: } catch (Throwable ex) {
1629: renderer.doCatch(ex);
1630: } finally {
1631: renderer.doFinally();
1632: }
1633: }
1634: return li;
1635: }
1636:
1637: /** Renders all {@link Listitem} if not loaded yet,
1638: * with {@link #getItemRenderer}.
1639: *
1640: * @see #renderItem
1641: * @see #renderItems
1642: */
1643: public void renderAll() {
1644: if (_model == null)
1645: return;
1646:
1647: final Renderer renderer = new Renderer();
1648: try {
1649: for (Iterator it = getItems().iterator(); it.hasNext();)
1650: renderer.render((Listitem) it.next());
1651: } catch (Throwable ex) {
1652: renderer.doCatch(ex);
1653: } finally {
1654: renderer.doFinally();
1655: }
1656: }
1657:
1658: public void renderItems(Set items) {
1659: if (_model == null)
1660: return;
1661:
1662: if (items.isEmpty())
1663: return; //nothing to do
1664:
1665: final Renderer renderer = new Renderer();
1666: try {
1667: for (Iterator it = items.iterator(); it.hasNext();)
1668: renderer.render((Listitem) it.next());
1669: } catch (Throwable ex) {
1670: renderer.doCatch(ex);
1671: } finally {
1672: renderer.doFinally();
1673: }
1674: }
1675:
1676: //-- super --//
1677: public void setMold(String mold) {
1678: final String old = getMold();
1679: if (!Objects.equals(old, mold)) {
1680: super .setMold(mold);
1681:
1682: if ("paging".equals(old)) { //change from paging
1683: if (_paging != null) {
1684: removePagingListener(_paging);
1685: _paging.detach();
1686: } else if (_pgi != null) {
1687: removePagingListener(_pgi);
1688: }
1689: } else if (inPagingMold()) { //change to paging
1690: if (_pgi != null)
1691: addPagingListener(_pgi);
1692: else
1693: newInternalPaging();
1694: }
1695: }
1696: }
1697:
1698: public String getOuterAttrs() {
1699: final StringBuffer sb = new StringBuffer(80).append(super
1700: .getOuterAttrs());
1701:
1702: if (inSelectMold()) {
1703: HTMLs.appendAttribute(sb, "name", _name);
1704: HTMLs.appendAttribute(sb, "size", getRows());
1705:
1706: if (isMultiple())
1707: HTMLs.appendAttribute(sb, "multiple", "multiple");
1708: if (_disabled)
1709: HTMLs.appendAttribute(sb, "disabled", "disabled");
1710: if (_tabindex >= 0)
1711: HTMLs.appendAttribute(sb, "tabindex", _tabindex);
1712: } else {
1713: HTMLs.appendAttribute(sb, "z.name", _name);
1714: HTMLs.appendAttribute(sb, "z.size", _rows);
1715: if (_disabled)
1716: HTMLs.appendAttribute(sb, "z.disabled", true);
1717: if (_multiple)
1718: HTMLs.appendAttribute(sb, "z.multiple", true);
1719: HTMLs.appendAttribute(sb, "z.selId", getSelectedId());
1720: //if (_checkmark)
1721: // HTMLs.appendAttribute(sb, "z.checkmark", true);
1722: if (_vflex)
1723: HTMLs.appendAttribute(sb, "z.vflex", true);
1724: if (_model != null)
1725: HTMLs.appendAttribute(sb, "z.model", true);
1726:
1727: if (_scOddRow != null)
1728: HTMLs.appendAttribute(sb, "z.scOddRow", _scOddRow);
1729:
1730: if (getModel() != null) {
1731: int index = getItemCount();
1732: for (final ListIterator it = getItems().listIterator(
1733: index); it.hasPrevious(); --index)
1734: if (((Listitem) it.previous()).isLoaded())
1735: break;
1736: HTMLs.appendAttribute(sb, "z.lastLoadIdx", index);
1737: }
1738: }
1739:
1740: appendAsapAttr(sb, Events.ON_SELECT);
1741: return sb.toString();
1742: }
1743:
1744: private class ItemIter implements ListIterator,
1745: java.io.Serializable {
1746: private ListIterator _it;
1747: private int _j;
1748: private boolean _bNxt;
1749:
1750: private ItemIter(int index) {
1751: _j = index;
1752: }
1753:
1754: public void add(Object o) {
1755: prepare();
1756: _it.add(o);
1757: ++_j;
1758: }
1759:
1760: public boolean hasNext() {
1761: return _j < _items.size();
1762: }
1763:
1764: public boolean hasPrevious() {
1765: return _j > 0;
1766: }
1767:
1768: public Object next() {
1769: if (!hasNext())
1770: throw new NoSuchElementException();
1771:
1772: prepare();
1773: final Object o = _it.next();
1774: ++_j;
1775: _bNxt = true;
1776: return o;
1777: }
1778:
1779: public Object previous() {
1780: if (!hasPrevious())
1781: throw new NoSuchElementException();
1782:
1783: prepare();
1784: final Object o = _it.previous();
1785: --_j;
1786: _bNxt = false;
1787: return o;
1788: }
1789:
1790: public int nextIndex() {
1791: return _j;
1792: }
1793:
1794: public int previousIndex() {
1795: return _j - 1;
1796: }
1797:
1798: public void remove() {
1799: if (_it == null)
1800: throw new IllegalStateException();
1801: _it.remove();
1802: if (_bNxt)
1803: --_j;
1804: }
1805:
1806: public void set(Object o) {
1807: if (_it == null)
1808: throw new IllegalStateException();
1809: _it.set(o);
1810: }
1811:
1812: private void prepare() {
1813: if (_it == null)
1814: _it = getChildren().listIterator(_j + _hdcnt);
1815: }
1816: }
1817:
1818: //Cloneable//
1819: public Object clone() {
1820: final Listbox clone = (Listbox) super .clone();
1821: clone.init();
1822: clone.afterUnmarshal();
1823: if (clone._model != null) {
1824: //we use the same data model but we have to create a new listener
1825: clone._dataListener = null;
1826: clone.initDataListener();
1827: }
1828: return clone;
1829: }
1830:
1831: private void afterUnmarshal() {
1832: int index = 0;
1833: for (Iterator it = getChildren().iterator(); it.hasNext();) {
1834: final Object child = it.next();
1835: if (child instanceof Listitem) {
1836: final Listitem li = (Listitem) child;
1837: li.setIndexDirectly(index++); //since Listitem.clone() resets index
1838: if (li.isSelected()) {
1839: _selItems.add(li);
1840: }
1841: } else if (child instanceof Listhead) {
1842: _listhead = (Listhead) child;
1843: } else if (child instanceof Listfoot) {
1844: _listfoot = (Listfoot) child;
1845: } else if (child instanceof Paging) {
1846: _pgi = _paging = (Paging) child;
1847: }
1848: }
1849: }
1850:
1851: //-- Serializable --//
1852: private synchronized void readObject(java.io.ObjectInputStream s)
1853: throws java.io.IOException, ClassNotFoundException {
1854: s.defaultReadObject();
1855:
1856: init();
1857:
1858: afterUnmarshal();
1859: //TODO: how to marshal _pgi if _pgi != _paging
1860: //TODO: re-register event listener for onPaging
1861:
1862: if (_model != null)
1863: initDataListener();
1864: }
1865:
1866: //-- ComponentCtrl --//
1867: protected Object newExtraCtrl() {
1868: return new ExtraCtrl();
1869: }
1870:
1871: /** A utility class to implement {@link #getExtraCtrl}.
1872: * It is used only by component developers.
1873: */
1874: protected class ExtraCtrl extends XulElement.ExtraCtrl implements
1875: InnerWidth, Selectable, Cropper, RenderOnDemand {
1876: //InnerWidth//
1877: public void setInnerWidthByClient(String width) {
1878: _innerWidth = width == null ? "100%" : width;
1879: }
1880:
1881: //RenderOnDemand//
1882: public void renderItems(Set items) {
1883: int cnt = items.size();
1884: if (cnt == 0)
1885: return; //nothing to do
1886: cnt = 20 - cnt;
1887:
1888: if (cnt > 0 && _preloadsz > 0) { //Feature 1740072: pre-load
1889: if (cnt > _preloadsz)
1890: cnt = _preloadsz; //at most 8 more to load
1891:
1892: //1. locate the first item found in items
1893: final List toload = new LinkedList();
1894: Iterator it = _items.iterator();
1895: while (it.hasNext()) {
1896: final Listitem li = (Listitem) it.next();
1897: if (items.contains(li)) //found
1898: break;
1899: if (!li.isLoaded())
1900: toload.add(0, li); //reverse order
1901: }
1902:
1903: //2. add unload items before the found one
1904: if (!toload.isEmpty()) {
1905: int bfcnt = cnt / 3;
1906: for (Iterator e = toload.iterator(); bfcnt > 0
1907: && e.hasNext(); --bfcnt, --cnt) {
1908: items.add(e.next());
1909: }
1910: }
1911:
1912: //3. add unloaded after the found one
1913: while (cnt > 0 && it.hasNext()) {
1914: final Listitem li = (Listitem) it.next();
1915: if (!li.isLoaded() && items.add(li))
1916: --cnt;
1917: }
1918: }
1919:
1920: Listbox.this .renderItems(items);
1921: }
1922:
1923: //--Cropper--//
1924: public boolean isCropper() {
1925: return inPagingMold();
1926: }
1927:
1928: public Set getAvailableAtClient() {
1929: if (!inPagingMold())
1930: return null;
1931:
1932: final Set avail = new HashSet(37);
1933: avail.addAll(_heads);
1934: if (_listfoot != null)
1935: avail.add(_listfoot);
1936: if (_paging != null)
1937: avail.add(_paging);
1938:
1939: final Paginal pgi = getPaginal();
1940: int pgsz = pgi.getPageSize();
1941: final int ofs = pgi.getActivePage() * pgsz;
1942: for (final Iterator it = getItems().listIterator(ofs); --pgsz >= 0
1943: && it.hasNext();)
1944: avail.add(it.next());
1945: return avail;
1946: }
1947:
1948: //-- Selectable --//
1949: public void selectItemsByClient(Set selItems) {
1950: _noSmartUpdate = true;
1951: try {
1952: final boolean paging = inPagingMold();
1953: if (!_multiple
1954: || (!paging && (selItems == null || selItems
1955: .size() <= 1))) {
1956: final Listitem item = selItems != null
1957: && selItems.size() > 0 ? (Listitem) selItems
1958: .iterator().next()
1959: : null;
1960: selectItem(item);
1961: } else {
1962: int from, to;
1963: if (paging) {
1964: final Paginal pgi = getPaginal();
1965: int pgsz = pgi.getPageSize();
1966: from = pgi.getActivePage() * pgsz;
1967: to = from + pgsz; //excluded
1968: } else {
1969: from = to = 0;
1970: }
1971:
1972: int j = 0;
1973: for (Iterator it = _items.iterator(); it.hasNext(); ++j) {
1974: final Listitem item = (Listitem) it.next();
1975: if (selItems.contains(item)) {
1976: addItemToSelection(item);
1977: } else if (!paging) {
1978: removeItemFromSelection(item);
1979: } else {
1980: final int index = item.getIndex();
1981: if (index >= from && index < to)
1982: removeItemFromSelection(item);
1983: }
1984: }
1985: }
1986: } finally {
1987: _noSmartUpdate = false;
1988: }
1989: }
1990: }
1991:
1992: /** An iterator used by _heads.
1993: */
1994: private class Iter implements Iterator {
1995: private final Iterator _it = getChildren().iterator();
1996: private int _j;
1997:
1998: public boolean hasNext() {
1999: return _j < _hdcnt;
2000: }
2001:
2002: public Object next() {
2003: final Object o = _it.next();
2004: ++_j;
2005: return o;
2006: }
2007:
2008: public void remove() {
2009: throw new UnsupportedOperationException();
2010: }
2011: }
2012: }
|