/* Group.java Purpose: Description: History: Apr 25, 2008 4:15:11 PM , Created by jumperchen Copyright (C) 2008 Potix Corporation. All Rights Reserved. {{IS_RIGHT This program is distributed under LGPL Version 2.1 in the hope that it will be useful, but WITHOUT ANY WARRANTY. }}IS_RIGHT */ package org.zkoss.zul; import java.util.AbstractList; import java.util.Iterator; import java.util.List; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.UiException; import org.zkoss.zk.ui.event.Events; import org.zkoss.zk.ui.event.OpenEvent; import org.zkoss.zul.impl.GroupsListModel; import org.zkoss.zul.impl.XulElement; /** * Adds the ability for single level grouping to the Grid. * <p>Available in ZK PE and EE. * * <p>Event: * <ol> * <li>onOpen is sent when this listgroup is opened or closed by user.</li> * </ol> * * <p>Default {@link #getZclass}: z-group. * * <p>Note: All the child of this component are automatically applied * the group-cell CSS, if you don't want this CSS, you can invoke the {@link Label#setSclass(String)} * after the child added. * @author jumperchen * @since 3.5.0 */ public class Group extends Row { private boolean _open = true; private transient List<Row> _items; static { addClientEvent(Group.class, Events.ON_OPEN, CE_DUPLICATE_IGNORE | CE_IMPORTANT); } public Group() { init(); } public Group(String label) { this(); setLabel(label); } public <T> Group(String label, T value) { this(); setLabel(label); setValue(value); } private void init() { _items = new AbstractList<Row>() { public int size() { return getItemCount(); } public Iterator<Row> iterator() { return new IterItems(); } public Row get(int index) { final Rows rows = (Rows) getParent(); if (rows != null) { int i = 0; for (Iterator<Component> it = rows.getChildren().listIterator(getIndex() + 1); it.hasNext() && i <= index; i++) { if (i == index) return (Row) it.next(); it.next(); } } throw new IndexOutOfBoundsException("Index: " + index); } }; } /** * Returns a list of all {@link Row} are grouped by this group. */ public List<Row> getItems() { return _items; } /** Returns the number of items. */ public int getItemCount() { final Rows rows = (Rows) getParent(); if (rows != null) { int[] g = rows.getGroupsInfoAt(getIndex(), true); if (g != null) { if (g[2] == -1) return g[1] - 1; else return g[1] - 2; } } return 0; } public Group getGroup() { return this; } /** * Returns the number of visible descendant {@link Row}. * @since 3.5.1 */ public int getVisibleItemCount() { int count = getItemCount(); int visibleCount = 0; Row row = (Row) getNextSibling(); while (count-- > 0 && row != null) { if (row.isVisible()) visibleCount++; row = (Row) row.getNextSibling(); } return visibleCount; } /** * Returns the index of Groupfoot * <p> -1: no Groupfoot */ public int getGroupfootIndex() { final Rows rows = (Rows) getParent(); if (rows != null) { int[] g = rows.getGroupsInfoAt(getIndex(), true); if (g != null) return g[2]; } return -1; } /** * Returns the Groupfoot, if any. Otherwise, null is returned. */ public Groupfoot getGroupfoot() { int index = getGroupfootIndex(); if (index < 0) return null; final Rows rows = (Rows) getParent(); return (Groupfoot) rows.getChildren().get(index); } /** Returns whether this container is open. * <p>Default: true. */ public boolean isOpen() { return _open; } /** Sets whether this container is open. * * <p>Note: if you use a model as the data to render, don't use setOpen(). It'll tangle the lifecycle with model * You should control the model directly. * For example, you can use setClose() of GroupsModelArray */ public void setOpen(boolean open) { if (_open != open) { _open = open; smartUpdate("open", _open); final Rows rows = (Rows) getParent(); if (rows != null) rows.addVisibleItemCount(isOpen() ? getVisibleItemCount() : -getVisibleItemCount()); } } /** Returns the HTML IMG tag for the image part, or null * if no image is assigned. * * <p>Used only for component template, not for application developers. * */ public String getImgTag() { final StringBuffer sb = new StringBuffer(64).append("<img src=\"") .append(getDesktop().getExecution().encodeURL("~./img/spacer.gif")).append("\" class=\"") .append(getZclass()).append("-img ").append(getZclass()).append(isOpen() ? "-img-open" : "-img-close") .append("\" align=\"absmiddle\"/>"); final String label = getLabel(); if (label != null && label.length() > 0) sb.append(' '); return sb.toString(); //keep a space } /** Returns the value of the {@link Label} it contains, or null * if no such cell. */ public String getLabel() { final Component cell = getFirstChild(); return cell != null && cell instanceof Label ? ((Label) cell).getValue() : null; } /** Sets the value of the {@link Label} it contains. * * <p>If it is not created, we automatically create it. */ public void setLabel(String label) { autoFirstCell().setValue(label); } private Label autoFirstCell() { Component cell = getFirstChild(); if (cell == null || cell instanceof Label) { if (cell == null) cell = new Label(); cell.applyProperties(); cell.setParent(this); return (Label) cell; } throw new UiException("Unsupported child for setLabel: " + cell); } public String getZclass() { return _zclass == null ? "z-group" : _zclass; } // super protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) throws java.io.IOException { super.renderProperties(renderer); if (!isOpen()) renderer.render("open", false); } //-- ComponentCtrl --// /** Processes an AU request. * * <p>Default: in addition to what are handled by {@link XulElement#service}, * it also handles onOpen. * @since 5.0.0 */ public void service(org.zkoss.zk.au.AuRequest request, boolean everError) { final String cmd = request.getCommand(); if (cmd.equals(Events.ON_OPEN)) { OpenEvent evt = OpenEvent.getOpenEvent(request); _open = evt.isOpen(); final Rows rows = (Rows) getParent(); if (rows != null) { rows.addVisibleItemCount(_open ? getVisibleItemCount() : -getVisibleItemCount()); final Grid grid = getGrid(); if (grid != null) { final ListModel model = grid.getModel(); if (model instanceof GroupsListModel) { int gindex = rows.getGroupIndex(getIndex()); GroupsModel gmodel = ((GroupsListModel) model).getGroupsModel(); if (_open) gmodel.addOpenGroup(gindex); else gmodel.removeOpenGroup(gindex); } } } Events.postEvent(evt); } else super.service(request, everError); } /** * An iterator used by _items. */ private class IterItems implements Iterator<Row> { private final Iterator<Component> _it = getParent().getChildren().listIterator(getIndex() + 1); private int _j; public boolean hasNext() { return _j < getItemCount(); } public Row next() { ++_j; return (Row) _it.next(); } public void remove() { throw new UnsupportedOperationException(); } } //Cloneable// public Object clone() { final Group clone = (Group) super.clone(); clone.init(); return clone; } //-- Serializable --// private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); init(); } }