/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.web.common.components; import org.libreplan.web.common.Util; import org.zkoss.lang.Objects; import org.zkoss.zk.ui.HtmlMacroComponent; import org.zkoss.zul.Listbox; import org.zkoss.zul.Listcell; import org.zkoss.zul.Listitem; import org.zkoss.zul.ListitemRenderer; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.HashMap; import java.util.Set; import java.util.Arrays; import static org.libreplan.web.I18nHelper._; /** * ZK macro component that shows two {@link Listbox} allowing to move objects * between each other. * * In the {@link Listbox} on the left you will have the assigned objects and in * the right the possible other objects to be assigned. * * Finally it provides methods to get the current assigned and unassigned * objects. * * @author Manuel Rego Casasnovas <mrego@igalia.com> */ public class TwoWaySelector extends HtmlMacroComponent { /** * A {@link Set} of objects that are assigned (so they're shown on the left * {@link Listbox}) */ private Set<Object> assignedObjects = new HashSet<Object>(); /** * Title for the left {@link Listbox} (where assigned objects are shown) */ private String assignedTitle = _("Assigned"); /** * A {@link Set} of objects that are not assigned (so they're shown on the * right {@link Listbox}) */ private Set unassignedObjects = new HashSet(); /** * Title for the right {@link Listbox} (where unassigned objects are shown) */ private String unassignedTitle = _("Unassigned"); /** * A {@link List} of properties to be shown on the {@link Listbox} for each * object. */ private List<String> columns = null; /** * {@link ListitemRenderer} that knows how to paint an object according to * the {@link List} stored in the columns attribute. If columns is null then * the object will be rendered as a string. * * @author Manuel Rego Casasnovas <mrego@igalia.com> */ private transient ListitemRenderer renderer = new ListitemRenderer() { @Override public void render(Listitem item, Object data, int i) throws Exception { Class<?> klass = data.getClass(); Map<String, PropertyDescriptor> propertiesByName = getProperties(klass); // If a list of attributes is defined if (columns != null) { // For each attribute for (String column : columns) { // Call the method to get the information PropertyDescriptor propertyDescriptor = propertiesByName.get(column); if (propertyDescriptor == null) { throw new RuntimeException(_("Unknown attribute '{0}' in class {1}", column, klass.getName())); } String label = Objects.toString(propertyDescriptor.getReadMethod().invoke(data)); // Add a new Listcell item.appendChild(new Listcell(label)); } } else { // If the list of attributes is not defined // Render the object as string item.setLabel(Objects.toString(data)); } item.setValue(data); } /** * A {@link Map} that stores the information about the attributes for a * class. * * The information about attributes is stored with another Map where * keys are the properties name and the values the * {@link PropertyDescriptor}. */ private Map<Class<?>, Map<String, PropertyDescriptor>> propertiesMapsCached = new HashMap<Class<?>, Map<String, PropertyDescriptor>>(); /** * Creates a {@link Map} that relates the properties and their * {@link PropertyDescriptor} from the {@link BeanInfo}. * * @param info * Information about the bean * @return A {@link Map} that relates properties name and * {@link PropertyDescriptor} */ private Map<String, PropertyDescriptor> buildPropertyDescriptorsMap(BeanInfo info) { PropertyDescriptor[] propertyDescriptors = info.getPropertyDescriptors(); Map<String, PropertyDescriptor> propertiesByName = new HashMap<>(); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { propertiesByName.put(propertyDescriptor.getName(), propertyDescriptor); } return propertiesByName; } /** * Gets the attributes of a {@link Class} together with the * {@link PropertyDescriptor} of each property. * * @param klass * The {@link Class} to get the properties * @return A {@link Map} that relates properties name and * {@link PropertyDescriptor} * @throws IntrospectionException */ private Map<String, PropertyDescriptor> getProperties(Class<?> klass) throws IntrospectionException { // If it's already cached if (propertiesMapsCached.containsKey(klass)) { return propertiesMapsCached.get(klass); } BeanInfo beanInfo = Introspector.getBeanInfo(klass); Map<String, PropertyDescriptor> result = buildPropertyDescriptorsMap(beanInfo); // Store in cache propertiesMapsCached.put(klass, result); return result; } }; public void setAssignedTitle(String assignedTitle) { if (assignedTitle != null) { this.assignedTitle = assignedTitle; } } public String getAssignedTitle() { return assignedTitle; } public void setUnassignedTitle(String unassignedTitle) { if (unassignedTitle != null) { this.unassignedTitle = unassignedTitle; } } public String getUnassignedTitle() { return unassignedTitle; } public void setAssignedObjects(Set<Object> assignedObjects) { if (assignedObjects != null) { this.assignedObjects = assignedObjects; } } public Set<Object> getAssignedObjects() { return assignedObjects; } public void setUnassignedObjects(Set unassignedObjects) { if (assignedObjects != null) { this.unassignedObjects = unassignedObjects; } } public Set getUnassignedObjects() { return unassignedObjects; } /** * Sets the list of attributes to be shown when an object is renderer. * * @param columns * A comma-separated string */ public void setColumns(String columns) { if (columns != null) { // Remove white spaces columns = columns.replaceAll("\\s", ""); if (!columns.isEmpty()) { // Split the string this.columns = Arrays.asList(columns.split(",")); } } } public List<String> getColumns() { return columns; } public ListitemRenderer getRenderer() { return renderer; } /** * Assign (move to the left {@link Listbox}) the selected items from the * right {@link Listbox}. And reload both {@link Listbox} in order to * relfect the changes. * * @param unassignedObjectsListbox * The right {@link Listbox} */ public void assign(Listbox unassignedObjectsListbox) { Set<Listitem> selectedItems = unassignedObjectsListbox .getSelectedItems(); for (Listitem listitem : selectedItems) { Object value = listitem.getValue(); unassignedObjects.remove(value); assignedObjects.add(value); } Util.reloadBindings(unassignedObjectsListbox.getParent()); Util.saveBindings(this); } /** * Unassign (move to the rigth {@link Listbox}) the selected items from the * left {@link Listbox}. And reload both {@link Listbox} in order to relfect * the changes. * * @param assignedObjectsListbox * The left {@link Listbox} */ public void unassign(Listbox assignedObjectsListbox) { Set<Listitem> selectedItems = assignedObjectsListbox.getSelectedItems(); for (Listitem listitem : selectedItems) { Object value = listitem.getValue(); assignedObjects.remove(value); unassignedObjects.add(value); } Util.reloadBindings(assignedObjectsListbox.getParent()); Util.saveBindings(this); } }