View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation Licensed under the Educational Community
3    * License, Version 1.0 (the "License"); you may not use this file except in
4    * compliance with the License. You may obtain a copy of the License at
5    * http://www.opensource.org/licenses/ecl1.php Unless required by applicable law
6    * or agreed to in writing, software distributed under the License is
7    * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8    * KIND, either express or implied. See the License for the specific language
9    * governing permissions and limitations under the License.
10   */
11  package org.kuali.rice.krad.uif.container;
12  
13  import org.apache.commons.lang.StringUtils;
14  import org.kuali.rice.core.api.mo.common.active.ImmutableInactivatable;
15  import org.kuali.rice.krad.uif.UifConstants;
16  import org.kuali.rice.krad.uif.UifParameters;
17  import org.kuali.rice.krad.uif.core.ActiveCollectionFilter;
18  import org.kuali.rice.krad.uif.core.BindingInfo;
19  import org.kuali.rice.krad.uif.core.CollectionFilter;
20  import org.kuali.rice.krad.uif.core.Component;
21  import org.kuali.rice.krad.uif.core.DataBinding;
22  import org.kuali.rice.krad.uif.field.ActionField;
23  import org.kuali.rice.krad.uif.field.AttributeField;
24  import org.kuali.rice.krad.uif.field.Field;
25  import org.kuali.rice.krad.uif.field.LabelField;
26  import org.kuali.rice.krad.uif.util.ComponentUtils;
27  
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  /**
32   * Group that holds a collection of objects and configuration for presenting the
33   * collection in the UI. Supports functionality such as add line, line actions,
34   * and nested collections.
35   * 
36   * <p>
37   * Note the standard header/footer can be used to give a header to the
38   * collection as a whole, or to provide actions that apply to the entire
39   * collection
40   * </p>
41   * 
42   * <p>
43   * For binding purposes the binding path of each row field is indexed. The name
44   * property inherited from <code>ComponentBase</code> is used as the collection
45   * name. The collectionObjectClass property is used to lookup attributes from
46   * the data dictionary.
47   * </p>
48   * 
49   * @author Kuali Rice Team (rice.collab@kuali.org)
50   */
51  public class CollectionGroup extends Group implements DataBinding {
52      private static final long serialVersionUID = -6496712566071542452L;
53  
54      private Class<?> collectionObjectClass;
55  
56      private String propertyName;
57      private BindingInfo bindingInfo;
58  
59      private boolean renderAddLine;
60      private String addLinePropertyName;
61      private BindingInfo addLineBindingInfo;
62      private LabelField addLineLabelField;
63      private List<? extends Field> addLineFields;
64      private List<ActionField> addLineActionFields;
65  
66      private boolean renderLineActions;
67      private List<ActionField> actionFields;
68  
69      private boolean showInactive;
70      private CollectionFilter activeCollectionFilter;
71  
72      private List<CollectionGroup> subCollections;
73  
74      private CollectionGroupBuilder collectionGroupBuilder;
75  
76      public CollectionGroup() {
77          renderAddLine = true;
78          renderLineActions = true;
79          showInactive = false;
80  
81          actionFields = new ArrayList<ActionField>();
82          addLineFields = new ArrayList<Field>();
83          addLineActionFields = new ArrayList<ActionField>();
84          subCollections = new ArrayList<CollectionGroup>();
85      }
86  
87      /**
88       * The following actions are performed:
89       *
90       * <ul>
91       * <li>Set fieldBindModelPath to the collection model path (since the fields
92       * have to belong to the same model as the collection)</li>
93       * <li>Set defaults for binding</li>
94       * <li>Default add line field list to groups items list</li>
95       * <li>Sets default active collection filter if not set</li>
96       * <li>Sets the dictionary entry (if blank) on each of the items to the
97       * collection class</li>
98       * </ul>
99       *
100      * @see org.kuali.rice.krad.uif.core.ComponentBase#performInitialization(org.kuali.rice.krad.uif.container.View)
101      */
102     @Override
103     public void performInitialization(View view) {
104         setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
105 
106         super.performInitialization(view);
107 
108         if (bindingInfo != null) {
109             bindingInfo.setDefaults(view, getPropertyName());
110         }
111 
112         if (addLineBindingInfo != null) {
113             // add line binds to model property
114             if (StringUtils.isNotBlank(addLinePropertyName)) {
115                 addLineBindingInfo.setDefaults(view, getPropertyName());
116                 addLineBindingInfo.setBindingName(addLinePropertyName);
117                 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
118                     addLineBindingInfo.setBindByNamePrefix(getFieldBindByNamePrefix());
119                 }
120             }
121         }
122 
123         for (Component item : getItems()) {
124             if (item instanceof AttributeField) {
125                 AttributeField field = (AttributeField) item;
126 
127                 if (StringUtils.isBlank(field.getDictionaryObjectEntry())) {
128                     field.setDictionaryObjectEntry(collectionObjectClass.getName());
129                 }
130             }
131         }
132         
133         if ((addLineFields == null) || addLineFields.isEmpty()) {
134             addLineFields = getItems();
135         }
136 
137         // if active collection filter not set use default
138         if (this.activeCollectionFilter == null) {
139             activeCollectionFilter = new ActiveCollectionFilter();
140         }
141         
142         // set static collection path on items
143         String collectionPath = "";
144         if (StringUtils.isNotBlank(getBindingInfo().getCollectionPath())) {
145             collectionPath += getBindingInfo().getCollectionPath() + ".";
146         }
147         if (StringUtils.isNotBlank(getBindingInfo().getBindByNamePrefix())) {
148             collectionPath += getBindingInfo().getBindByNamePrefix() + ".";
149         }
150         collectionPath += getBindingInfo().getBindingName();
151         
152         List<AttributeField> collectionFields = ComponentUtils.getComponentsOfTypeDeep(getItems(), AttributeField.class);
153         for (AttributeField collectionField : collectionFields) {
154             collectionField.getBindingInfo().setCollectionPath(collectionPath);
155         }
156         
157         for (CollectionGroup collectionGroup : getSubCollections()) {
158             collectionGroup.getBindingInfo().setCollectionPath(collectionPath);
159             view.getViewHelperService().performComponentInitialization(view, collectionGroup);
160         }
161         
162         // add collection entry to abstract classes
163         if (!view.getAbstractTypeClasses().containsKey(collectionPath)) {
164             view.getAbstractTypeClasses().put(collectionPath, getCollectionObjectClass());
165         }
166 
167         // initialize container items and sub-collections (since they are not in
168         // child list)
169         for (Component item : getItems()) {
170             view.getViewHelperService().performComponentInitialization(view, item);
171         }
172     }
173 
174     /**
175      * Calls the configured <code>CollectionGroupBuilder</code> to build the
176      * necessary components based on the collection data
177      * 
178      * @see org.kuali.rice.krad.uif.container.ContainerBase#performApplyModel(org.kuali.rice.krad.uif.container.View,
179      *      java.lang.Object)
180      */
181     @Override
182     public void performApplyModel(View view, Object model, Component parent) {
183         super.performApplyModel(view, model, parent);
184 
185         pushCollectionGroupToReference();
186 
187         performCollectionFiltering(view, model);
188 
189         getCollectionGroupBuilder().build(view, model, this);
190         
191         pushCollectionGroupToReference();
192     }
193 
194     /**
195      * Sets a reference in the context map for all nested components to the collection group
196      * instance, and sets name as parameter for an action fields in the group
197      */
198     protected void pushCollectionGroupToReference() {
199         List<Component> components = this.getNestedComponents();
200         
201         ComponentUtils
202                 .pushObjectToContext(components, UifConstants.ContextVariableNames.COLLECTION_GROUP,
203                         this);
204 
205         List<ActionField> actionFields =
206                 ComponentUtils.getComponentsOfTypeDeep(components, ActionField.class);
207         for (ActionField actionField : actionFields) {
208             actionField.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH,
209                     this.getBindingInfo().getBindingPath());
210         }
211     }
212 
213     /**
214      * Performs any filtering necessary on the collection before building the collection fields
215      *
216      * <p>
217      * If showInactive is set to false and the collection line type implements <code>Inactivatable</code>,
218      * invokes the active collection filer
219      * </p>
220      *
221      * @param model - object containing the views data, from which the collection will be pulled
222      */
223     protected void performCollectionFiltering(View view, Object model) {
224         if (ImmutableInactivatable.class.isAssignableFrom(this.collectionObjectClass) && !showInactive) {
225             this.activeCollectionFilter.filter(view, model, this);
226         }
227     }
228 
229     /**
230      * New collection lines are handled in the framework by maintaining a map on
231      * the form. The map contains as a key the collection name, and as value an
232      * instance of the collection type. An entry is created here for the
233      * collection represented by the <code>CollectionGroup</code> if an instance
234      * is not available (clearExistingLine will force a new instance). The given
235      * model must be a subclass of <code>UifFormBase</code> in order to find the
236      * Map.
237      * 
238      * @param model
239      *            - Model instance that contains the new collection lines Map
240      * @param clearExistingLine
241      *            - boolean that indicates whether the line should be set to a
242      *            new instance if it already exists
243      */
244     public void initializeNewCollectionLine(View view, Object model, CollectionGroup collectionGroup,
245             boolean clearExistingLine) {
246         getCollectionGroupBuilder().initializeNewCollectionLine(view, model, collectionGroup, clearExistingLine);
247     }
248 
249     /**
250      * @see org.kuali.rice.krad.uif.container.ContainerBase#getNestedComponents()
251      */
252     @Override
253     public List<Component> getNestedComponents() {
254         List<Component> components = super.getNestedComponents();
255 
256         components.add(addLineLabelField);
257         components.addAll(actionFields);
258         components.addAll(addLineActionFields);
259 
260         // remove the containers items because we don't want them as children
261         // (they will become children of the layout manager as the rows are
262         // created)
263         // TODO: is this necessary?
264         for (Component item : getItems()) {
265             if (components.contains(item)) {
266                 components.remove(item);
267             }
268         }
269 
270         return components;
271     }
272 
273     /**
274      * Object class the collection maintains. Used to get dictionary information
275      * in addition to creating new instances for the collection when necessary
276      * 
277      * @return Class<?> collection object class
278      */
279     public Class<?> getCollectionObjectClass() {
280         return this.collectionObjectClass;
281     }
282 
283     /**
284      * Setter for the collection object class
285      * 
286      * @param collectionObjectClass
287      */
288     public void setCollectionObjectClass(Class<?> collectionObjectClass) {
289         this.collectionObjectClass = collectionObjectClass;
290     }
291 
292     /**
293      * @see org.kuali.rice.krad.uif.core.DataBinding#getPropertyName()
294      */
295     public String getPropertyName() {
296         return this.propertyName;
297     }
298 
299     /**
300      * Setter for the collections property name
301      * 
302      * @param propertyName
303      */
304     public void setPropertyName(String propertyName) {
305         this.propertyName = propertyName;
306     }
307 
308     /**
309      * Determines the binding path for the collection. Used to get the
310      * collection value from the model in addition to setting the binding path
311      * for the collection attributes
312      * 
313      * @see org.kuali.rice.krad.uif.core.DataBinding#getBindingInfo()
314      */
315     public BindingInfo getBindingInfo() {
316         return this.bindingInfo;
317     }
318 
319     /**
320      * Setter for the binding info instance
321      * 
322      * @param bindingInfo
323      */
324     public void setBindingInfo(BindingInfo bindingInfo) {
325         this.bindingInfo = bindingInfo;
326     }
327 
328     /**
329      * Action fields that should be rendered for each collection line. Example
330      * line action is the delete action
331      * 
332      * @return List<ActionField> line action fields
333      */
334     public List<ActionField> getActionFields() {
335         return this.actionFields;
336     }
337 
338     /**
339      * Setter for the line action fields list
340      * 
341      * @param actionFields
342      */
343     public void setActionFields(List<ActionField> actionFields) {
344         this.actionFields = actionFields;
345     }
346 
347     /**
348      * Indicates whether the action column for the collection should be rendered
349      * 
350      * @return boolean true if the actions should be rendered, false if not
351      * @see #getActionFields()
352      */
353     public boolean isRenderLineActions() {
354         return this.renderLineActions;
355     }
356 
357     /**
358      * Setter for the render line actions indicator
359      * 
360      * @param renderLineActions
361      */
362     public void setRenderLineActions(boolean renderLineActions) {
363         this.renderLineActions = renderLineActions;
364     }
365 
366     /**
367      * Indicates whether an add line should be rendered for the collection
368      * 
369      * @return boolean true if add line should be rendered, false if it should
370      *         not be
371      */
372     public boolean isRenderAddLine() {
373         return this.renderAddLine;
374     }
375 
376     /**
377      * Setter for the render add line indicator
378      * 
379      * @param renderAddLine
380      */
381     public void setRenderAddLine(boolean renderAddLine) {
382         this.renderAddLine = renderAddLine;
383     }
384 
385     /**
386      * Convenience getter for the add line label field text. The text is used to
387      * label the add line when rendered and its placement depends on the
388      * <code>LayoutManager</code>.
389      * <p>
390      * For the <code>TableLayoutManager</code> the label appears in the sequence
391      * column to the left of the add line fields. For the
392      * <code>StackedLayoutManager</code> the label is placed into the group
393      * header for the line.
394      * </p>
395      * 
396      * @return String add line label
397      */
398     public String getAddLineLabel() {
399         if (getAddLineLabelField() != null) {
400             return getAddLineLabelField().getLabelText();
401         }
402 
403         return null;
404     }
405 
406     /**
407      * Setter for the add line label text
408      * 
409      * @param addLineLabel
410      */
411     public void setAddLineLabel(String addLineLabel) {
412         if (getAddLineLabelField() != null) {
413             getAddLineLabelField().setLabelText(addLineLabel);
414         }
415     }
416 
417     /**
418      * <code>LabelField</code> instance for the add line label
419      * 
420      * @return LabelField add line label field
421      * @see #getAddLineLabel()
422      */
423     public LabelField getAddLineLabelField() {
424         return this.addLineLabelField;
425     }
426 
427     /**
428      * Setter for the <code>LabelField</code> instance for the add line label
429      * 
430      * @param addLineLabelField
431      * @see #getAddLineLabel()
432      */
433     public void setAddLineLabelField(LabelField addLineLabelField) {
434         this.addLineLabelField = addLineLabelField;
435     }
436 
437     /**
438      * Name of the property that contains an instance for the add line. If set
439      * this is used with the binding info to create the path to the add line.
440      * Can be left blank in which case the framework will manage the add line
441      * instance in a generic map.
442      * 
443      * @return String add line property name
444      */
445     public String getAddLinePropertyName() {
446         return this.addLinePropertyName;
447     }
448 
449     /**
450      * Setter for the add line property name
451      * 
452      * @param addLinePropertyName
453      */
454     public void setAddLinePropertyName(String addLinePropertyName) {
455         this.addLinePropertyName = addLinePropertyName;
456     }
457 
458     /**
459      * <code>BindingInfo</code> instance for the add line property used to
460      * determine the full binding path. If add line name given
461      * {@link #getAddLineLabel()} then it is set as the binding name on the
462      * binding info. Add line label and binding info are not required, in which
463      * case the framework will manage the new add line instances through a
464      * generic map (model must extend UifFormBase)
465      * 
466      * @return BindingInfo add line binding info
467      */
468     public BindingInfo getAddLineBindingInfo() {
469         return this.addLineBindingInfo;
470     }
471 
472     /**
473      * Setter for the add line binding info
474      * 
475      * @param addLineBindingInfo
476      */
477     public void setAddLineBindingInfo(BindingInfo addLineBindingInfo) {
478         this.addLineBindingInfo = addLineBindingInfo;
479     }
480 
481     /**
482      * List of <code>Field</code> instances that should be rendered for the
483      * collection add line (if enabled). If not set, the default group's items
484      * list will be used
485      * 
486      * @return List<? extends Field> add line field list
487      */
488     public List<? extends Field> getAddLineFields() {
489         return this.addLineFields;
490     }
491 
492     /**
493      * Setter for the add line field list
494      * 
495      * @param addLineFields
496      */
497     public void setAddLineFields(List<? extends Field> addLineFields) {
498         this.addLineFields = addLineFields;
499     }
500 
501     /**
502      * Action fields that should be rendered for the add line. This is generally
503      * the add action (button) but can be configured to contain additional
504      * actions
505      * 
506      * @return List<ActionField> add line action fields
507      */
508     public List<ActionField> getAddLineActionFields() {
509         return this.addLineActionFields;
510     }
511 
512     /**
513      * Setter for the add line action fields
514      * 
515      * @param addLineActionFields
516      */
517     public void setAddLineActionFields(List<ActionField> addLineActionFields) {
518         this.addLineActionFields = addLineActionFields;
519     }
520 
521     /**
522      * Indicates whether inactive collections lines should be displayed
523      *
524      * <p>
525      * Setting only applies when the collection line type implements the
526      * <code>Inactivatable</code> interface. If true and showInactive is
527      * set to false, the collection will be filtered to remove any items
528      * whose active status returns false
529      * </p>
530      *
531      * @return boolean true to show inactive records, false to not render inactive records
532      */
533     public boolean isShowInactive() {
534         return showInactive;
535     }
536 
537     /**
538      * Setter for the show inactive indicator
539      *
540      * @param boolean show inactive
541      */
542     public void setShowInactive(boolean showInactive) {
543         this.showInactive = showInactive;
544     }
545 
546     /**
547      * Collection filter instance for filtering the collection data when the
548      * showInactive flag is set to false
549      *
550      * @return CollectionFilter
551      */
552     public CollectionFilter getActiveCollectionFilter() {
553         return activeCollectionFilter;
554     }
555 
556     /**
557      * Setter for the collection filter to use for filter inactive records from the
558      * collection
559      *
560      * @param activeCollectionFilter - CollectionFilter instance
561      */
562     public void setActiveCollectionFilter(CollectionFilter activeCollectionFilter) {
563         this.activeCollectionFilter = activeCollectionFilter;
564     }
565 
566     /**
567      * List of <code>CollectionGroup</code> instances that are sub-collections
568      * of the collection represented by this collection group
569      * 
570      * @return List<CollectionGroup> sub collections
571      */
572     public List<CollectionGroup> getSubCollections() {
573         return this.subCollections;
574     }
575 
576     /**
577      * Setter for the sub collection list
578      * 
579      * @param subCollections
580      */
581     public void setSubCollections(List<CollectionGroup> subCollections) {
582         this.subCollections = subCollections;
583     }
584 
585     /**
586      * <code>CollectionGroupBuilder</code> instance that will build the
587      * components dynamically for the collection instance
588      * 
589      * @return CollectionGroupBuilder instance
590      */
591     public CollectionGroupBuilder getCollectionGroupBuilder() {
592         if (this.collectionGroupBuilder == null) {
593             this.collectionGroupBuilder = new CollectionGroupBuilder();
594         }
595         return this.collectionGroupBuilder;
596     }
597 
598     /**
599      * Setter for the collection group building instance
600      * 
601      * @param collectionGroupBuilder
602      */
603     public void setCollectionGroupBuilder(CollectionGroupBuilder collectionGroupBuilder) {
604         this.collectionGroupBuilder = collectionGroupBuilder;
605     }
606 
607     /**
608      * @see org.kuali.rice.krad.uif.container.ContainerBase#getItems()
609      */
610     @SuppressWarnings("unchecked")
611     @Override
612     public List<? extends Field> getItems() {
613         return (List<? extends Field>) super.getItems();
614     }
615 
616 }