001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.uif.container;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.kuali.rice.core.api.util.tree.Node;
020 import org.kuali.rice.core.api.util.tree.Tree;
021 import org.kuali.rice.krad.uif.UifConstants;
022 import org.kuali.rice.krad.uif.component.BindingInfo;
023 import org.kuali.rice.krad.uif.component.Component;
024 import org.kuali.rice.krad.uif.component.DataBinding;
025 import org.kuali.rice.krad.uif.field.MessageField;
026 import org.kuali.rice.krad.uif.util.ComponentUtils;
027 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
028 import org.kuali.rice.krad.uif.view.View;
029
030 import java.util.ArrayList;
031 import java.util.List;
032 import java.util.Map;
033
034 /**
035 * Group component that is backed by a <code>Tree</code> data structure and typically
036 * rendered as a tree in the user interface
037 *
038 * @author Kuali Rice Team (rice.collab@kuali.org)
039 */
040 public class TreeGroup extends Group implements DataBinding{
041 private static final long serialVersionUID = 5841343037089286740L;
042
043 private String propertyName;
044 private BindingInfo bindingInfo;
045
046 private Map<Class<?>, NodePrototype> nodePrototypeMap;
047 private NodePrototype defaultNodePrototype;
048
049 private Tree<Group, MessageField> treeGroups;
050
051 private org.kuali.rice.krad.uif.widget.Tree tree;
052
053 public TreeGroup() {
054 super();
055
056 treeGroups = new Tree<Group, MessageField>();
057 }
058
059 /**
060 * The following actions are performed:
061 *
062 * <ul>
063 * <li>Set fieldBindModelPath to the collection model path (since the fields
064 * have to belong to the same model as the collection)</li>
065 * <li>Set defaults for binding</li>
066 * <li>Calls view helper service to initialize prototypes</li>
067 * </ul>
068 *
069 */
070 @Override
071 public void performInitialization(View view, Object model) {
072 setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
073
074 super.performInitialization(view, model);
075
076 if (bindingInfo != null) {
077 bindingInfo.setDefaults(view, getPropertyName());
078 }
079
080 // TODO: set object path for prototypes equal to the tree group object path?
081
082 initializeNodePrototypeComponents(view, model);
083 }
084
085 protected void initializeNodePrototypeComponents(View view, Object model) {
086 view.getViewHelperService().performComponentInitialization(view, model,
087 defaultNodePrototype.getLabelPrototype());
088 view.getViewHelperService().performComponentInitialization(view, model,
089 defaultNodePrototype.getDataGroupPrototype());
090
091 if (nodePrototypeMap != null) {
092 for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
093 NodePrototype prototype = prototypeEntry.getValue();
094 if (prototype != null) {
095
096 if (prototype.getLabelPrototype() != null) {
097 view.getViewHelperService().performComponentInitialization(view, model,
098 prototype.getLabelPrototype());
099 } else {
100 throw new IllegalStateException("encountered null NodePrototype.labelPrototype");
101 }
102
103 if (prototype.getDataGroupPrototype() != null) {
104 view.getViewHelperService().performComponentInitialization(view, model,
105 prototype.getDataGroupPrototype());
106 } else {
107 throw new IllegalStateException("encountered null NodePrototype.dataGroupPrototype");
108 }
109 } else {
110 throw new IllegalStateException("encountered null NodePrototype");
111 }
112 }
113 }
114 }
115
116 @Override
117 public void performApplyModel(View view, Object model, Component parent) {
118 super.performApplyModel(view, model, parent);
119
120 buildTreeGroups(view, model);
121 }
122
123 /**
124 * Builds the components that will be rendered as part of the tree group
125 *
126 * <p>
127 * The component tree group mirrors the tree data structure on the model. For each node of
128 * the data structure, a corresponding <code>MessageField</code> will be created for the node
129 * label, and a <code>Group</code> component for the node data. These are placed into a new
130 * node for the component tree. After the tree is built it is set as a property on the tree group
131 * to be read by the renderer
132 * </p>
133 *
134 * @param view - view instance the tree group belongs to
135 * @param model - object containing the view data from which the tree data will be retrieved
136 */
137 protected void buildTreeGroups(View view, Object model) {
138 // get Tree data property
139 Tree<Object, String> treeData = ObjectPropertyUtils.getPropertyValue(model, getBindingInfo().getBindingPath());
140
141 // build component tree that corresponds with tree data
142 Tree<Group, MessageField> treeGroups = new Tree<Group, MessageField>();
143
144 String bindingPrefix = getBindingInfo().getBindingPrefixForNested();
145 Node<Group, MessageField> rootNode =
146 buildTreeNode(treeData.getRootElement(), bindingPrefix + /* TODO: hack */ ".rootElement", "root");
147 treeGroups.setRootElement(rootNode);
148
149 setTreeGroups(treeGroups);
150 }
151
152 protected Node<Group, MessageField> buildTreeNode(Node<Object, String> nodeData, String bindingPrefix,
153 String parentNode) {
154 if (nodeData == null) {
155 return null;
156 }
157
158 Node<Group, MessageField> node = new Node<Group, MessageField>();
159 node.setNodeType(nodeData.getNodeType());
160
161 NodePrototype prototype = getNodePrototype(nodeData);
162
163 MessageField messageField = ComponentUtils.copy(prototype.getLabelPrototype(), parentNode);
164 ComponentUtils.pushObjectToContext(messageField, UifConstants.ContextVariableNames.NODE, nodeData);
165 messageField.setMessageText(nodeData.getNodeLabel());
166 node.setNodeLabel(messageField);
167
168 Group nodeGroup = ComponentUtils.copyComponent(prototype.getDataGroupPrototype(), bindingPrefix + ".data",
169 parentNode);
170 ComponentUtils.pushObjectToContext(nodeGroup, UifConstants.ContextVariableNames.NODE, nodeData);
171
172 String nodePath = bindingPrefix + ".data";
173 if (StringUtils.isNotBlank(getBindingInfo().getBindingObjectPath())) {
174 nodePath = getBindingInfo().getBindingObjectPath() + "." + nodePath;
175 }
176 ComponentUtils.pushObjectToContext(nodeGroup, UifConstants.ContextVariableNames.NODE_PATH, nodePath);
177 node.setData(nodeGroup);
178
179 List<Node<Group, MessageField>> nodeChildren = new ArrayList<Node<Group, MessageField>>();
180
181 int childIndex = 0;
182 for (Node<Object, String> childDataNode : nodeData.getChildren()) {
183 String nextBindingPrefix = bindingPrefix + ".children[" + childIndex + "]";
184 Node<Group, MessageField> childNode = buildTreeNode(childDataNode, nextBindingPrefix,
185 "_node_" + childIndex + ("root".equals(parentNode) ? "_parent_" : "_parent") + parentNode);
186
187 nodeChildren.add(childNode);
188
189 // Don't forget about me:
190 ++childIndex;
191 }
192 node.setChildren(nodeChildren);
193
194 return node;
195 }
196
197 /**
198 * Gets the NodePrototype to use for the given Node
199 */
200 private NodePrototype getNodePrototype(Node<Object, String> nodeData) {
201 NodePrototype result = null;
202 if (nodeData != null && nodeData.getData() != null) {
203 Class<?> dataClass = nodeData.getData().getClass();
204 result = nodePrototypeMap.get(dataClass);
205
206 // somewhat lame fallback - to do this right we'd find all entries that are assignable from the data class
207 // and then figure out which one is the closest relative
208 if (result == null) {
209 for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
210 if (prototypeEntry.getKey().isAssignableFrom(dataClass)) {
211 result = prototypeEntry.getValue();
212 break;
213 }
214 }
215 }
216 }
217
218 if (result == null) {
219 result = defaultNodePrototype;
220 }
221
222 return result;
223 }
224
225 /**
226 * @see org.kuali.rice.krad.uif.component.Component#getComponentsForLifecycle()
227 */
228 @Override
229 public List<Component> getComponentsForLifecycle() {
230 List<Component> components = super.getComponentsForLifecycle();
231
232 components.add(tree);
233 addNodeComponents(treeGroups.getRootElement(), components);
234
235 return components;
236 }
237
238 /**
239 * @see org.kuali.rice.krad.uif.component.Component#getComponentPrototypes()
240 */
241 @Override
242 public List<Component> getComponentPrototypes() {
243 List<Component> components = super.getComponentPrototypes();
244
245 if (defaultNodePrototype != null) {
246 components.add(defaultNodePrototype.getLabelPrototype());
247 components.add(defaultNodePrototype.getDataGroupPrototype());
248 }
249
250 if (nodePrototypeMap != null) {
251 for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
252 NodePrototype prototype = prototypeEntry.getValue();
253 if (prototype != null) {
254 components.add(prototype.getLabelPrototype());
255 components.add(prototype.getDataGroupPrototype());
256 }
257 }
258 }
259
260 return components;
261 }
262
263 /**
264 * Retrieves the <code>Component</code> instances from the node for building the nested
265 * components list
266 *
267 * @param node - node to pull components from
268 * @param components - list to add components to
269 */
270 protected void addNodeComponents(Node<Group, MessageField> node, List<Component> components) {
271 if (node != null) {
272 components.add(node.getNodeLabel());
273 components.add(node.getData());
274
275 for (Node<Group, MessageField> nodeChild : node.getChildren()) {
276 addNodeComponents(nodeChild, components);
277 }
278 }
279 }
280
281 public String getPropertyName() {
282 return propertyName;
283 }
284
285 public void setPropertyName(String propertyName) {
286 this.propertyName = propertyName;
287 }
288
289 public BindingInfo getBindingInfo() {
290 return bindingInfo;
291 }
292
293 public void setBindingInfo(BindingInfo bindingInfo) {
294 this.bindingInfo = bindingInfo;
295 }
296
297 /**
298 * @return the defaultNodePrototype
299 */
300 public NodePrototype getDefaultNodePrototype() {
301 return this.defaultNodePrototype;
302 }
303
304 /**
305 * @param defaultNodePrototype the defaultNodePrototype to set
306 */
307 public void setDefaultNodePrototype(NodePrototype defaultNodePrototype) {
308 this.defaultNodePrototype = defaultNodePrototype;
309 }
310
311 /**
312 * @return the nodePrototypeMap
313 */
314 public Map<Class<?>, NodePrototype> getNodePrototypeMap() {
315 return this.nodePrototypeMap;
316 }
317
318 /**
319 * @param nodePrototypeMap the nodePrototypeMap to set
320 */
321 public void setNodePrototypeMap(Map<Class<?>, NodePrototype> nodePrototypeMap) {
322 this.nodePrototypeMap = nodePrototypeMap;
323 }
324
325 public Tree<Group, MessageField> getTreeGroups() {
326 return treeGroups;
327 }
328
329 public void setTreeGroups(Tree<Group, MessageField> treeGroups) {
330 this.treeGroups = treeGroups;
331 }
332
333 public org.kuali.rice.krad.uif.widget.Tree getTree() {
334 return tree;
335 }
336
337 public void setTree(org.kuali.rice.krad.uif.widget.Tree tree) {
338 this.tree = tree;
339 }
340 }