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