View Javadoc

1   /**
2    * Copyright 2005-2013 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 com.google.common.collect.Lists;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.exception.RiceRuntimeException;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
22  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
23  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
24  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
25  import org.kuali.rice.krad.datadictionary.validator.Validator;
26  import org.kuali.rice.krad.uif.UifConstants;
27  import org.kuali.rice.krad.uif.UifParameters;
28  import org.kuali.rice.krad.uif.component.BindingInfo;
29  import org.kuali.rice.krad.uif.component.ClientSideState;
30  import org.kuali.rice.krad.uif.component.Component;
31  import org.kuali.rice.krad.uif.component.ComponentSecurity;
32  import org.kuali.rice.krad.uif.component.DataBinding;
33  import org.kuali.rice.krad.uif.element.Action;
34  import org.kuali.rice.krad.uif.element.Message;
35  import org.kuali.rice.krad.uif.field.DataField;
36  import org.kuali.rice.krad.uif.field.Field;
37  import org.kuali.rice.krad.uif.util.ComponentFactory;
38  import org.kuali.rice.krad.uif.util.ComponentUtils;
39  import org.kuali.rice.krad.uif.view.View;
40  import org.kuali.rice.krad.uif.widget.QuickFinder;
41  
42  import java.util.ArrayList;
43  import java.util.List;
44  
45  /**
46   * Group that holds a collection of objects and configuration for presenting the
47   * collection in the UI. Supports functionality such as add line, line actions,
48   * and nested collections.
49   *
50   * <p>
51   * Note the standard header/footer can be used to give a header to the
52   * collection as a whole, or to provide actions that apply to the entire
53   * collection
54   * </p>
55   *
56   * <p>
57   * For binding purposes the binding path of each row field is indexed. The name
58   * property inherited from <code>ComponentBase</code> is used as the collection
59   * name. The collectionObjectClass property is used to lookup attributes from
60   * the data dictionary.
61   * </p>
62   *
63   * @author Kuali Rice Team (rice.collab@kuali.org)
64   */
65  @BeanTags({@BeanTag(name = "collectionGroup-bean", parent = "Uif-CollectionGroupBase"),
66          @BeanTag(name = "stackedCollectionGroup-bean", parent = "Uif-StackedCollectionGroup"),
67          @BeanTag(name = "stackedCollectionSection-bean", parent = "Uif-StackedCollectionSection"),
68          @BeanTag(name = "stackedCollectionSubSection-bean", parent = "Uif-StackedCollectionSubSection"),
69          @BeanTag(name = "stackedSubCollection-withinSection-bean", parent = "Uif-StackedSubCollection-WithinSection"),
70          @BeanTag(name = "stackedSubCollection-withinSubSection-bean", parent = "Uif-StackedSubCollection-WithinSubSection"),
71          @BeanTag(name = "disclosure-stackedCollectionSection-bean", parent = "Uif-Disclosure-StackedCollectionSection"),
72          @BeanTag(name = "disclosure-stackedCollectionSubSection-bean",
73                  parent = "Uif-Disclosure-StackedCollectionSubSection"),
74          @BeanTag(name = "disclosure-stackedSubCollection-withinSection-bean",
75                  parent = "Uif-Disclosure-StackedSubCollection-WithinSection"),
76          @BeanTag(name = "disclosure-stackedSubCollection-withinSubSection-bean",
77                  parent = "Uif-Disclosure-StackedSubCollection-WithinSubSection"),
78          @BeanTag(name = "tableCollectionGroup-bean", parent = "Uif-TableCollectionGroup"),
79          @BeanTag(name = "tableCollectionSection-bean", parent = "Uif-TableCollectionSection"),
80          @BeanTag(name = "tableCollectionSubSection-bean", parent = "Uif-TableCollectionSubSection"),
81          @BeanTag(name = "tableSubCollection-withinSection-bean", parent = "Uif-TableSubCollection-WithinSection"),
82          @BeanTag(name = "tableSubCollection-withinSubSection-bean", parent = "Uif-TableSubCollection-WithinSubSection"),
83          @BeanTag(name = "disclosure-tableCollectionSection-bean", parent = "Uif-Disclosure-TableCollectionSection"),
84          @BeanTag(name = "disclosure-tableCollectionSubSection-bean", parent = "Uif-Disclosure-TableCollectionSubSection"),
85          @BeanTag(name = "disclosure-tableSubCollection-withinSection-bean",
86                  parent = "Uif-Disclosure-TableSubCollection-WithinSection"),
87          @BeanTag(name = "disclosure-tableSubCollection-withinSubSection-bean",
88                  parent = "Uif-Disclosure-TableSubCollection-WithinSubSection"),
89          @BeanTag(name = "listCollectionGroup-bean", parent = "Uif-ListCollectionGroup"),
90          @BeanTag(name = "listCollectionSection-bean", parent = "Uif-ListCollectionSection"),
91          @BeanTag(name = "listCollectionSubSection-bean", parent = "Uif-ListCollectionSubSection"),
92          @BeanTag(name = "documentNotesSection-bean", parent = "Uif-DocumentNotesSection"),
93          @BeanTag(name = "lookupResultsCollectionSection-bean", parent = "Uif-LookupResultsCollectionSection"),
94          @BeanTag(name = "maintenanceStackedCollectionSection-bean", parent = "Uif-MaintenanceStackedCollectionSection"),
95          @BeanTag(name = "maintenanceStackedSubCollection-withinSection-bean",
96                  parent = "Uif-MaintenanceStackedSubCollection-WithinSection"),
97          @BeanTag(name = "maintenanceTableCollectionSection-bean", parent = "Uif-MaintenanceTableCollectionSection"),
98          @BeanTag(name = "maintenanceTableSubCollection-withinSection-bean",
99                  parent = "Uif-MaintenanceTableSubCollection-withinSection")})
100 public class CollectionGroup extends Group implements DataBinding {
101     private static final long serialVersionUID = -6496712566071542452L;
102 
103     private Class<?> collectionObjectClass;
104 
105     private String propertyName;
106     private BindingInfo bindingInfo;
107 
108     private boolean renderAddLine;
109     private String addLinePropertyName;
110     private BindingInfo addLineBindingInfo;
111 
112     private Message addLineLabel;
113     private List<? extends Component> addLineItems;
114     private List<Action> addLineActions;
115 
116     private boolean renderLineActions;
117     private List<Action> lineActions;
118 
119     private boolean includeLineSelectionField;
120     private String lineSelectPropertyName;
121 
122     private QuickFinder collectionLookup;
123 
124     private boolean renderInactiveToggleButton;
125     @ClientSideState(variableName = "inactive")
126     private boolean showInactiveLines;
127     private CollectionFilter activeCollectionFilter;
128     private List<CollectionFilter> filters;
129 
130     private List<CollectionGroup> subCollections;
131     private String subCollectionSuffix;
132 
133     private CollectionGroupBuilder collectionGroupBuilder;
134 
135     private int displayCollectionSize = -1;
136 
137     private boolean highlightNewItems;
138     private boolean highlightAddItem;
139     private String newItemsCssClass;
140     private String addItemCssClass;
141 
142     private boolean renderAddBlankLineButton;
143     private Action addBlankLineAction;
144     private String addLinePlacement;
145 
146     private boolean renderSaveLineActions;
147     private boolean addViaLightBox;
148     private Action addViaLightBoxAction;
149 
150     private boolean useServerPaging = false;
151     private int displayStart = -1;
152     private int displayLength = -1;
153     private int filteredCollectionSize = -1;
154 
155     private List<String> totalColumns;
156 
157     public CollectionGroup() {
158         renderAddLine = true;
159         renderLineActions = true;
160         renderInactiveToggleButton = true;
161         highlightNewItems = true;
162         highlightAddItem = true;
163         addLinePlacement = "TOP";
164 
165         filters = new ArrayList<CollectionFilter>();
166         lineActions = new ArrayList<Action>();
167         addLineItems = new ArrayList<Field>();
168         addLineActions = new ArrayList<Action>();
169         subCollections = new ArrayList<CollectionGroup>();
170     }
171 
172     /**
173      * The following actions are performed:
174      *
175      * <ul>
176      * <li>Set fieldBindModelPath to the collection model path (since the fields
177      * have to belong to the same model as the collection)</li>
178      * <li>Set defaults for binding</li>
179      * <li>Default add line field list to groups items list</li>
180      * <li>Sets default active collection filter if not set</li>
181      * <li>Sets the dictionary entry (if blank) on each of the items to the
182      * collection class</li>
183      * </ul>
184      *
185      * @see org.kuali.rice.krad.uif.component.ComponentBase#performInitialization(org.kuali.rice.krad.uif.view.View,
186      *      java.lang.Object)
187      */
188     @Override
189     public void performInitialization(View view, Object model) {
190         setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
191 
192         super.performInitialization(view, model);
193 
194         if (bindingInfo != null) {
195             bindingInfo.setDefaults(view, getPropertyName());
196         }
197 
198         if (addLineBindingInfo != null) {
199             // add line binds to model property
200             if (StringUtils.isNotBlank(addLinePropertyName)) {
201                 addLineBindingInfo.setDefaults(view, getPropertyName());
202                 addLineBindingInfo.setBindingName(addLinePropertyName);
203                 if (StringUtils.isNotBlank(getFieldBindByNamePrefix())) {
204                     addLineBindingInfo.setBindByNamePrefix(getFieldBindByNamePrefix());
205                 }
206             }
207         }
208 
209         for (Component item : getItems()) {
210             if (item instanceof DataField) {
211                 DataField field = (DataField) item;
212 
213                 if (StringUtils.isBlank(field.getDictionaryObjectEntry())) {
214                     field.setDictionaryObjectEntry(collectionObjectClass.getName());
215                 }
216             }
217         }
218 
219         if ((addLineItems == null) || addLineItems.isEmpty()) {
220             addLineItems = getItems();
221         } else {
222             for (Component addLineField : addLineItems) {
223                 if (!(addLineField instanceof DataField)) {
224                     continue;
225                 }
226 
227                 DataField field = (DataField) addLineField;
228 
229                 if (StringUtils.isBlank(field.getDictionaryObjectEntry())) {
230                     field.setDictionaryObjectEntry(collectionObjectClass.getName());
231                 }
232             }
233         }
234 
235         // if active collection filter not set use default
236         if (this.activeCollectionFilter == null) {
237             activeCollectionFilter = new ActiveCollectionFilter();
238         }
239 
240         // set static collection path on items
241         String collectionPath = "";
242         if (StringUtils.isNotBlank(getBindingInfo().getCollectionPath())) {
243             collectionPath += getBindingInfo().getCollectionPath() + ".";
244         }
245         if (StringUtils.isNotBlank(getBindingInfo().getBindByNamePrefix())) {
246             collectionPath += getBindingInfo().getBindByNamePrefix() + ".";
247         }
248         collectionPath += getBindingInfo().getBindingName();
249 
250         List<DataField> collectionFields = ComponentUtils.getComponentsOfTypeDeep(getItems(), DataField.class);
251         for (DataField collectionField : collectionFields) {
252             collectionField.getBindingInfo().setCollectionPath(collectionPath);
253         }
254 
255         List<DataField> addLineCollectionFields = ComponentUtils.getComponentsOfTypeDeep(addLineItems, DataField.class);
256         for (DataField collectionField : addLineCollectionFields) {
257             collectionField.getBindingInfo().setCollectionPath(collectionPath);
258         }
259 
260         for (CollectionGroup collectionGroup : getSubCollections()) {
261             collectionGroup.getBindingInfo().setCollectionPath(collectionPath);
262         }
263 
264         // add collection entry to abstract classes
265         if (!view.getObjectPathToConcreteClassMapping().containsKey(collectionPath)) {
266             view.getObjectPathToConcreteClassMapping().put(collectionPath, getCollectionObjectClass());
267         }
268     }
269 
270     /**
271      * Calls the configured <code>CollectionGroupBuilder</code> to build the
272      * necessary components based on the collection data
273      *
274      * @see org.kuali.rice.krad.uif.container.ContainerBase#performApplyModel(org.kuali.rice.krad.uif.view.View,
275      *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
276      */
277     @Override
278     public void performApplyModel(View view, Object model, Component parent) {
279         super.performApplyModel(view, model, parent);
280 
281         // adds the script to the add line buttons to keep collection on the same page
282         if (this.renderAddBlankLineButton) {
283             if (this.addBlankLineAction == null) {
284                 this.addBlankLineAction = (Action) ComponentFactory.getNewComponentInstance(
285                         ComponentFactory.ADD_BLANK_LINE_ACTION);
286                 view.assignComponentIds(this.addBlankLineAction);
287             }
288 
289             if (addLinePlacement.equals(UifConstants.Position.BOTTOM.name())) {
290                 this.addBlankLineAction.setOnClickScript("writeCurrentPageToSession(this, 'last');");
291             } else {
292                 this.addBlankLineAction.setOnClickScript("writeCurrentPageToSession(this, 'first');");
293             }
294         } else if (this.addViaLightBox) {
295             if (this.addViaLightBoxAction == null) {
296                 this.addViaLightBoxAction = (Action) ComponentFactory.getNewComponentInstance(
297                         ComponentFactory.ADD_VIA_LIGHTBOX_ACTION);
298                 view.assignComponentIds(this.addViaLightBoxAction);
299             }
300 
301             if (this.addLinePlacement.equals(UifConstants.Position.BOTTOM.name())) {
302                 this.addViaLightBoxAction.setOnClickScript("writeCurrentPageToSession(this, 'last');");
303             } else {
304                 this.addViaLightBoxAction.setOnClickScript("writeCurrentPageToSession(this, 'first');");
305             }
306         }
307 
308         pushCollectionGroupToReference();
309 
310         // if rendering the collection group, build out the lines
311         if (isRender()) {
312             getCollectionGroupBuilder().build(view, model, this);
313         }
314 
315         // TODO: is this necessary to call again?
316         // This may be necessary to call in case getCollectionGroupBuilder().build resets the context map
317         pushCollectionGroupToReference();
318     }
319 
320     /**
321      * Sets a reference in the context map for all nested components to the collection group
322      * instance, and sets name as parameter for an action fields in the group
323      */
324     protected void pushCollectionGroupToReference() {
325         List<Component> components = getComponentsForLifecycle();
326         components.addAll(getComponentPrototypes());
327 
328         ComponentUtils.pushObjectToContext(components, UifConstants.ContextVariableNames.COLLECTION_GROUP, this);
329 
330         List<Action> actions = ComponentUtils.getComponentsOfTypeDeep(components, Action.class);
331         for (Action action : actions) {
332             action.addActionParameter(UifParameters.SELLECTED_COLLECTION_PATH, this.getBindingInfo().getBindingPath());
333         }
334     }
335 
336     /**
337      * New collection lines are handled in the framework by maintaining a map on
338      * the form. The map contains as a key the collection name, and as value an
339      * instance of the collection type. An entry is created here for the
340      * collection represented by the <code>CollectionGroup</code> if an instance
341      * is not available (clearExistingLine will force a new instance). The given
342      * model must be a subclass of <code>UifFormBase</code> in order to find the
343      * Map.
344      *
345      * @param model Model instance that contains the new collection lines Map
346      * @param clearExistingLine boolean that indicates whether the line should be set to a
347      * new instance if it already exists
348      */
349     public void initializeNewCollectionLine(View view, Object model, CollectionGroup collectionGroup,
350             boolean clearExistingLine) {
351         getCollectionGroupBuilder().initializeNewCollectionLine(view, model, collectionGroup, clearExistingLine);
352     }
353 
354     /**
355      * @see org.kuali.rice.krad.uif.container.ContainerBase#getComponentsForLifecycle()
356      */
357     @Override
358     public List<Component> getComponentsForLifecycle() {
359         List<Component> components = super.getComponentsForLifecycle();
360 
361         components.add(addLineLabel);
362         components.add(collectionLookup);
363         components.add(addBlankLineAction);
364         components.add(addViaLightBoxAction);
365 
366         // remove the containers items because we don't want them as children
367         // (they will become children of the layout manager as the rows are created)
368         for (Component item : getItems()) {
369             if (components.contains(item)) {
370                 components.remove(item);
371             }
372         }
373 
374         return components;
375     }
376 
377     /**
378      * @see org.kuali.rice.krad.uif.component.Component#getComponentPrototypes()
379      */
380     @Override
381     public List<Component> getComponentPrototypes() {
382         List<Component> components = super.getComponentPrototypes();
383 
384         components.addAll(lineActions);
385         components.addAll(addLineActions);
386         components.addAll(getItems());
387         components.addAll(getSubCollections());
388 
389         // iterate through addLineItems to make sure we have not already
390         // added them as prototypes (they could have been copied from add lines)
391         if (addLineItems != null) {
392             for (Component addLineItem : addLineItems) {
393                 if (!components.contains(addLineItem)) {
394                     components.add(addLineItem);
395                 }
396             }
397         }
398 
399         return components;
400     }
401 
402     /**
403      * Object class the collection maintains. Used to get dictionary information
404      * in addition to creating new instances for the collection when necessary
405      *
406      * @return collection object class
407      */
408     @BeanTagAttribute(name = "collectionObjectClass")
409     public Class<?> getCollectionObjectClass() {
410         return this.collectionObjectClass;
411     }
412 
413     /**
414      * Setter for the collection object class
415      *
416      * @param collectionObjectClass
417      */
418     public void setCollectionObjectClass(Class<?> collectionObjectClass) {
419         this.collectionObjectClass = collectionObjectClass;
420     }
421 
422     /**
423      * @see org.kuali.rice.krad.uif.component.DataBinding#getPropertyName()
424      */
425     @BeanTagAttribute(name = "propertyName")
426     public String getPropertyName() {
427         return this.propertyName;
428     }
429 
430     /**
431      * Setter for the collections property name
432      *
433      * @param propertyName
434      */
435     public void setPropertyName(String propertyName) {
436         this.propertyName = propertyName;
437     }
438 
439     /**
440      * Determines the binding path for the collection. Used to get the
441      * collection value from the model in addition to setting the binding path
442      * for the collection attributes
443      *
444      * @see org.kuali.rice.krad.uif.component.DataBinding#getBindingInfo()
445      */
446     @BeanTagAttribute(name = "bindingInfo", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
447     public BindingInfo getBindingInfo() {
448         return this.bindingInfo;
449     }
450 
451     /**
452      * Setter for the binding info instance
453      *
454      * @param bindingInfo
455      */
456     public void setBindingInfo(BindingInfo bindingInfo) {
457         this.bindingInfo = bindingInfo;
458     }
459 
460     /**
461      * Action fields that should be rendered for each collection line. Example
462      * line action is the delete action
463      *
464      * @return line action fields
465      */
466     @BeanTagAttribute(name = "lineActions", type = BeanTagAttribute.AttributeType.LISTBEAN)
467     public List<Action> getLineActions() {
468         return this.lineActions;
469     }
470 
471     /**
472      * Setter for the line action fields list
473      *
474      * @param lineActions
475      */
476     public void setLineActions(List<Action> lineActions) {
477         this.lineActions = lineActions;
478     }
479 
480     /**
481      * Indicates whether the action column for the collection should be rendered
482      *
483      * @return true if the actions should be rendered, false if not
484      * @see #getLineActions()
485      */
486     @BeanTagAttribute(name = "renderLineActions")
487     public boolean isRenderLineActions() {
488         return this.renderLineActions;
489     }
490 
491     /**
492      * Setter for the render line actions indicator
493      *
494      * @param renderLineActions
495      */
496     public void setRenderLineActions(boolean renderLineActions) {
497         this.renderLineActions = renderLineActions;
498     }
499 
500     /**
501      * Indicates whether an add line should be rendered for the collection
502      *
503      * @return true if add line should be rendered, false if it should
504      *         not be
505      */
506     @BeanTagAttribute(name = "renderAddLine")
507     public boolean isRenderAddLine() {
508         return this.renderAddLine;
509     }
510 
511     /**
512      * Setter for the render add line indicator
513      *
514      * @param renderAddLine
515      */
516     public void setRenderAddLine(boolean renderAddLine) {
517         this.renderAddLine = renderAddLine;
518     }
519 
520     /**
521      * Convenience getter for the add line label field text. The text is used to
522      * label the add line when rendered and its placement depends on the
523      * <code>LayoutManager</code>
524      *
525      * <p>
526      * For the <code>TableLayoutManager</code> the label appears in the sequence
527      * column to the left of the add line fields. For the
528      * <code>StackedLayoutManager</code> the label is placed into the group
529      * header for the line.
530      * </p>
531      *
532      * @return add line label
533      */
534     public String getAddLabel() {
535         if (getAddLineLabel() != null) {
536             return getAddLineLabel().getMessageText();
537         }
538 
539         return null;
540     }
541 
542     /**
543      * Setter for the add line label text
544      *
545      * @param addLabelText
546      */
547     public void setAddLabel(String addLabelText) {
548         if (getAddLineLabel() != null) {
549             getAddLineLabel().setMessageText(addLabelText);
550         }
551     }
552 
553     /**
554      * <code>Message</code> instance for the add line label
555      *
556      * @return add line Message
557      * @see #getAddLabel
558      */
559     @BeanTagAttribute(name = "addLineLabel", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
560     public Message getAddLineLabel() {
561         return this.addLineLabel;
562     }
563 
564     /**
565      * Setter for the <code>Message</code> instance for the add line label
566      *
567      * @param addLineLabel
568      * @see #getAddLabel
569      */
570     public void setAddLineLabel(Message addLineLabel) {
571         this.addLineLabel = addLineLabel;
572     }
573 
574     /**
575      * Name of the property that contains an instance for the add line. If set
576      * this is used with the binding info to create the path to the add line.
577      * Can be left blank in which case the framework will manage the add line
578      * instance in a generic map.
579      *
580      * @return add line property name
581      */
582     @BeanTagAttribute(name = "addLinePropertyName")
583     public String getAddLinePropertyName() {
584         return this.addLinePropertyName;
585     }
586 
587     /**
588      * Setter for the add line property name
589      *
590      * @param addLinePropertyName
591      */
592     public void setAddLinePropertyName(String addLinePropertyName) {
593         this.addLinePropertyName = addLinePropertyName;
594     }
595 
596     /**
597      * <code>BindingInfo</code> instance for the add line property used to
598      * determine the full binding path. If add line name given
599      * {@link #getAddLabel} then it is set as the binding name on the
600      * binding info. Add line label and binding info are not required, in which
601      * case the framework will manage the new add line instances through a
602      * generic map (model must extend UifFormBase)
603      *
604      * @return BindingInfo add line binding info
605      */
606     @BeanTagAttribute(name = "addLineBindingInfo", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
607     public BindingInfo getAddLineBindingInfo() {
608         return this.addLineBindingInfo;
609     }
610 
611     /**
612      * Setter for the add line binding info
613      *
614      * @param addLineBindingInfo
615      */
616     public void setAddLineBindingInfo(BindingInfo addLineBindingInfo) {
617         this.addLineBindingInfo = addLineBindingInfo;
618     }
619 
620     /**
621      * List of <code>Component</code> instances that should be rendered for the
622      * collection add line (if enabled). If not set, the default group's items
623      * list will be used
624      *
625      * @return add line field list
626      * @see CollectionGroup#performInitialization(org.kuali.rice.krad.uif.view.View, java.lang.Object)
627      */
628     @BeanTagAttribute(name = "addLineItems", type = BeanTagAttribute.AttributeType.LISTBEAN)
629     public List<? extends Component> getAddLineItems() {
630         return this.addLineItems;
631     }
632 
633     /**
634      * Setter for the add line field list
635      *
636      * @param addLineItems
637      */
638     public void setAddLineItems(List<? extends Component> addLineItems) {
639         this.addLineItems = addLineItems;
640     }
641 
642     /**
643      * Action fields that should be rendered for the add line. This is generally
644      * the add action (button) but can be configured to contain additional
645      * actions
646      *
647      * @return add line action fields
648      */
649     @BeanTagAttribute(name = "addLineActions", type = BeanTagAttribute.AttributeType.LISTBEAN)
650     public List<Action> getAddLineActions() {
651         return this.addLineActions;
652     }
653 
654     /**
655      * Setter for the add line action fields
656      *
657      * @param addLineActions
658      */
659     public void setAddLineActions(List<Action> addLineActions) {
660         this.addLineActions = addLineActions;
661     }
662 
663     /**
664      * Indicates whether lines of the collection group should be selected by rendering a
665      * field for each line that will allow selection
666      *
667      * <p>
668      * For example, having the select field enabled could allow selecting multiple lines from a search
669      * to return (multi-value lookup)
670      * </p>
671      *
672      * @return true if select field should be rendered, false if not
673      */
674     @BeanTagAttribute(name = "includeLineSelectionField")
675     public boolean isIncludeLineSelectionField() {
676         return includeLineSelectionField;
677     }
678 
679     /**
680      * Setter for the render selected field indicator
681      *
682      * @param includeLineSelectionField
683      */
684     public void setIncludeLineSelectionField(boolean includeLineSelectionField) {
685         this.includeLineSelectionField = includeLineSelectionField;
686     }
687 
688     /**
689      * When {@link #isIncludeLineSelectionField()} is true, gives the name of the property the select field
690      * should bind to
691      *
692      * <p>
693      * Note if no prefix is given in the property name, such as 'form.', it is assumed the property is
694      * contained on the collection line. In this case the binding path to the collection line will be
695      * appended. In other cases, it is assumed the property is a list or set of String that will hold the
696      * selected identifier strings
697      * </p>
698      *
699      * <p>
700      * This property is not required. If not the set the framework will use a property contained on
701      * <code>UifFormBase</code>
702      * </p>
703      *
704      * @return property name for select field
705      */
706     @BeanTagAttribute(name = "lineSelectPropertyName")
707     public String getLineSelectPropertyName() {
708         return lineSelectPropertyName;
709     }
710 
711     /**
712      * Setter for the property name that will bind to the select field
713      *
714      * @param lineSelectPropertyName
715      */
716     public void setLineSelectPropertyName(String lineSelectPropertyName) {
717         this.lineSelectPropertyName = lineSelectPropertyName;
718     }
719 
720     /**
721      * Instance of the <code>QuickFinder</code> widget that configures a multi-value lookup for the collection
722      *
723      * <p>
724      * If the collection lookup is enabled (by the render property of the quick finder), {@link
725      * #getCollectionObjectClass()} will be used as the data object class for the lookup (if not set). Field
726      * conversions need to be set as usual and will be applied for each line returned
727      * </p>
728      *
729      * @return instance configured for the collection lookup
730      */
731     @BeanTagAttribute(name = "collectionLookup", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
732     public QuickFinder getCollectionLookup() {
733         return collectionLookup;
734     }
735 
736     /**
737      * Setter for the collection lookup quickfinder instance
738      *
739      * @param collectionLookup
740      */
741     public void setCollectionLookup(QuickFinder collectionLookup) {
742         this.collectionLookup = collectionLookup;
743     }
744 
745     /**
746      * Indicates whether inactive collections lines should be displayed
747      *
748      * <p>
749      * Setting only applies when the collection line type implements the
750      * <code>Inactivatable</code> interface. If true and showInactive is
751      * set to false, the collection will be filtered to remove any items
752      * whose active status returns false
753      * </p>
754      *
755      * @return true to show inactive records, false to not render inactive records
756      */
757     @BeanTagAttribute(name = "showInactiveLines")
758     public boolean isShowInactiveLines() {
759         return showInactiveLines;
760     }
761 
762     /**
763      * Setter for the show inactive indicator
764      *
765      * @param showInactiveLines boolean show inactive
766      */
767     public void setShowInactiveLines(boolean showInactiveLines) {
768         this.showInactiveLines = showInactiveLines;
769     }
770 
771     /**
772      * Collection filter instance for filtering the collection data when the
773      * showInactive flag is set to false
774      *
775      * @return CollectionFilter
776      */
777     @BeanTagAttribute(name = "activeCollectionFilter", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
778     public CollectionFilter getActiveCollectionFilter() {
779         return activeCollectionFilter;
780     }
781 
782     /**
783      * Setter for the collection filter to use for filter inactive records from the
784      * collection
785      *
786      * @param activeCollectionFilter CollectionFilter instance
787      */
788     public void setActiveCollectionFilter(CollectionFilter activeCollectionFilter) {
789         this.activeCollectionFilter = activeCollectionFilter;
790     }
791 
792     /**
793      * List of {@link CollectionFilter} instances that should be invoked to filter the collection before
794      * displaying
795      *
796      * @return List<CollectionFilter>
797      */
798     @BeanTagAttribute(name = "filters", type = BeanTagAttribute.AttributeType.LISTBEAN)
799     public List<CollectionFilter> getFilters() {
800         return filters;
801     }
802 
803     /**
804      * Setter for the List of collection filters for which the collection will be filtered against
805      *
806      * @param filters
807      */
808     public void setFilters(List<CollectionFilter> filters) {
809         this.filters = filters;
810     }
811 
812     /**
813      * List of <code>CollectionGroup</code> instances that are sub-collections
814      * of the collection represented by this collection group
815      *
816      * @return sub collections
817      */
818     @BeanTagAttribute(name = "subCollections", type = BeanTagAttribute.AttributeType.LISTBEAN)
819     public List<CollectionGroup> getSubCollections() {
820         return this.subCollections;
821     }
822 
823     /**
824      * Setter for the sub collection list
825      *
826      * @param subCollections
827      */
828     public void setSubCollections(List<CollectionGroup> subCollections) {
829         this.subCollections = subCollections;
830     }
831 
832     /**
833      * Suffix for IDs that identifies the collection line the sub-collection belongs to
834      *
835      * <p>
836      * Built by the framework as the collection lines are being generated
837      * </p>
838      *
839      * @return id suffix for sub-collection
840      */
841     public String getSubCollectionSuffix() {
842         return subCollectionSuffix;
843     }
844 
845     /**
846      * Setter for the sub-collection suffix (used by framework, should not be
847      * set in configuration)
848      *
849      * @param subCollectionSuffix
850      */
851     public void setSubCollectionSuffix(String subCollectionSuffix) {
852         this.subCollectionSuffix = subCollectionSuffix;
853     }
854 
855     /**
856      * Collection Security object that indicates what authorization (permissions) exist for the collection
857      *
858      * @return CollectionGroupSecurity instance
859      */
860     public CollectionGroupSecurity getCollectionGroupSecurity() {
861         return (CollectionGroupSecurity) super.getComponentSecurity();
862     }
863 
864     /**
865      * Override to assert a {@link CollectionGroupSecurity} instance is set
866      *
867      * @param componentSecurity instance of CollectionGroupSecurity
868      */
869     @Override
870     public void setComponentSecurity(ComponentSecurity componentSecurity) {
871         if (!(componentSecurity instanceof CollectionGroupSecurity)) {
872             throw new RiceRuntimeException(
873                     "Component security for CollectionGroup should be instance of CollectionGroupSecurity");
874         }
875 
876         super.setComponentSecurity(componentSecurity);
877     }
878 
879     @Override
880     protected Class<? extends ComponentSecurity> getComponentSecurityClass() {
881         return CollectionGroupSecurity.class;
882     }
883 
884     /**
885      * <code>CollectionGroupBuilder</code> instance that will build the
886      * components dynamically for the collection instance
887      *
888      * @return CollectionGroupBuilder instance
889      */
890     @BeanTagAttribute(name = "collectionGroupBuilder", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
891     public CollectionGroupBuilder getCollectionGroupBuilder() {
892         if (this.collectionGroupBuilder == null) {
893             this.collectionGroupBuilder = new CollectionGroupBuilder();
894         }
895         return this.collectionGroupBuilder;
896     }
897 
898     /**
899      * Setter for the collection group building instance
900      *
901      * @param collectionGroupBuilder
902      */
903     public void setCollectionGroupBuilder(CollectionGroupBuilder collectionGroupBuilder) {
904         this.collectionGroupBuilder = collectionGroupBuilder;
905     }
906 
907     /**
908      * @param renderInactiveToggleButton the showHideInactiveButton to set
909      */
910     public void setRenderInactiveToggleButton(boolean renderInactiveToggleButton) {
911         this.renderInactiveToggleButton = renderInactiveToggleButton;
912     }
913 
914     /**
915      * @return the showHideInactiveButton
916      */
917     @BeanTagAttribute(name = "renderInactiveToggleButton")
918     public boolean isRenderInactiveToggleButton() {
919         return renderInactiveToggleButton;
920     }
921 
922     /**
923      * The number of records to display for a collection
924      *
925      * @return int
926      */
927     @BeanTagAttribute(name = "displayCollectionSize")
928     public int getDisplayCollectionSize() {
929         return this.displayCollectionSize;
930     }
931 
932     /**
933      * Setter for the display collection size
934      *
935      * @param displayCollectionSize
936      */
937     public void setDisplayCollectionSize(int displayCollectionSize) {
938         this.displayCollectionSize = displayCollectionSize;
939     }
940 
941     /**
942      * Indicates whether new items should be styled with the #newItemsCssClass
943      *
944      * @return true if new items must be highlighted
945      */
946     @BeanTagAttribute(name = "highlightNewItems")
947     public boolean isHighlightNewItems() {
948         return highlightNewItems;
949     }
950 
951     /**
952      * Setter for the flag that allows for different styling of new items
953      *
954      * @param highlightNewItems
955      */
956     public void setHighlightNewItems(boolean highlightNewItems) {
957         this.highlightNewItems = highlightNewItems;
958     }
959 
960     /**
961      * The css style class that will be added on new items
962      *
963      * @return the new items css style class
964      */
965     @BeanTagAttribute(name = "newItemsCssClass")
966     public String getNewItemsCssClass() {
967         return newItemsCssClass;
968     }
969 
970     /**
971      * Setter for the new items css style class
972      *
973      * @param newItemsCssClass
974      */
975     public void setNewItemsCssClass(String newItemsCssClass) {
976         this.newItemsCssClass = newItemsCssClass;
977     }
978 
979     /**
980      * The css style class that will be added on the add item group or row
981      *
982      * @return the add item group or row css style class
983      */
984     @BeanTagAttribute(name = "addItemCssClass")
985     public String getAddItemCssClass() {
986         return addItemCssClass;
987     }
988 
989     /**
990      * Setter for the add item css style class
991      *
992      * @param addItemCssClass
993      */
994     public void setAddItemCssClass(String addItemCssClass) {
995         this.addItemCssClass = addItemCssClass;
996     }
997 
998     /**
999      * Indicates whether the add item group or row should be styled with the #addItemCssClass
1000      *
1001      * @return true if add item group or row must be highlighted
1002      */
1003     @BeanTagAttribute(name = "highlightAddItem")
1004     public boolean isHighlightAddItem() {
1005         return highlightAddItem;
1006     }
1007 
1008     /**
1009      * Setter for the flag that allows for different styling of the add item group or row
1010      *
1011      * @param highlightAddItem
1012      */
1013     public void setHighlightAddItem(boolean highlightAddItem) {
1014         this.highlightAddItem = highlightAddItem;
1015     }
1016 
1017     /**
1018      * Indicates that a button will be rendered that allows the user to add blank lines to the collection
1019      *
1020      * <p>
1021      * The button will be added separately from the collection items. The default add line wil not be rendered. The
1022      * action of the button will call the controller, add the blank line to the collection and do a component refresh.
1023      * </p>
1024      *
1025      * @return boolean
1026      */
1027     @BeanTagAttribute(name = "renderAddBlankLineButton")
1028     public boolean isRenderAddBlankLineButton() {
1029         return renderAddBlankLineButton;
1030     }
1031 
1032     /**
1033      * Setter for the flag indicating that the add blank line button must be rendered
1034      *
1035      * @param renderAddBlankLineButton
1036      */
1037     public void setRenderAddBlankLineButton(boolean renderAddBlankLineButton) {
1038         this.renderAddBlankLineButton = renderAddBlankLineButton;
1039     }
1040 
1041     /**
1042      * The add blank line {@link Action} field rendered when renderAddBlankLineButton is true
1043      *
1044      * @return boolean
1045      */
1046     @BeanTagAttribute(name = "addBlankLineAction", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1047     public Action getAddBlankLineAction() {
1048         return addBlankLineAction;
1049     }
1050 
1051     /**
1052      * Setter for the add blank line {@link Action} field
1053      *
1054      * @param addBlankLineAction
1055      */
1056     public void setAddBlankLineAction(Action addBlankLineAction) {
1057         this.addBlankLineAction = addBlankLineAction;
1058     }
1059 
1060     /**
1061      * Indicates the add line placement
1062      *
1063      * <p>
1064      * Valid values are 'TOP' or 'BOTTOM'. The default is 'TOP'. When the value is 'BOTTOM' the blank line will be
1065      * added
1066      * to the end of the collection.
1067      * </p>
1068      *
1069      * @return the add blank line action placement
1070      */
1071     @BeanTagAttribute(name = "addLinePlacement")
1072     public String getAddLinePlacement() {
1073         return addLinePlacement;
1074     }
1075 
1076     /**
1077      * Setter for the add line placement
1078      *
1079      * @param addLinePlacement add line placement string
1080      */
1081     public void setAddLinePlacement(String addLinePlacement) {
1082         this.addLinePlacement = addLinePlacement;
1083     }
1084 
1085     /**
1086      * Indicates whether the save line actions should be rendered
1087      *
1088      * @return boolean
1089      */
1090     @BeanTagAttribute(name = "renderSaveLineActions")
1091     public boolean isRenderSaveLineActions() {
1092         return renderSaveLineActions;
1093     }
1094 
1095     /**
1096      * Setter for the flag indicating whether the save actions should be rendered
1097      *
1098      * @param renderSaveLineActions
1099      */
1100     public void setRenderSaveLineActions(boolean renderSaveLineActions) {
1101         this.renderSaveLineActions = renderSaveLineActions;
1102     }
1103 
1104     /**
1105      * Indicates that a add action should be rendered and that the add group be displayed in a lightbox
1106      *
1107      * @return boolean
1108      */
1109     @BeanTagAttribute(name = "addViaLightBox")
1110     public boolean isAddViaLightBox() {
1111         return addViaLightBox;
1112     }
1113 
1114     /**
1115      * Setter for the flag to indicate that add groups should be displayed in a light box
1116      *
1117      * @param addViaLightBox
1118      */
1119     public void setAddViaLightBox(boolean addViaLightBox) {
1120         this.addViaLightBox = addViaLightBox;
1121     }
1122 
1123     /**
1124      * The {@link Action} that will be displayed that will open the add line group in a lightbox
1125      *
1126      * @return Action
1127      */
1128     @BeanTagAttribute(name = "addViaLightBoxAction", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1129     public Action getAddViaLightBoxAction() {
1130         return addViaLightBoxAction;
1131     }
1132 
1133     /**
1134      * Setter for the add line via lightbox {@link Action}
1135      *
1136      * @param addViaLightBoxAction
1137      */
1138     public void setAddViaLightBoxAction(Action addViaLightBoxAction) {
1139         this.addViaLightBoxAction = addViaLightBoxAction;
1140     }
1141 
1142     /**
1143      * Gets useServerPaging, the flag that indicates whether server side paging is enabled.  Defaults to false.
1144      *
1145      * @return true if server side paging is enabled.
1146      */
1147     @BeanTagAttribute(name = "useServerPaging")
1148     public boolean isUseServerPaging() {
1149         return useServerPaging;
1150     }
1151 
1152     /**
1153      * Sets useServerPaging, the flag indicating whether server side paging is enabled.
1154      *
1155      * @param useServerPaging the useServerPaging value to set
1156      */
1157     public void setUseServerPaging(boolean useServerPaging) {
1158         this.useServerPaging = useServerPaging;
1159     }
1160 
1161     /**
1162      * Gets the displayStart, the index of the first item to display on the page (assuming useServerPaging is enabled).
1163      *
1164      * <p>if this field has not been set, the returned value will be -1</p>
1165      *
1166      * @return the index of the first item to display, or -1 if unset
1167      */
1168     public int getDisplayStart() {
1169         return displayStart;
1170     }
1171 
1172     /**
1173      * Sets the displayStart, the index of the first item to display on the page (assuming useServerPaging is enabled).
1174      *
1175      * @param displayStart the displayStart to set
1176      */
1177     public void setDisplayStart(int displayStart) {
1178         this.displayStart = displayStart;
1179     }
1180 
1181     /**
1182      * Gets the displayLength, the number of items to display on the page (assuming useServerPaging is enabled).
1183      *
1184      * <p>if this field has not been set, the returned value will be -1</p>
1185      *
1186      * @return the number of items to display on the page, or -1 if unset
1187      */
1188     public int getDisplayLength() {
1189         return displayLength;
1190     }
1191 
1192     /**
1193      * Sets the displayLength, the number of items to display on the page (assuming useServerPaging is enabled).
1194      *
1195      * @param displayLength the displayLength to set
1196      */
1197     public void setDisplayLength(int displayLength) {
1198         this.displayLength = displayLength;
1199     }
1200 
1201     /**
1202      * Gets the number of un-filtered elements from the model collection.
1203      *
1204      * <p>if this field has not been set, the returned value will be -1</p>
1205      *
1206      * @return the filtered collection size, or -1 if unset
1207      */
1208     public int getFilteredCollectionSize() {
1209         return filteredCollectionSize;
1210     }
1211 
1212     /**
1213      * Sets the number of un-filtered elements from the model collection.
1214      *
1215      * <p>This value is used for display and rendering purposes, it has no effect on the model collection</p>
1216      *
1217      * @param filteredCollectionSize the filtered collection size
1218      */
1219     public void setFilteredCollectionSize(int filteredCollectionSize) {
1220         this.filteredCollectionSize = filteredCollectionSize;
1221     }
1222 
1223     /**
1224      *
1225      * @return list of total columns
1226      */
1227     @BeanTagAttribute(name = "addTotalColumns")
1228     protected List<String> getTotalColumns() {
1229         return totalColumns;
1230     }
1231 
1232     /**
1233      * Setter for the total columns
1234      *
1235      * @param totalColumns
1236      */
1237     protected void setTotalColumns(List<String> totalColumns) {
1238         this.totalColumns = totalColumns;
1239     }
1240 
1241     /**
1242      * @see org.kuali.rice.krad.uif.component.Component#completeValidation
1243      */
1244     @Override
1245     public void completeValidation(ValidationTrace tracer) {
1246         tracer.addBean(this);
1247 
1248         // Checking if collectionObjectClass is set
1249         if (getCollectionObjectClass() == null) {
1250             if (Validator.checkExpressions(this, "collectionObjectClass")) {
1251                 String currentValues[] = {"collectionObjectClass = " + getCollectionObjectClass()};
1252                 tracer.createWarning("CollectionObjectClass is not set (disregard if part of an abstract)",
1253                         currentValues);
1254             }
1255         }
1256 
1257         super.completeValidation(tracer.getCopy());
1258     }
1259 
1260     /**
1261      * @see org.kuali.rice.krad.uif.component.ComponentBase#copy()
1262      */
1263     @Override
1264     protected <T> void copyProperties(T component) {
1265         super.copyProperties(component);
1266 
1267         CollectionGroup collectionGroupCopy = (CollectionGroup) component;
1268 
1269         collectionGroupCopy.setDisplayCollectionSize(this.displayCollectionSize);
1270         collectionGroupCopy.setActiveCollectionFilter(this.activeCollectionFilter);
1271 
1272         if (this.addBlankLineAction != null) {
1273             collectionGroupCopy.setAddBlankLineAction((Action) this.addBlankLineAction.copy());
1274         }
1275 
1276         collectionGroupCopy.setAddItemCssClass(this.addItemCssClass);
1277 
1278         if (addLineItems != null) {
1279             List<Component> addLineItemsCopy = new ArrayList<Component>();
1280 
1281             for (Component addLineItem : this.addLineItems) {
1282                 addLineItemsCopy.add((Component) addLineItem.copy());
1283             }
1284 
1285             collectionGroupCopy.setAddLineItems(addLineItemsCopy);
1286         }
1287 
1288         if (addLineActions != null) {
1289             List<Action> addLineActionsCopy = new ArrayList<Action>();
1290 
1291             for (Action addLineAction : this.addLineActions) {
1292                 addLineActionsCopy.add((Action) addLineAction.copy());
1293             }
1294 
1295             collectionGroupCopy.setAddLineActions(addLineActionsCopy);
1296         }
1297 
1298         if (this.addLineBindingInfo != null) {
1299             collectionGroupCopy.setAddLineBindingInfo((BindingInfo) this.addLineBindingInfo.copy());
1300         }
1301 
1302         if (this.addLineLabel != null) {
1303             collectionGroupCopy.setAddLineLabel((Message) this.addLineLabel.copy());
1304         }
1305 
1306         collectionGroupCopy.setAddLinePlacement(this.addLinePlacement);
1307         collectionGroupCopy.setAddLinePropertyName(this.addLinePropertyName);
1308         collectionGroupCopy.setAddViaLightBox(this.addViaLightBox);
1309 
1310         if (this.addViaLightBoxAction != null) {
1311             collectionGroupCopy.setAddViaLightBoxAction((Action) this.addViaLightBoxAction.copy());
1312         }
1313 
1314         if (this.bindingInfo != null) {
1315             collectionGroupCopy.setBindingInfo((BindingInfo) this.bindingInfo.copy());
1316         }
1317 
1318         if (this.collectionLookup != null) {
1319             collectionGroupCopy.setCollectionLookup((QuickFinder) this.collectionLookup.copy());
1320         }
1321 
1322         collectionGroupCopy.setCollectionObjectClass(this.collectionObjectClass);
1323         collectionGroupCopy.setFilters(new ArrayList<CollectionFilter>(this.filters));
1324         collectionGroupCopy.setHighlightAddItem(this.highlightAddItem);
1325         collectionGroupCopy.setHighlightNewItems(this.highlightNewItems);
1326         collectionGroupCopy.setIncludeLineSelectionField(this.includeLineSelectionField);
1327         collectionGroupCopy.setUseServerPaging(this.useServerPaging);
1328 
1329         if (lineActions != null) {
1330             List<Action> lineActions = new ArrayList<Action>();
1331             for (Action lineAction : this.lineActions) {
1332                 lineActions.add((Action) lineAction.copy());
1333             }
1334 
1335             collectionGroupCopy.setLineActions(lineActions);
1336         }
1337         collectionGroupCopy.setLineSelectPropertyName(this.lineSelectPropertyName);
1338         collectionGroupCopy.setNewItemsCssClass(this.newItemsCssClass);
1339         collectionGroupCopy.setPropertyName(this.propertyName);
1340         collectionGroupCopy.setRenderAddBlankLineButton(this.renderAddBlankLineButton);
1341         collectionGroupCopy.setRenderAddLine(this.renderAddLine);
1342         collectionGroupCopy.setRenderInactiveToggleButton(this.renderInactiveToggleButton);
1343         collectionGroupCopy.setActiveCollectionFilter(this.activeCollectionFilter);
1344         collectionGroupCopy.setFilters(this.filters);
1345 
1346         collectionGroupCopy.setRenderLineActions(this.renderLineActions);
1347         collectionGroupCopy.setRenderSaveLineActions(this.renderSaveLineActions);
1348         collectionGroupCopy.setShowInactiveLines(this.showInactiveLines);
1349 
1350         if (subCollections != null) {
1351             List<CollectionGroup> subCollectionsCopy = new ArrayList<CollectionGroup>();
1352             for (CollectionGroup subCollection : this.subCollections) {
1353                 subCollectionsCopy.add((CollectionGroup) subCollection.copy());
1354             }
1355 
1356             collectionGroupCopy.setSubCollections(subCollectionsCopy);
1357         }
1358         collectionGroupCopy.setSubCollectionSuffix(this.subCollectionSuffix);
1359 
1360         if (this.totalColumns != null) {
1361             collectionGroupCopy.setTotalColumns(new ArrayList<String>(this.totalColumns));
1362         }
1363     }
1364 }