From cdd856067682d1c000c514c1f607bb7a3faba1a3 Mon Sep 17 00:00:00 2001 From: Manuel Rego Casasnovas Date: Wed, 15 Jul 2009 16:34:00 +0200 Subject: [PATCH] ItEr17S12CUAltaTipoParteDeTraballo: Creating a macro component called TwoWaySelector. --- .../org/navalplanner/web/common/Util.java | 9 + .../web/common/components/TwoWaySelector.java | 268 ++++++++++++++++++ .../main/resources/metainfo/zk/lang-addon.xml | 14 + .../common/components/twowayselector.zul | 42 +++ 4 files changed, 333 insertions(+) create mode 100644 navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/TwoWaySelector.java create mode 100755 navalplanner-webapp/src/main/resources/metainfo/zk/lang-addon.xml create mode 100644 navalplanner-webapp/src/main/webapp/common/components/twowayselector.zul diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Util.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Util.java index 2107cce88..c6f6c2fe2 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Util.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/Util.java @@ -32,6 +32,15 @@ public class Util { } } + public static void saveBindings(Component... toReload) { + for (Component reload : toReload) { + DataBinder binder = Util.getBinder(reload); + if (binder != null) { + binder.saveComponent(reload); + } + } + } + public static DataBinder getBinder(Component component) { return (DataBinder) component.getVariable("binder", false); } diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/TwoWaySelector.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/TwoWaySelector.java new file mode 100644 index 000000000..d6c7e5f1a --- /dev/null +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/common/components/TwoWaySelector.java @@ -0,0 +1,268 @@ +package org.navalplanner.web.common.components; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.navalplanner.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; + +/** + * 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 + */ +public class TwoWaySelector extends HtmlMacroComponent { + + /** + * A {@link Set} of objects that are assigned (so they're shown on the left + * {@link Listbox}) + */ + private Set assignedObjects = new HashSet(); + + /** + * 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 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 + */ + private ListitemRenderer renderer = new ListitemRenderer() { + @Override + public void render(Listitem item, Object data) throws Exception { + + Class klass = data.getClass(); + Map 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 '" + + column + "' in class " + 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, Map> propertiesMapsCached = new HashMap, Map>(); + + /** + * 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 buildPropertyDescriptorsMap( + BeanInfo info) { + PropertyDescriptor[] propertyDescriptors = info + .getPropertyDescriptors(); + Map 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 getProperties( + Class klass) throws IntrospectionException { + // If it's already cached + if (propertiesMapsCached.containsKey(klass)) { + return propertiesMapsCached.get(klass); + } + + BeanInfo beanInfo = Introspector.getBeanInfo(klass); + Map 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 assignedObjects) { + if (assignedObjects != null) { + this.assignedObjects = assignedObjects; + } + } + + public Set 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 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 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 selectedItems = assignedObjectsListbox.getSelectedItems(); + for (Listitem listitem : selectedItems) { + Object value = listitem.getValue(); + assignedObjects.remove(value); + unassignedObjects.add(value); + } + Util.reloadBindings(assignedObjectsListbox.getParent()); + Util.saveBindings(this); + } + +} \ No newline at end of file diff --git a/navalplanner-webapp/src/main/resources/metainfo/zk/lang-addon.xml b/navalplanner-webapp/src/main/resources/metainfo/zk/lang-addon.xml new file mode 100755 index 000000000..ad28e0255 --- /dev/null +++ b/navalplanner-webapp/src/main/resources/metainfo/zk/lang-addon.xml @@ -0,0 +1,14 @@ + + + + + navalplanner-webapp + xul/html + + + twowayselector + org.navalplanner.web.common.components.TwoWaySelector + /common/components/twowayselector.zul + + + \ No newline at end of file diff --git a/navalplanner-webapp/src/main/webapp/common/components/twowayselector.zul b/navalplanner-webapp/src/main/webapp/common/components/twowayselector.zul new file mode 100644 index 000000000..f79bb275e --- /dev/null +++ b/navalplanner-webapp/src/main/webapp/common/components/twowayselector.zul @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + +