View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.container;
17  
18  import org.kuali.rice.core.api.util.tree.Node;
19  import org.kuali.rice.core.api.util.tree.Tree;
20  import org.kuali.rice.krad.uif.UifConstants;
21  import org.kuali.rice.krad.uif.component.BindingInfo;
22  import org.kuali.rice.krad.uif.component.Component;
23  import org.kuali.rice.krad.uif.component.DataBinding;
24  import org.kuali.rice.krad.uif.field.MessageField;
25  import org.kuali.rice.krad.uif.util.ComponentUtils;
26  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
27  import org.kuali.rice.krad.uif.view.View;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  import java.util.Map;
32  
33  /**
34   * Group component that is backed by a <code>Tree</code> data structure and typically
35   * rendered as a tree in the user interface
36   *
37   * @author Kuali Rice Team (rice.collab@kuali.org)
38   */
39  public class TreeGroup extends Group implements DataBinding{
40      private static final long serialVersionUID = 5841343037089286740L;
41  
42      private String propertyName;
43      private BindingInfo bindingInfo;
44  
45      private Map<Class<?>, NodePrototype> nodePrototypeMap;
46      private NodePrototype defaultNodePrototype;
47  
48      private Tree<Group, MessageField> treeGroups;
49  
50      private org.kuali.rice.krad.uif.widget.Tree tree;
51  
52      public TreeGroup() {
53          super();
54  
55          treeGroups = new Tree<Group, MessageField>();
56      }
57  
58      /**
59       * The following actions are performed:
60       *
61       * <ul>
62       * <li>Set fieldBindModelPath to the collection model path (since the fields
63       * have to belong to the same model as the collection)</li>
64       * <li>Set defaults for binding</li>
65       * <li>Calls view helper service to initialize prototypes</li>
66       * </ul>
67       *
68       */
69      @Override
70      public void performInitialization(View view, Object model) {
71          setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
72  
73          super.performInitialization(view, model);
74  
75          if (bindingInfo != null) {
76              bindingInfo.setDefaults(view, getPropertyName());
77          }
78  
79          // TODO: set object path for prototypes equal to the tree group object path?
80  
81          initializeNodePrototypeComponents(view, model);
82      }
83  
84      protected void initializeNodePrototypeComponents(View view, Object model) {
85          view.getViewHelperService().performComponentInitialization(view, model,
86                  defaultNodePrototype.getLabelPrototype());
87          view.getViewHelperService().performComponentInitialization(view, model,
88                  defaultNodePrototype.getDataGroupPrototype());
89  
90          if (nodePrototypeMap != null) {
91              for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
92                  NodePrototype prototype = prototypeEntry.getValue();
93                  if (prototype != null) {
94  
95                      if (prototype.getLabelPrototype() != null) {
96                          view.getViewHelperService().performComponentInitialization(view, model,
97                                  prototype.getLabelPrototype());
98                      } else {
99                          throw new IllegalStateException("encountered null NodePrototype.labelPrototype");
100                     }
101 
102                     if (prototype.getDataGroupPrototype() != null) {
103                         view.getViewHelperService().performComponentInitialization(view, model,
104                                 prototype.getDataGroupPrototype());
105                     } else {
106                         throw new IllegalStateException("encountered null NodePrototype.dataGroupPrototype");
107                     }
108                 } else {
109                     throw new IllegalStateException("encountered null NodePrototype");
110                 }
111             }
112         }
113     }
114 
115     @Override
116     public void performApplyModel(View view, Object model, Component parent) {
117         super.performApplyModel(view, model, parent);
118 
119         buildTreeGroups(view, model);
120     }
121 
122     /**
123      * Builds the components that will be rendered as part of the tree group
124      *
125      * <p>
126      * The component tree group mirrors the tree data structure on the model. For each node of
127      * the data structure, a corresponding <code>MessageField</code>  will be created for the node
128      * label, and a <code>Group</code> component for the node data. These are placed into a new
129      * node for the component tree. After the tree is built it is set as a property on the tree group
130      * to be read by the renderer
131      * </p>
132      *
133      * @param view - view instance the tree group belongs to
134      * @param model - object containing the view data from which the tree data will be retrieved
135      */
136     protected void buildTreeGroups(View view, Object model) {
137         // get Tree data property
138         Tree<Object, String> treeData = ObjectPropertyUtils.getPropertyValue(model, getBindingInfo().getBindingPath());
139 
140         // build component tree that corresponds with tree data
141         Tree<Group, MessageField> treeGroups = new Tree<Group, MessageField>();
142 
143         String bindingPrefix = getBindingInfo().getBindingPrefixForNested();
144         Node<Group, MessageField> rootNode =
145                 buildTreeNode(treeData.getRootElement(), bindingPrefix + /* TODO: hack */ ".rootElement", "root");
146         treeGroups.setRootElement(rootNode);
147 
148         setTreeGroups(treeGroups);
149     }
150 
151     protected Node<Group, MessageField> buildTreeNode(Node<Object, String> nodeData, String bindingPrefix,
152             String parentNode) {
153         if (nodeData == null) {
154             return null;
155         }
156 
157         Node<Group, MessageField> node = new Node<Group, MessageField>();
158         node.setNodeType(nodeData.getNodeType());
159 
160         NodePrototype prototype = getNodePrototype(nodeData);
161 
162         MessageField messageField = ComponentUtils.copy(prototype.getLabelPrototype(), parentNode);
163         ComponentUtils.pushObjectToContext(messageField, UifConstants.ContextVariableNames.NODE, nodeData);
164         messageField.setMessageText(nodeData.getNodeLabel());
165         node.setNodeLabel(messageField);
166 
167         Group nodeGroup =
168                 ComponentUtils.copyComponent(prototype.getDataGroupPrototype(), bindingPrefix + ".data", parentNode);
169         ComponentUtils.pushObjectToContext(nodeGroup, UifConstants.ContextVariableNames.NODE, nodeData);
170         node.setData(nodeGroup);
171 
172         List<Node<Group, MessageField>> nodeChildren = new ArrayList<Node<Group, MessageField>>();
173 
174         int childIndex = 0;
175         for (Node<Object, String> childDataNode : nodeData.getChildren()) {
176             String nextBindingPrefix = bindingPrefix + ".children[" + childIndex + "]";
177             Node<Group, MessageField> childNode = buildTreeNode(childDataNode, nextBindingPrefix, "_node_" + childIndex + ("root".equals(parentNode) ? "_parent_" : "_parent") + parentNode);
178 
179             nodeChildren.add(childNode);
180 
181             // Don't forget about me:
182             ++childIndex;
183         }
184         node.setChildren(nodeChildren);
185 
186         return node;
187     }
188 
189     /**
190      * This method gets the NodePrototype to use for the given Node
191      */
192     private NodePrototype getNodePrototype(Node<Object, String> nodeData) {
193         NodePrototype result = null;
194         if (nodeData != null && nodeData.getData() != null) {
195             Class<?> dataClass = nodeData.getData().getClass();
196             result = nodePrototypeMap.get(dataClass);
197 
198             // somewhat lame fallback - to do this right we'd find all entries that are assignable from the data class
199             // and then figure out which one is the closest relative
200             if (result == null)
201                 for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
202                     if (prototypeEntry.getKey().isAssignableFrom(dataClass)) {
203                         result = prototypeEntry.getValue();
204                         break;
205                     }
206                 }
207         }
208         if (result == null)
209             result = defaultNodePrototype;
210         return result;
211     }
212 
213     /**
214      * @see org.kuali.rice.krad.uif.component.Component#getComponentsForLifecycle()
215      */
216     @Override
217     public List<Component> getComponentsForLifecycle() {
218         List<Component> components = super.getComponentsForLifecycle();
219 
220         components.add(tree);
221         addNodeComponents(treeGroups.getRootElement(), components);
222 
223         return components;
224     }
225 
226     /**
227      * @see org.kuali.rice.krad.uif.component.Component#getComponentPrototypes()
228      */
229     @Override
230     public List<Component> getComponentPrototypes() {
231         List<Component> components = super.getComponentPrototypes();
232 
233         if (defaultNodePrototype != null) {
234             components.add(defaultNodePrototype.getLabelPrototype());
235             components.add(defaultNodePrototype.getDataGroupPrototype());
236         }
237 
238         if (nodePrototypeMap != null) {
239             for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
240                 NodePrototype prototype = prototypeEntry.getValue();
241                 if (prototype != null) {
242                     components.add(prototype.getLabelPrototype());
243                     components.add(prototype.getDataGroupPrototype());
244                 }
245             }
246         }
247 
248         return components;
249     }
250 
251     /**
252      * Retrieves the <code>Component</code> instances from the node for building the nested
253      * components list
254      *
255      * @param node - node to pull components from
256      * @param components - list to add components to
257      */
258     protected void addNodeComponents(Node<Group, MessageField> node, List<Component> components) {
259         if (node != null) {
260             components.add(node.getNodeLabel());
261             components.add(node.getData());
262 
263             for (Node<Group, MessageField> nodeChild : node.getChildren()) {
264                 addNodeComponents(nodeChild, components);
265             }
266         }
267     }
268 
269     public String getPropertyName() {
270         return propertyName;
271     }
272 
273     public void setPropertyName(String propertyName) {
274         this.propertyName = propertyName;
275     }
276 
277     public BindingInfo getBindingInfo() {
278         return bindingInfo;
279     }
280 
281     public void setBindingInfo(BindingInfo bindingInfo) {
282         this.bindingInfo = bindingInfo;
283     }
284 
285     /**
286      * @return the defaultNodePrototype
287      */
288     public NodePrototype getDefaultNodePrototype() {
289         return this.defaultNodePrototype;
290     }
291 
292     /**
293      * @param defaultNodePrototype the defaultNodePrototype to set
294      */
295     public void setDefaultNodePrototype(NodePrototype defaultNodePrototype) {
296         this.defaultNodePrototype = defaultNodePrototype;
297     }
298 
299     /**
300      * @return the nodePrototypeMap
301      */
302     public Map<Class<?>, NodePrototype> getNodePrototypeMap() {
303         return this.nodePrototypeMap;
304     }
305 
306     /**
307      * @param nodePrototypeMap the nodePrototypeMap to set
308      */
309     public void setNodePrototypeMap(Map<Class<?>, NodePrototype> nodePrototypeMap) {
310         this.nodePrototypeMap = nodePrototypeMap;
311     }
312 
313     public Tree<Group, MessageField> getTreeGroups() {
314         return treeGroups;
315     }
316 
317     public void setTreeGroups(Tree<Group, MessageField> treeGroups) {
318         this.treeGroups = treeGroups;
319     }
320 
321     public org.kuali.rice.krad.uif.widget.Tree getTree() {
322         return tree;
323     }
324 
325     public void setTree(org.kuali.rice.krad.uif.widget.Tree tree) {
326         this.tree = tree;
327     }
328 }