View Javadoc

1   /**
2    * Copyright 2005-2012 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 java.util.ArrayList;
19  import java.util.List;
20  
21  import org.apache.commons.lang.StringUtils;
22  import org.kuali.rice.core.api.exception.RiceRuntimeException;
23  import org.kuali.rice.krad.uif.UifConstants;
24  import org.kuali.rice.krad.uif.UifParameters;
25  import org.kuali.rice.krad.uif.component.BindingInfo;
26  import org.kuali.rice.krad.uif.component.Component;
27  import org.kuali.rice.krad.uif.component.ComponentSecurity;
28  import org.kuali.rice.krad.uif.component.DataBinding;
29  import org.kuali.rice.krad.uif.field.ActionField;
30  import org.kuali.rice.krad.uif.field.DataField;
31  import org.kuali.rice.krad.uif.field.DataFieldSecurity;
32  import org.kuali.rice.krad.uif.field.Field;
33  import org.kuali.rice.krad.uif.field.LabelField;
34  import org.kuali.rice.krad.uif.util.ComponentUtils;
35  import org.kuali.rice.krad.uif.view.View;
36  import org.kuali.rice.krad.uif.widget.QuickFinder;
37  
38  /**
39   * Group that holds a collection of objects and configuration for presenting the
40   * collection in the UI. Supports functionality such as add line, line actions,
41   * and nested collections.
42   *
43   * <p>
44   * Note the standard header/footer can be used to give a header to the
45   * collection as a whole, or to provide actions that apply to the entire
46   * collection
47   * </p>
48   *
49   * <p>
50   * For binding purposes the binding path of each row field is indexed. The name
51   * property inherited from <code>ComponentBase</code> is used as the collection
52   * name. The collectionObjectClass property is used to lookup attributes from
53   * the data dictionary.
54   * </p>
55   *
56   * @author Kuali Rice Team (rice.collab@kuali.org)
57   */
58  public class CollectionGroup extends Group implements DataBinding {
59      private static final long serialVersionUID = -6496712566071542452L;
60  
61      private Class<?> collectionObjectClass;
62  
63      private String propertyName;
64      private BindingInfo bindingInfo;
65  
66      private boolean renderAddLine;
67      private String addLinePropertyName;
68      private BindingInfo addLineBindingInfo;
69      private LabelField addLineLabelField;
70      private List<? extends Component> addLineFields;
71      private List<ActionField> addLineActionFields;
72  
73      private boolean renderLineActions;
74      private List<ActionField> actionFields;
75  
76      private boolean renderSelectField;
77      private String selectPropertyName;
78  
79      private QuickFinder collectionLookup;
80  
81      private boolean showHideInactiveButton;
82      private boolean showInactive;
83      private CollectionFilter activeCollectionFilter;
84      private List<CollectionFilter> filters;
85  
86      private List<CollectionGroup> subCollections;
87      private String subCollectionSuffix;
88  
89      private CollectionGroupBuilder collectionGroupBuilder;
90  
91      public CollectionGroup() {
92          renderAddLine = true;
93          renderLineActions = true;
94          showInactive = false;
95          showHideInactiveButton = true;
96          renderSelectField = false;
97  
98          filters = new ArrayList<CollectionFilter>();
99          actionFields = new ArrayList<ActionField>();
100         addLineFields = new ArrayList<Field>();
101         addLineActionFields = new ArrayList<ActionField>();
102         subCollections = new ArrayList<CollectionGroup>();
103     }
104 
105     /**
106      * The following actions are performed:
107      *
108      * <ul>
109      * <li>Set fieldBindModelPath to the collection model path (since the fields
110      * have to belong to the same model as the collection)</li>
111      * <li>Set defaults for binding</li>
112      * <li>Default add line field list to groups items list</li>
113      * <li>Sets default active collection filter if not set</li>
114      * <li>Sets the dictionary entry (if blank) on each of the items to the
115      * collection class</li>
116      * </ul>
117      *
118      * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View,
119      *      java.lang.Object)
120      */
121     @Override
122     public void performInitialization(View view, Object model) {
123         setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
124 
125         super.performInitialization(view, model);
126 
127         if (bindingInfo != null) {
128             bindingInfo.setDefaults(view, getPropertyName());
129         }
130 
131         if (addLineBindingInfo != null) {
132             // add line binds to model property
133             if (StringUtils.isNotBlank(addLinePropertyName)) {
134                 addLineBindingInfo.setDefaults(view, getPropertyName());
135                 addLineBindingInfo.setBindingName(addLinePropertyName);
136                 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
137                     addLineBindingInfo.setBindByNamePrefix(getFieldBindByNamePrefix());
138                 }
139             }
140         }
141 
142         for (Component item : getItems()) {
143             if (item instanceof DataField) {
144                 DataField field = (DataField) item;
145 
146                 if (StringUtils.isBlank(field.getDictionaryObjectEntry())) {
147                     field.setDictionaryObjectEntry(collectionObjectClass.getName());
148                 }
149             }
150         }
151 
152         if ((addLineFields == null) || addLineFields.isEmpty()) {
153             addLineFields = getItems();
154         }
155 
156         // if active collection filter not set use default
157         if (this.activeCollectionFilter == null) {
158             activeCollectionFilter = new ActiveCollectionFilter();
159         }
160 
161         // set static collection path on items
162         String collectionPath = "";
163         if (StringUtils.isNotBlank(getBindingInfo().getCollectionPath())) {
164             collectionPath += getBindingInfo().getCollectionPath() + ".";
165         }
166         if (StringUtils.isNotBlank(getBindingInfo().getBindByNamePrefix())) {
167             collectionPath += getBindingInfo().getBindByNamePrefix() + ".";
168         }
169         collectionPath += getBindingInfo().getBindingName();
170 
171         List<DataField> collectionFields = ComponentUtils.getComponentsOfTypeDeep(getItems(), DataField.class);
172         for (DataField collectionField : collectionFields) {
173             collectionField.getBindingInfo().setCollectionPath(collectionPath);
174         }
175 
176         // add collection entry to abstract classes
177         if (!view.getAbstractTypeClasses().containsKey(collectionPath)) {
178             view.getAbstractTypeClasses().put(collectionPath, getCollectionObjectClass());
179         }
180 
181         // initialize container items and sub-collections (since they are not in
182         // child list)
183         for (Component item : getItems()) {
184             view.getViewHelperService().performComponentInitialization(view, model, item);
185         }
186 
187         for (CollectionGroup collectionGroup : getSubCollections()) {
188             collectionGroup.getBindingInfo().setCollectionPath(collectionPath);
189             view.getViewHelperService().performComponentInitialization(view, model, collectionGroup);
190         }
191     }
192 
193     /**
194      * Calls the configured <code>CollectionGroupBuilder</code> to build the
195      * necessary components based on the collection data
196      *
197      * @see org.kuali.rice.krad.uif.container.ContainerBase#performApplyModel(org.kuali.rice.krad.uif.view.View,
198      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
199      */
200     @Override
201     public void performApplyModel(View view, Object model, Component parent) {
202         super.performApplyModel(view, model, parent);
203 
204         pushCollectionGroupToReference();
205 
206         // if rendering the collection group, build out the lines
207         if (isRender()) {
208             getCollectionGroupBuilder().build(view, model, this);
209         }
210 
211         // TODO: is this necessary to call again?
212         pushCollectionGroupToReference();
213     }
214 
215     /**
216      * Sets a reference in the context map for all nested components to the collection group
217      * instance, and sets name as parameter for an action fields in the group
218      */
219     protected void pushCollectionGroupToReference() {
220         List<Component> components = this.getComponentsForLifecycle();
221 
222         ComponentUtils.pushObjectToContext(components, UifConstants.ContextVariableNames.COLLECTION_GROUP, this);
223 
224         List<ActionField> actionFields = ComponentUtils.getComponentsOfTypeDeep(components, ActionField.class);
225         for (ActionField actionField : actionFields) {
226             actionField.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH,
227                     this.getBindingInfo().getBindingPath());
228         }
229     }
230 
231     /**
232      * New collection lines are handled in the framework by maintaining a map on
233      * the form. The map contains as a key the collection name, and as value an
234      * instance of the collection type. An entry is created here for the
235      * collection represented by the <code>CollectionGroup</code> if an instance
236      * is not available (clearExistingLine will force a new instance). The given
237      * model must be a subclass of <code>UifFormBase</code> in order to find the
238      * Map.
239      *
240      * @param model - Model instance that contains the new collection lines Map
241      * @param clearExistingLine - 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#getComponentsForLifecycle()
251      */
252     @Override
253     public List<Component> getComponentsForLifecycle() {
254         List<Component> components = super.getComponentsForLifecycle();
255 
256         components.add(addLineLabelField);
257         components.add(collectionLookup);
258 
259         // remove the containers items because we don't want them as children
260         // (they will become children of the layout manager as the rows are
261         // created)
262         for (Component item : getItems()) {
263             if (components.contains(item)) {
264                 components.remove(item);
265             }
266         }
267 
268         return components;
269     }
270 
271     /**
272      * @see org.kuali.rice.krad.uif.component.Component#getComponentPrototypes()
273      */
274     @Override
275     public List<Component> getComponentPrototypes() {
276         List<Component> components = super.getComponentPrototypes();
277 
278         components.addAll(actionFields);
279         components.addAll(addLineActionFields);
280         components.addAll(getItems());
281         components.addAll(getSubCollections());
282 
283         return components;
284     }
285 
286     /**
287      * Object class the collection maintains. Used to get dictionary information
288      * in addition to creating new instances for the collection when necessary
289      *
290      * @return Class<?> collection object class
291      */
292     public Class<?> getCollectionObjectClass() {
293         return this.collectionObjectClass;
294     }
295 
296     /**
297      * Setter for the collection object class
298      *
299      * @param collectionObjectClass
300      */
301     public void setCollectionObjectClass(Class<?> collectionObjectClass) {
302         this.collectionObjectClass = collectionObjectClass;
303     }
304 
305     /**
306      * @see org.kuali.rice.krad.uif.component.DataBinding#getPropertyName()
307      */
308     public String getPropertyName() {
309         return this.propertyName;
310     }
311 
312     /**
313      * Setter for the collections property name
314      *
315      * @param propertyName
316      */
317     public void setPropertyName(String propertyName) {
318         this.propertyName = propertyName;
319     }
320 
321     /**
322      * Determines the binding path for the collection. Used to get the
323      * collection value from the model in addition to setting the binding path
324      * for the collection attributes
325      *
326      * @see org.kuali.rice.krad.uif.component.DataBinding#getBindingInfo()
327      */
328     public BindingInfo getBindingInfo() {
329         return this.bindingInfo;
330     }
331 
332     /**
333      * Setter for the binding info instance
334      *
335      * @param bindingInfo
336      */
337     public void setBindingInfo(BindingInfo bindingInfo) {
338         this.bindingInfo = bindingInfo;
339     }
340 
341     /**
342      * Action fields that should be rendered for each collection line. Example
343      * line action is the delete action
344      *
345      * @return List<ActionField> line action fields
346      */
347     public List<ActionField> getActionFields() {
348         return this.actionFields;
349     }
350 
351     /**
352      * Setter for the line action fields list
353      *
354      * @param actionFields
355      */
356     public void setActionFields(List<ActionField> actionFields) {
357         this.actionFields = actionFields;
358     }
359 
360     /**
361      * Indicates whether the action column for the collection should be rendered
362      *
363      * @return boolean true if the actions should be rendered, false if not
364      * @see #getActionFields()
365      */
366     public boolean isRenderLineActions() {
367         return this.renderLineActions;
368     }
369 
370     /**
371      * Setter for the render line actions indicator
372      *
373      * @param renderLineActions
374      */
375     public void setRenderLineActions(boolean renderLineActions) {
376         this.renderLineActions = renderLineActions;
377     }
378 
379     /**
380      * Indicates whether an add line should be rendered for the collection
381      *
382      * @return boolean true if add line should be rendered, false if it should
383      *         not be
384      */
385     public boolean isRenderAddLine() {
386         return this.renderAddLine;
387     }
388 
389     /**
390      * Setter for the render add line indicator
391      *
392      * @param renderAddLine
393      */
394     public void setRenderAddLine(boolean renderAddLine) {
395         this.renderAddLine = renderAddLine;
396     }
397 
398     /**
399      * Convenience getter for the add line label field text. The text is used to
400      * label the add line when rendered and its placement depends on the
401      * <code>LayoutManager</code>.
402      * <p>
403      * For the <code>TableLayoutManager</code> the label appears in the sequence
404      * column to the left of the add line fields. For the
405      * <code>StackedLayoutManager</code> the label is placed into the group
406      * header for the line.
407      * </p>
408      *
409      * @return String add line label
410      */
411     public String getAddLineLabel() {
412         if (getAddLineLabelField() != null) {
413             return getAddLineLabelField().getLabelText();
414         }
415 
416         return null;
417     }
418 
419     /**
420      * Setter for the add line label text
421      *
422      * @param addLineLabel
423      */
424     public void setAddLineLabel(String addLineLabel) {
425         if (getAddLineLabelField() != null) {
426             getAddLineLabelField().setLabelText(addLineLabel);
427         }
428     }
429 
430     /**
431      * <code>LabelField</code> instance for the add line label
432      *
433      * @return LabelField add line label field
434      * @see #getAddLineLabel()
435      */
436     public LabelField getAddLineLabelField() {
437         return this.addLineLabelField;
438     }
439 
440     /**
441      * Setter for the <code>LabelField</code> instance for the add line label
442      *
443      * @param addLineLabelField
444      * @see #getAddLineLabel()
445      */
446     public void setAddLineLabelField(LabelField addLineLabelField) {
447         this.addLineLabelField = addLineLabelField;
448     }
449 
450     /**
451      * Name of the property that contains an instance for the add line. If set
452      * this is used with the binding info to create the path to the add line.
453      * Can be left blank in which case the framework will manage the add line
454      * instance in a generic map.
455      *
456      * @return String add line property name
457      */
458     public String getAddLinePropertyName() {
459         return this.addLinePropertyName;
460     }
461 
462     /**
463      * Setter for the add line property name
464      *
465      * @param addLinePropertyName
466      */
467     public void setAddLinePropertyName(String addLinePropertyName) {
468         this.addLinePropertyName = addLinePropertyName;
469     }
470 
471     /**
472      * <code>BindingInfo</code> instance for the add line property used to
473      * determine the full binding path. If add line name given
474      * {@link #getAddLineLabel()} then it is set as the binding name on the
475      * binding info. Add line label and binding info are not required, in which
476      * case the framework will manage the new add line instances through a
477      * generic map (model must extend UifFormBase)
478      *
479      * @return BindingInfo add line binding info
480      */
481     public BindingInfo getAddLineBindingInfo() {
482         return this.addLineBindingInfo;
483     }
484 
485     /**
486      * Setter for the add line binding info
487      *
488      * @param addLineBindingInfo
489      */
490     public void setAddLineBindingInfo(BindingInfo addLineBindingInfo) {
491         this.addLineBindingInfo = addLineBindingInfo;
492     }
493 
494     /**
495      * List of <code>Component</code> instances that should be rendered for the
496      * collection add line (if enabled). If not set, the default group's items
497      * list will be used
498      *
499      * @return List<? extends Component> add line field list
500      */
501     public List<? extends Component> getAddLineFields() {
502         return this.addLineFields;
503     }
504 
505     /**
506      * Setter for the add line field list
507      *
508      * @param addLineFields
509      */
510     public void setAddLineFields(List<? extends Component> addLineFields) {
511         this.addLineFields = addLineFields;
512     }
513 
514     /**
515      * Action fields that should be rendered for the add line. This is generally
516      * the add action (button) but can be configured to contain additional
517      * actions
518      *
519      * @return List<ActionField> add line action fields
520      */
521     public List<ActionField> getAddLineActionFields() {
522         return this.addLineActionFields;
523     }
524 
525     /**
526      * Setter for the add line action fields
527      *
528      * @param addLineActionFields
529      */
530     public void setAddLineActionFields(List<ActionField> addLineActionFields) {
531         this.addLineActionFields = addLineActionFields;
532     }
533 
534     /**
535      * Indicates whether lines of the collection group should be selected by rendering a
536      * field for each line that will allow selection
537      *
538      * <p>
539      * For example, having the select field enabled could allow selecting multiple lines from a search
540      * to return (multi-value lookup)
541      * </p>
542      *
543      * @return boolean true if select field should be rendered, false if not
544      */
545     public boolean isRenderSelectField() {
546         return renderSelectField;
547     }
548 
549     /**
550      * Setter for the render selected field indicator
551      *
552      * @param renderSelectField
553      */
554     public void setRenderSelectField(boolean renderSelectField) {
555         this.renderSelectField = renderSelectField;
556     }
557 
558     /**
559      * When {@link #isRenderSelectField()} is true, gives the name of the property the select field
560      * should bind to
561      *
562      * <p>
563      * Note if no prefix is given in the property name, such as 'form.', it is assumed the property is
564      * contained on the collection line. In this case the binding path to the collection line will be
565      * appended. In other cases, it is assumed the property is a list or set of String that will hold the
566      * selected identifier strings
567      * </p>
568      *
569      * <p>
570      * This property is not required. If not the set the framework will use a property contained on
571      * <code>UifFormBase</code>
572      * </p>
573      *
574      * @return String property name for select field
575      */
576     public String getSelectPropertyName() {
577         return selectPropertyName;
578     }
579 
580     /**
581      * Setter for the property name that will bind to the select field
582      *
583      * @param selectPropertyName
584      */
585     public void setSelectPropertyName(String selectPropertyName) {
586         this.selectPropertyName = selectPropertyName;
587     }
588 
589     /**
590      * Instance of the <code>QuickFinder</code> widget that configures a multi-value lookup for the collection
591      *
592      * <p>
593      * If the collection lookup is enabled (by the render property of the quick finder), {@link
594      * #getCollectionObjectClass()} will be used as the data object class for the lookup (if not set). Field
595      * conversions need to be set as usual and will be applied for each line returned
596      * </p>
597      *
598      * @return QuickFinder instance configured for the collection lookup
599      */
600     public QuickFinder getCollectionLookup() {
601         return collectionLookup;
602     }
603 
604     /**
605      * Setter for the collection lookup quickfinder instance
606      *
607      * @param collectionLookup
608      */
609     public void setCollectionLookup(QuickFinder collectionLookup) {
610         this.collectionLookup = collectionLookup;
611     }
612 
613     /**
614      * Indicates whether inactive collections lines should be displayed
615      *
616      * <p>
617      * Setting only applies when the collection line type implements the
618      * <code>Inactivatable</code> interface. If true and showInactive is
619      * set to false, the collection will be filtered to remove any items
620      * whose active status returns false
621      * </p>
622      *
623      * @return boolean true to show inactive records, false to not render inactive records
624      */
625     public boolean isShowInactive() {
626         return showInactive;
627     }
628 
629     /**
630      * Setter for the show inactive indicator
631      *
632      * @param showInactive boolean show inactive
633      */
634     public void setShowInactive(boolean showInactive) {
635         this.showInactive = showInactive;
636     }
637 
638     /**
639      * Collection filter instance for filtering the collection data when the
640      * showInactive flag is set to false
641      *
642      * @return CollectionFilter
643      */
644     public CollectionFilter getActiveCollectionFilter() {
645         return activeCollectionFilter;
646     }
647 
648     /**
649      * Setter for the collection filter to use for filter inactive records from the
650      * collection
651      *
652      * @param activeCollectionFilter - CollectionFilter instance
653      */
654     public void setActiveCollectionFilter(CollectionFilter activeCollectionFilter) {
655         this.activeCollectionFilter = activeCollectionFilter;
656     }
657 
658     /**
659      * List of {@link CollectionFilter} instances that should be invoked to filter the collection before
660      * displaying
661      *
662      * @return List<CollectionFilter>
663      */
664     public List<CollectionFilter> getFilters() {
665         return filters;
666     }
667 
668     /**
669      * Setter for the List of collection filters for which the collection will be filtered against
670      *
671      * @param filters
672      */
673     public void setFilters(List<CollectionFilter> filters) {
674         this.filters = filters;
675     }
676 
677     /**
678      * List of <code>CollectionGroup</code> instances that are sub-collections
679      * of the collection represented by this collection group
680      *
681      * @return List<CollectionGroup> sub collections
682      */
683     public List<CollectionGroup> getSubCollections() {
684         return this.subCollections;
685     }
686 
687     /**
688      * Setter for the sub collection list
689      *
690      * @param subCollections
691      */
692     public void setSubCollections(List<CollectionGroup> subCollections) {
693         this.subCollections = subCollections;
694     }
695 
696     /**
697      * Suffix for IDs that identifies the collection line the sub-collection belongs to
698      *
699      * <p>
700      * Built by the framework as the collection lines are being generated
701      * </p>
702      *
703      * @return String id suffix for sub-collection
704      */
705     public String getSubCollectionSuffix() {
706         return subCollectionSuffix;
707     }
708 
709     /**
710      * Setter for the sub-collection suffix (used by framework, should not be
711      * set in configuration)
712      *
713      * @param subCollectionSuffix
714      */
715     public void setSubCollectionSuffix(String subCollectionSuffix) {
716         this.subCollectionSuffix = subCollectionSuffix;
717     }
718 
719     /**
720      * Collection Security object that indicates what authorization (permissions) exist for the collection
721      *
722      * @return CollectionGroupSecurity instance
723      */
724     @Override
725     public CollectionGroupSecurity getComponentSecurity() {
726         return (CollectionGroupSecurity) super.getComponentSecurity();
727     }
728 
729     /**
730      * Override to assert a {@link CollectionGroupSecurity} instance is set
731      *
732      * @param componentSecurity - instance of CollectionGroupSecurity
733      */
734     @Override
735     public void setComponentSecurity(ComponentSecurity componentSecurity) {
736         if (!(componentSecurity instanceof CollectionGroupSecurity)) {
737             throw new RiceRuntimeException(
738                     "Component security for CollectionGroup should be instance of CollectionGroupSecurity");
739         }
740 
741         super.setComponentSecurity(componentSecurity);
742     }
743 
744     @Override
745     protected Class<? extends ComponentSecurity> getComponentSecurityClass() {
746         return CollectionGroupSecurity.class;
747     }
748 
749     /**
750      * <code>CollectionGroupBuilder</code> instance that will build the
751      * components dynamically for the collection instance
752      *
753      * @return CollectionGroupBuilder instance
754      */
755     public CollectionGroupBuilder getCollectionGroupBuilder() {
756         if (this.collectionGroupBuilder == null) {
757             this.collectionGroupBuilder = new CollectionGroupBuilder();
758         }
759         return this.collectionGroupBuilder;
760     }
761 
762     /**
763      * Setter for the collection group building instance
764      *
765      * @param collectionGroupBuilder
766      */
767     public void setCollectionGroupBuilder(CollectionGroupBuilder collectionGroupBuilder) {
768         this.collectionGroupBuilder = collectionGroupBuilder;
769     }
770 
771     /**
772      * @param showHideInactiveButton the showHideInactiveButton to set
773      */
774     public void setShowHideInactiveButton(boolean showHideInactiveButton) {
775         this.showHideInactiveButton = showHideInactiveButton;
776     }
777 
778     /**
779      * @return the showHideInactiveButton
780      */
781     public boolean isShowHideInactiveButton() {
782         return showHideInactiveButton;
783     }
784 
785 }