All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zkoss.zul.Column Maven / Gradle / Ivy

There is a newer version: 10.0.0-jakarta
Show newest version
/* Column.java

	Purpose:
		
	Description:
		
	History:
		Tue Oct 25 16:02:36     2005, Created by tomyeh

Copyright (C) 2005 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.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.zkoss.lang.Classes;
import org.zkoss.lang.Objects;
import org.zkoss.lang.Strings;
import org.zkoss.mesg.Messages;
import org.zkoss.zk.au.AuRequests;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Components;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.SortEvent;
import org.zkoss.zk.ui.ext.Scopes;
import org.zkoss.zul.ext.GroupsSortableModel;
import org.zkoss.zul.ext.Sortable;
import org.zkoss.zul.impl.HeaderElement;
import org.zkoss.zul.mesg.MZul;

/**
 * A single column in a {@link Columns} element.
 * Each child of the {@link Column} element is placed in each successive
 * cell of the grid.
 * The column with the most child elements determines the number of rows
 * in each column.
 *
 * 

The use of column is mainly to define attributes for each cell * in the grid. * *

Default {@link #getZclass}: z-column. (since 3.5.0) * * @author tomyeh */ public class Column extends HeaderElement { private String _sortDir = "natural"; private transient Comparator _sortAsc, _sortDsc; private String _sortAscNm = "none"; private String _sortDscNm = "none"; private Object _value; private boolean _ignoreSort = false; private boolean _isCustomAscComparator = false; private boolean _isCustomDscComparator = false; static { addClientEvent(Column.class, Events.ON_SORT, CE_DUPLICATE_IGNORE); addClientEvent(Column.class, Events.ON_GROUP, CE_DUPLICATE_IGNORE); addClientEvent(Column.class, Events.ON_UNGROUP, CE_DUPLICATE_IGNORE); } public Column() { } public Column(String label) { super(label); } /* Constructs a grid header with label and image. * * @param lable the label. No label if null or empty. * @param src the URI of the image, or null to ignore. */ public Column(String label, String src) { super(label, src); } /* Constructs a grid header with label, image and width. * * @param lable the label. No label if null or empty. * @param src the URI of the image. Ignored if null or empty. * @param width the width of the column. Ignored if null or empty. * @since 3.0.4 */ public Column(String label, String src, String width) { super(label, src); setWidth(width); } /** Returns the grid that contains this column. */ public Grid getGrid() { final Component parent = getParent(); return parent != null ? (Grid) parent.getParent() : null; } /** Returns the sort direction. *

Default: "natural". */ public String getSortDirection() { return _sortDir; } /** Sets the sort direction. This does not sort the data, it only serves * as an indicator as to how the grid is sorted. (unless the grid has "autosort" attribute) * *

If you use {@link #sort(boolean)} to sort rows ({@link Row}), * the sort direction is maintained automatically. * If you want to sort it in customized way, you have to set the * sort direction manually. * * @param sortDir one of "ascending", "descending" and "natural" */ public void setSortDirection(String sortDir) throws WrongValueException { if (sortDir == null || (!"ascending".equals(sortDir) && !"descending".equals(sortDir) && !"natural".equals(sortDir))) throw new WrongValueException("Unknown sort direction: " + sortDir); if (!Objects.equals(_sortDir, sortDir)) { _sortDir = sortDir; if (!"natural".equals(sortDir) && !_ignoreSort) { Grid grid = getGrid(); if (grid != null && grid.isAutosort()) { doSort("ascending".equals(sortDir)); } } smartUpdate("sortDirection", _sortDir); } } /** Sets the type of the sorter. * You might specify either "auto", "auto(FIELD_NAME1[,FIELD_NAME2] ...)"(since 3.5.3), * "auto(number)" (since 5.0.6) or "none". * *

If "client" or "client(number)" is specified, * the sort functionality will be done by Javascript at client without notifying * to server, that is, the order of the component in the row is out of sync. *

    *
  • "client" : it is treated by a string
  • *
  • "client(number)" : it is treated by a number
  • *
*

Note: client sorting cannot work in model case. (since 5.0.0) * *

If "auto" is specified, * {@link #setSortAscending} and/or {@link #setSortDescending} * are called with {@link RowComparator}, if * {@link #getSortDescending} and/or {@link #getSortAscending} are null. * If you assigned a comparator to them, it won't be affected. * The auto created comparator is case-insensitive. * *

If "auto(FIELD_NAME1, FIELD_NAME2, ...)" is specified, * {@link #setSortAscending} and/or {@link #setSortDescending} * are called with {@link FieldComparator}, if * {@link #getSortDescending} and/or {@link #getSortAscending} are null. * If you assigned a comparator to them, it won't be affected. * The auto created comparator is case-sensitive. * *

If "auto(LOWER(FIELD_NAME))" or "auto(UPPER(FIELD_NAME))" is specified, * {@link #setSortAscending} and/or {@link #setSortDescending} * are called with {@link FieldComparator}, if * {@link #getSortDescending} and/or {@link #getSortAscending} are null. * If you assigned a comparator to them, it won't be affected. * The auto created comparator is case-insensitive. * *

If "auto(number)" is specified, * {@link #setSortAscending} and/or {@link #setSortDescending} * are called with {@link ArrayComparator}. Notice that the data must * be an array and the number-th element must be comparable ({@link Comparable}). * *

If "none" is specified, both {@link #setSortAscending} and * {@link #setSortDescending} are called with null. * Therefore, no more sorting is available to users for this column. * @throws IllegalAccessException * @throws InstantiationException * @throws ClassNotFoundException * @since 3.5.3 */ public void setSort(String type) throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (type == null) return; if (type.startsWith("client")) { setSortAscending(type); setSortDescending(type); } else if ("auto".equals(type)) { if (getSortAscending() == null) setSortAscending(new RowComparator(this, true, false, false)); if (getSortDescending() == null) setSortDescending(new RowComparator(this, false, false, false)); } else if (type.startsWith("auto")) { final int j = type.indexOf('('); final int k = type.lastIndexOf(')'); if (j >= 0 && k >= 0) { final String name = type.substring(j + 1, k); char cc; int index = -1; if (name.length() > 0 && (cc = name.charAt(0)) >= '0' && cc <= '9') if ((index = Integer.parseInt(name)) < 0) throw new IllegalArgumentException("Nonnegative number is required: " + name); if (getSortAscending() == null || !_isCustomAscComparator) { if (index < 0) setSortAscending(new FieldComparator(name, true)); else setSortAscending(new ArrayComparator(index, true)); _isCustomAscComparator = false; } if (getSortDescending() == null || !_isCustomDscComparator) { if (index < 0) setSortDescending(new FieldComparator(name, false)); else setSortDescending(new ArrayComparator(index, false)); _isCustomDscComparator = false; } } else { throw new UiException("Unknown sort type: " + type); } } else if ("none".equals(type)) { setSortAscending((Comparator) null); setSortDescending((Comparator) null); } } /** Returns the ascending sorter, or null if not available. */ public Comparator getSortAscending() { return _sortAsc; } /** Sets the ascending sorter, or null for no sorter for * the ascending order. * * @param sorter the comparator used to sort the ascending order. * If you are using the group feature, you can pass an instance of * {@link GroupComparator} to have a better control. * If an instance of {@link GroupComparator} is passed, * {@link GroupComparator#compareGroup} is used to group elements, * and {@link GroupComparator#compare} is used to sort elements * with a group. * Otherwise, {@link Comparator#compare} is used to group elements * and sort elements within a group. */ public void setSortAscending(Comparator sorter) { if (!Objects.equals(_sortAsc, sorter)) { _sortAsc = sorter; _isCustomAscComparator = _sortAsc != null; String nm = _isCustomAscComparator ? "fromServer" : "none"; if (!_sortAscNm.equals(nm)) { _sortAscNm = nm; smartUpdate("sortAscending", _sortAscNm); } } } /** Sets the ascending sorter with the class name, or null for * no sorter for the ascending order. */ public void setSortAscending(String clsnm) throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (!Strings.isBlank(clsnm) && clsnm.startsWith("client") && !_sortAscNm.equals(clsnm)) { _sortAscNm = clsnm; smartUpdate("sortAscending", clsnm); } else setSortAscending(toComparator(clsnm)); } /** Returns the descending sorter, or null if not available. */ public Comparator getSortDescending() { return _sortDsc; } /** Sets the descending sorter, or null for no sorter for the * descending order. * * @param sorter the comparator used to sort the descending order. * If you are using the group feature, you can pass an instance of * {@link GroupComparator} to have a better control. * If an instance of {@link GroupComparator} is passed, * {@link GroupComparator#compareGroup} is used to group elements, * and {@link GroupComparator#compare} is used to sort elements * with a group. * Otherwise, {@link Comparator#compare} is used to group elements * and sort elements within a group. */ public void setSortDescending(Comparator sorter) { if (!Objects.equals(_sortDsc, sorter)) { _sortDsc = sorter; _isCustomDscComparator = _sortDsc != null; String nm = _isCustomDscComparator ? "fromServer" : "none"; if (!_sortDscNm.equals(nm)) { _sortDscNm = nm; smartUpdate("sortDescending", _sortDscNm); } } } /** Sets the descending sorter with the class name, or null for * no sorter for the descending order. */ public void setSortDescending(String clsnm) throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (!Strings.isBlank(clsnm) && clsnm.startsWith("client") && !_sortDscNm.equals(clsnm)) { _sortDscNm = clsnm; smartUpdate("sortDescending", clsnm); } else setSortDescending(toComparator(clsnm)); } private Comparator toComparator(String clsnm) throws ClassNotFoundException, InstantiationException, IllegalAccessException { if (clsnm == null || clsnm.length() == 0) return null; final Page page = getPage(); final Class cls = page != null ? page.resolveClass(clsnm) : Classes.forNameByThread(clsnm); if (cls == null) throw new ClassNotFoundException(clsnm); if (!Comparator.class.isAssignableFrom(cls)) throw new UiException("Comparator must be implemented: " + clsnm); return (Comparator) cls.newInstance(); } /** Sorts the rows ({@link Row}) based on {@link #getSortAscending} * and {@link #getSortDescending}, if {@link #getSortDirection} doesn't * matches the ascending argument. * *

It checks {@link #getSortDirection} to see whether sorting * is required, and update {@link #setSortDirection} after sorted. * For example, if {@link #getSortDirection} returns "ascending" and * the ascending argument is false, nothing happens. * To enforce the sorting, you can invoke {@link #setSortDirection} * with "natural" before invoking this method. * Alternatively, you can invoke {@link #sort(boolean, boolean)} instead. * *

It sorts the rows by use of {@link Components#sort}, if not live * data (i.e., {@link Grid#getModel} is null). * *

On the other hand, it invokes {@link Sortable#sort} to sort * the rows, if live data (i.e., {@link Grid#getModel} is not null). * In other words, if you use the live data, you have to implement * {@link Sortable} to sort the live data explicitly. * * @param ascending whether to use {@link #getSortAscending}. * If the corresponding comparator is not set, it returns false * and does nothing. * @return whether the rows are sorted. * @exception UiException if {@link Grid#getModel} is not * null but {@link Sortable} is not implemented. */ public boolean sort(boolean ascending) { final String dir = getSortDirection(); if (ascending) { if ("ascending".equals(dir)) return false; } else { if ("descending".equals(dir)) return false; } return doSort(ascending); } /** Sorts the rows ({@link Row}) based on {@link #getSortAscending} * and {@link #getSortDescending}. * * @param ascending whether to use {@link #getSortAscending}. * If the corresponding comparator is not set, it returns false * and does nothing. * @param force whether to enforce the sorting no matter what the sort * direction ({@link #getSortDirection}) is. * If false, this method is the same as {@link #sort(boolean)}. * @return whether the rows are sorted. */ public boolean sort(boolean ascending, boolean force) { if (force) setSortDirection("natural"); return sort(ascending); } /*package*/ boolean doSort(boolean ascending) { final Comparator cmpr = ascending ? _sortAsc : _sortDsc; if (cmpr == null) return false; final Grid grid = getGrid(); if (grid == null) return false; final Rows rows = grid.getRows(); if (rows == null) return false; //comparator might be zscript Scopes.beforeInterpret(this); try { final ListModel model = grid.getModel(); boolean isPagingMold = grid.inPagingMold(); int activePg = isPagingMold ? grid.getPaginal().getActivePage() : 0; if (model != null) { //live data if (model instanceof GroupsSortableModel) { sortGroupsModel(grid, (GroupsSortableModel) model, cmpr, ascending); } else { if (!(model instanceof Sortable)) throw new UiException("Sortable must be implemented in " + model.getClass()); sortListModel((Sortable) model, cmpr, ascending); } } else { //not live data sort0(grid, cmpr); } if (isPagingMold) grid.getPaginal().setActivePage(activePg); // Because of maintaining the number of the visible item, we cause // the wrong active page when dynamically add/remove the item (i.e. sorting). // Therefore, we have to reset the correct active page. } finally { Scopes.afterInterpret(); } fixDirection(grid, ascending); // sometimes the items at client side are out of date grid.getRows().invalidate(); return true; } @SuppressWarnings("unchecked") private void sortGroupsModel(Grid grid, GroupsSortableModel model, Comparator cmpr, boolean ascending) { model.sort(cmpr, ascending, grid.getColumns().getChildren().indexOf(this)); } @SuppressWarnings("unchecked") private void sortListModel(Sortable model, Comparator cmpr, boolean ascending) { model.sort(cmpr, ascending); } /** Sorts the rows. If with group, each group is sorted independently. */ @SuppressWarnings("unchecked") private static void sort0(Grid grid, Comparator cmpr) { final Rows rows = grid.getRows(); if (rows.hasGroup()) for (Group g : rows.getGroups()) { int index = g.getIndex() + 1; Components.sort(rows.getChildren(), index, index + g.getItemCount(), cmpr); } else Components.sort(rows.getChildren(), cmpr); } private void fixDirection(Grid grid, boolean ascending) { _ignoreSort = true; //maintain for (Iterator it = grid.getColumns().getChildren().iterator(); it.hasNext();) { final Column hd = (Column) it.next(); hd.setSortDirection(hd != this ? "natural" : ascending ? "ascending" : "descending"); } _ignoreSort = false; } /** * Groups and sorts the rows ({@link Row}) based on * {@link #getSortAscending}. * If the corresponding comparator is not set, it returns false * and does nothing. * * @param ascending whether to use {@link #getSortAscending}. * If the corresponding comparator is not set, it returns false * and does nothing. * @return whether the rows are grouped. * @since 3.5.0 */ public boolean group(boolean ascending) { final String dir = getSortDirection(); if (ascending) { if ("ascending".equals(dir)) return false; } else { if ("descending".equals(dir)) return false; } final Comparator cmpr = ascending ? _sortAsc : _sortDsc; if (cmpr == null) return false; final Grid grid = getGrid(); if (grid == null) return false; //comparator might be zscript Scopes.beforeInterpret(this); try { final ListModel model = grid.getModel(); int index = grid.getColumns().getChildren().indexOf(this); if (model != null) { //live data if (!(model instanceof GroupsSortableModel)) throw new UiException( GroupsSortableModel.class + " must be implemented in " + model.getClass().getName()); groupGroupsModel((GroupsSortableModel) model, cmpr, ascending, index); } else { // not live data final Rows rows = grid.getRows(); if (rows == null) return false; //Avoid grid with null group if (rows.hasGroup()) { for (Group group : new ArrayList(rows.getGroups())) group.detach(); // Groupfoot is removed automatically, if any. } Comparator cmprx; if (cmpr instanceof GroupComparator) { cmprx = new GroupToComparator((GroupComparator) cmpr); } else { cmprx = cmpr; } final List children = new LinkedList(rows.getChildren()); rows.getChildren().clear(); sortCollection(children, cmprx); Row previous = null; for (Iterator it = children.iterator(); it.hasNext();) { final Row row = (Row) it.next(); it.remove(); if (previous == null || compare(cmprx, previous, row) != 0) { //new group final List cells = row.getChildren(); if (cells.size() < index) throw new IndexOutOfBoundsException("Index: " + index + " but size: " + cells.size()); Group group; Component cell = cells.get(index); if (cell instanceof Label) { String val = ((Label) cell).getValue(); group = new Group(val); } else { Component cc = cell.getFirstChild(); if (cc instanceof Label) { String val = ((Label) cc).getValue(); group = new Group(val); } else { group = new Group(Messages.get(MZul.GRID_OTHER)); } } rows.appendChild(group); } rows.appendChild(row); previous = row; } if (cmprx != cmpr) sort0(grid, cmpr); //need to sort each group } } finally { Scopes.afterInterpret(); } fixDirection(grid, ascending); // sometimes the items at client side are out of date grid.getRows().invalidate(); return true; } @SuppressWarnings("unchecked") private void groupGroupsModel(GroupsSortableModel model, Comparator cmpr, boolean ascending, int index) { model.group(cmpr, ascending, index); } @SuppressWarnings("unchecked") private static void sortCollection(List comps, Comparator cmpr) { Collections.sort(comps, cmpr); } @SuppressWarnings("unchecked") private static int compare(Comparator cmpr, Object a, Object b) { return cmpr.compare(a, b); } // super protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) throws java.io.IOException { super.renderProperties(renderer); if (!"none".equals(_sortDscNm)) render(renderer, "sortDescending", _sortDscNm); if (!"none".equals(_sortAscNm)) render(renderer, "sortAscending", _sortAscNm); if (!"natural".equals(_sortDir)) render(renderer, "sortDirection", _sortDir); org.zkoss.zul.impl.Utils.renderCrawlableText(getLabel()); } /** Returns the value. *

Default: null. *

Note: the value is application dependent, you can place * whatever value you want. * @since 3.6.0 */ @SuppressWarnings("unchecked") public T getValue() { return (T) _value; } /** Sets the value. * @param value the value. *

Note: the value is application dependent, you can place * whatever value you want. * @since 3.6.0 */ public void setValue(T value) { _value = value; } //-- event listener --// /** * Invokes a sorting action based on a {@link SortEvent} and maintains * {@link #getSortDirection}. * @since 5.0.8 */ public void onSort(SortEvent event) { sort(event.isAscending()); } /** It invokes {@link #sort(boolean)} to sort list items and maintain * {@link #getSortDirection}. * @deprecated As of release 5.0.8, use or override {@link #onSort(SortEvent)} * instead. */ public void onSort() { final String dir = getSortDirection(); if ("ascending".equals(dir)) sort(false); else if ("descending".equals(dir)) sort(true); else if (!sort(true)) sort(false); } /** * Internal use only. * @since 6.5.0 */ public void onGroupLater(SortEvent event) { group(event.isAscending()); } /** * Ungroups and sorts the rows ({@link Row}) based on the ascending. * If the corresponding comparator is not set, it returns false * and does nothing. * * @param ascending whether to use {@link #getSortAscending}. * If the corresponding comparator is not set, it returns false * and does nothing. * @since 6.5.0 */ public void ungroup(boolean ascending) { final Comparator cmpr = ascending ? _sortAsc : _sortDsc; if (cmpr != null) { final Grid grid = getGrid(); if (grid.getModel() == null) { // comparator might be zscript Scopes.beforeInterpret(this); try { final Rows rows = grid.getRows(); if (rows != null) { if (rows.hasGroup()) { for (Group group : new ArrayList(rows.getGroups())) group.detach(); // Groupfoot is removed // automatically, if any. } Comparator cmprx; if (cmpr instanceof GroupComparator) { cmprx = new GroupToComparator((GroupComparator) cmpr); } else { cmprx = cmpr; } final List children = new LinkedList(rows.getChildren()); rows.getChildren().clear(); sortCollection(children, cmprx); for (Component c : children) rows.appendChild(c); } } finally { Scopes.afterInterpret(); } } fixDirection(grid, ascending); // sometimes the items at client side are out of date grid.getRows().invalidate(); } } public String getZclass() { return _zclass == null ? "z-column" : _zclass; } //-- Component --// public void beforeParentChanged(Component parent) { if (parent != null && !(parent instanceof Columns)) throw new UiException("Unsupported parent for column: " + parent); super.beforeParentChanged(parent); } /** Processes an AU request. *

Default: in addition to what are handled by its superclass, it also * handles onSort. * @since 5.0.8 */ public void service(org.zkoss.zk.au.AuRequest request, boolean everError) { final String cmd = request.getCommand(); if (cmd.equals(Events.ON_SORT)) { SortEvent evt = SortEvent.getSortEvent(request); Events.postEvent(evt); } else if (cmd.equals(Events.ON_GROUP)) { final Map data = request.getData(); final boolean ascending = AuRequests.getBoolean(data, ""); Events.postEvent(new SortEvent(cmd, this, ascending)); // internal use, and it should be invoked after onGroup event. Events.postEvent(-1000, new SortEvent("onGroupLater", this, ascending)); } else if (cmd.equals(Events.ON_UNGROUP)) { final Map data = request.getData(); final boolean ascending = AuRequests.getBoolean(data, ""); ungroup(ascending); Events.postEvent(new SortEvent(cmd, request.getComponent(), ascending)); } else super.service(request, everError); } //Cloneable// public Object clone() { final Column clone = (Column) super.clone(); clone.fixClone(); return clone; } private void fixClone() { if (_sortAsc instanceof RowComparator) { final RowComparator c = (RowComparator) _sortAsc; if (c.getColumn() == this && c.isAscending()) _sortAsc = new RowComparator(this, true, c.shallIgnoreCase(), false); } if (_sortDsc instanceof RowComparator) { final RowComparator c = (RowComparator) _sortDsc; if (c.getColumn() == this && !c.isAscending()) _sortDsc = new RowComparator(this, false, c.shallIgnoreCase(), false); } } //Serializable// //NOTE: they must be declared as private private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); boolean written = false; if (_sortAsc instanceof RowComparator) { final RowComparator c = (RowComparator) _sortAsc; if (c.getColumn() == this && c.isAscending()) { s.writeBoolean(true); s.writeBoolean(c.shallIgnoreCase()); written = true; } } if (!written) { s.writeBoolean(false); s.writeObject(_sortAsc); } written = false; if (_sortDsc instanceof RowComparator) { final RowComparator c = (RowComparator) _sortDsc; if (c.getColumn() == this && !c.isAscending()) { s.writeBoolean(true); s.writeBoolean(c.shallIgnoreCase()); written = true; } } if (!written) { s.writeBoolean(false); s.writeObject(_sortDsc); } } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); boolean b = s.readBoolean(); if (b) { final boolean igcs = s.readBoolean(); _sortAsc = new RowComparator(this, true, igcs, false); } else { //bug #2830325 FieldComparator not castable to ListItemComparator _sortAsc = (Comparator) s.readObject(); } b = s.readBoolean(); if (b) { final boolean igcs = s.readBoolean(); _sortDsc = new RowComparator(this, false, igcs, false); } else { //bug #2830325 FieldComparator not castable to ListItemComparator _sortDsc = (Comparator) s.readObject(); } } private static class GroupToComparator implements Comparator { private final GroupComparator _gcmpr; private GroupToComparator(GroupComparator gcmpr) { _gcmpr = gcmpr; } @SuppressWarnings("unchecked") public int compare(Object o1, Object o2) { return _gcmpr.compareGroup(o1, o2); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy