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.layout;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.krad.datadictionary.parse.BeanTag;
020    import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
021    import org.kuali.rice.krad.datadictionary.parse.BeanTags;
022    import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
023    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
024    import org.kuali.rice.krad.uif.UifConstants;
025    import org.kuali.rice.krad.uif.UifPropertyPaths;
026    import org.kuali.rice.krad.uif.component.Component;
027    import org.kuali.rice.krad.uif.component.DataBinding;
028    import org.kuali.rice.krad.uif.component.KeepExpression;
029    import org.kuali.rice.krad.uif.container.CollectionGroup;
030    import org.kuali.rice.krad.uif.container.Container;
031    import org.kuali.rice.krad.uif.container.Group;
032    import org.kuali.rice.krad.uif.element.Action;
033    import org.kuali.rice.krad.uif.element.Label;
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.field.FieldGroup;
037    import org.kuali.rice.krad.uif.field.InputField;
038    import org.kuali.rice.krad.uif.field.MessageField;
039    import org.kuali.rice.krad.uif.service.ExpressionEvaluatorService;
040    import org.kuali.rice.krad.uif.util.ColumnCalculationInfo;
041    import org.kuali.rice.krad.uif.util.ComponentFactory;
042    import org.kuali.rice.krad.uif.util.ComponentUtils;
043    import org.kuali.rice.krad.uif.util.ExpressionUtils;
044    import org.kuali.rice.krad.uif.view.View;
045    import org.kuali.rice.krad.uif.widget.RichTable;
046    import org.kuali.rice.krad.web.form.UifFormBase;
047    
048    import java.util.ArrayList;
049    import java.util.List;
050    import java.util.Set;
051    import java.util.TreeMap;
052    
053    /**
054     * Layout manager that works with {@code CollectionGroup} components and
055     * renders the collection as a Table
056     *
057     * <p>
058     * Based on the fields defined, the {@code TableLayoutManager} will
059     * dynamically create instances of the fields for each collection row. In
060     * addition, the manager can create standard fields like the action and sequence
061     * fields for each row. The manager supports options inherited from the
062     * {@code GridLayoutManager} such as rowSpan, colSpan, and cell width
063     * settings.
064     * </p>
065     *
066     * @author Kuali Rice Team (rice.collab@kuali.org)
067     */
068    @BeanTag(name = "tableCollectionLayout-bean", parent = "Uif-TableCollectionLayout")
069    public class TableLayoutManager extends GridLayoutManager implements CollectionLayoutManager {
070        private static final long serialVersionUID = 3622267585541524208L;
071    
072        private boolean useShortLabels;
073        private boolean repeatHeader;
074        private Label headerLabelPrototype;
075    
076        private boolean renderSequenceField;
077        private boolean generateAutoSequence;
078        private Field sequenceFieldPrototype;
079    
080        private FieldGroup actionFieldPrototype;
081        private FieldGroup subCollectionFieldGroupPrototype;
082        private Field selectFieldPrototype;
083    
084        private boolean separateAddLine;
085        private Group addLineGroup;
086    
087        // internal counter for the data columns (not including sequence, action)
088        private int numberOfDataColumns;
089    
090        private List<Label> headerLabels;
091        private List<Component> dataFields;
092    
093        private RichTable richTable;
094        private boolean headerAdded;
095    
096        private int actionColumnIndex = -1;
097        private String actionColumnPlacement;
098    
099        //row details properties
100        private Group rowDetailsGroup;
101        private String rowDetailsLinkName = "Details";
102        private boolean rowDetailsSwapActionImage;
103        private boolean rowDetailsOpen;
104        private boolean showToggleAllDetails;
105        private Action toggleAllDetailsAction;
106        private boolean ajaxDetailsRetrieval;
107        private Action expandDetailsActionPrototype;
108    
109        //grouping properties
110        @KeepExpression
111        private String groupingTitle;
112        private String groupingPrefix;
113        private int groupingColumnIndex;
114        private List<String> groupingPropertyNames;
115    
116        //total properties
117        private boolean renderOnlyLeftTotalLabels;
118        private boolean showTotal;
119        private boolean showPageTotal;
120        private boolean showGroupTotal;
121        private boolean generateGroupTotalRows;
122        private Label totalLabel;
123        private Label pageTotalLabel;
124        private Label groupTotalLabelPrototype;
125    
126        private List<String> columnsToCalculate;
127        private List<ColumnCalculationInfo> columnCalculations;
128        private List<Component> footerCalculationComponents;
129    
130        public TableLayoutManager() {
131            useShortLabels = false;
132            repeatHeader = false;
133            renderSequenceField = true;
134            generateAutoSequence = false;
135            separateAddLine = false;
136            rowDetailsOpen = false;
137    
138            headerLabels = new ArrayList<Label>();
139            dataFields = new ArrayList<Component>();
140            columnsToCalculate = new ArrayList<String>();
141            columnCalculations = new ArrayList<ColumnCalculationInfo>();
142        }
143    
144        /**
145         * The following actions are performed:
146         *
147         * <ul>
148         * <li>Sets sequence field prototype if auto sequence is true</li>
149         * <li>Initializes the prototypes</li>
150         * </ul>
151         *
152         * @see org.kuali.rice.krad.uif.layout.BoxLayoutManager#performInitialization(org.kuali.rice.krad.uif.view.View,
153         *      java.lang.Object, org.kuali.rice.krad.uif.container.Container)
154         */
155        @Override
156        public void performInitialization(View view, Object model, Container container) {
157            CollectionGroup collectionGroup = (CollectionGroup) container;
158    
159            this.setupDetails(collectionGroup, view);
160            this.setupGrouping(collectionGroup, view);
161    
162            if (collectionGroup.isAddViaLightBox()) {
163                setSeparateAddLine(true);
164            }
165    
166            super.performInitialization(view, model, container);
167    
168            getRowCssClasses().clear();
169    
170            if (generateAutoSequence && !(getSequenceFieldPrototype() instanceof MessageField)) {
171                sequenceFieldPrototype = ComponentFactory.getMessageField();
172                view.assignComponentIds(getSequenceFieldPrototype());
173            }
174        }
175    
176        /**
177         * performApplyModel override.  Takes expressions that may be set in the columnCalculation objects
178         * and populates them correctly into those component's propertyExpressions.
179         *
180         * @param view view instance to which the layout manager belongs
181         * @param model Top level object containing the data (could be the form or a
182         * top level business object, dto)
183         * @param container
184         */
185        @Override
186        public void performApplyModel(View view, Object model, Container container) {
187            super.performApplyModel(view, model, container);
188    
189            for (ColumnCalculationInfo cInfo : columnCalculations) {
190                ExpressionUtils.populatePropertyExpressionsFromGraph(cInfo, false);
191            }
192        }
193    
194        /**
195         * Sets up the final column count for rendering based on whether the
196         * sequence and action fields have been generated, sets up column calculations, and richTable rowGrouping
197         * options
198         *
199         * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#performFinalize(org.kuali.rice.krad.uif.view.View,
200         *      java.lang.Object, org.kuali.rice.krad.uif.container.Container)
201         */
202        @Override
203        public void performFinalize(View view, Object model, Container container) {
204            super.performFinalize(view, model, container);
205    
206            UifFormBase formBase = (UifFormBase) model;
207    
208            CollectionGroup collectionGroup = (CollectionGroup) container;
209    
210            int totalColumns = getNumberOfDataColumns();
211            if (renderSequenceField) {
212                totalColumns++;
213            }
214    
215            if (collectionGroup.isIncludeLineSelectionField()) {
216                totalColumns++;
217            }
218    
219            if (collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly()) {
220                totalColumns++;
221            }
222    
223            setNumberOfColumns(totalColumns);
224    
225            // if add line event, add highlighting for added row
226            if (UifConstants.ActionEvents.ADD_LINE.equals(formBase.getActionEvent())) {
227                String highlightScript = "jQuery(\"#" + container.getId() + " tr:first\").effect(\"highlight\",{}, 6000);";
228                String onReadyScript = collectionGroup.getOnDocumentReadyScript();
229                if (StringUtils.isNotBlank(onReadyScript)) {
230                    highlightScript = onReadyScript + highlightScript;
231                }
232                collectionGroup.setOnDocumentReadyScript(highlightScript);
233            }
234    
235            //setup the column calculations functionality and components
236            if (columnCalculations != null && richTable != null &&
237                    this.getDataFields() != null && !this.getDataFields().isEmpty()) {
238                setupColumnCalculations(view, model, container, totalColumns);
239            }
240    
241            //set the js properties for rowGrouping on richTables
242            if ((groupingPropertyNames != null || StringUtils.isNotBlank(this.getGroupingTitle())) && richTable != null) {
243                richTable.setGroupingOptionsJSString("{iGroupingColumnIndex: "
244                        + groupingColumnIndex
245                        + ", bGenerateGroupTotalRows:"
246                        + this.generateGroupTotalRows
247                        + ", bSetGroupingClassOnTR: true"
248                        + ", sGroupingClass: 'uif-groupRow'"
249                        + (this.getGroupingPrefix() != null ? ", sGroupLabelPrefix: '" + this.getGroupingPrefix() + "'" :
250                        "")
251                        + "}");
252            }
253        }
254    
255        /**
256         * Sets up the grouping MessageField to be used in the first column of the table layout for grouping
257         * collection content into groups based on values of the line's fields
258         *
259         * @param collectionGroup collection group for this layout
260         * @param view the view
261         */
262        private void setupGrouping(CollectionGroup collectionGroup, View view) {
263            //Grouping setup
264            String groupingTitleExpression = "";
265            if (StringUtils.isNotBlank(this.getPropertyExpression("groupingTitle"))) {
266                groupingTitleExpression = this.getPropertyExpression("groupingTitle");
267                this.setGroupingTitle(this.getPropertyExpression("groupingTitle"));
268            } else if (this.getGroupingPropertyNames() != null) {
269    
270                for (String propertyName : this.getGroupingPropertyNames()) {
271                    groupingTitleExpression = groupingTitleExpression + ", " + propertyName;
272                }
273    
274                groupingTitleExpression = groupingTitleExpression.replaceFirst(", ", "@{#lp.");
275                groupingTitleExpression = groupingTitleExpression.replace(", ", "}, @{#lp.");
276                groupingTitleExpression = groupingTitleExpression.trim() + "}";
277            }
278    
279            if (StringUtils.isNotBlank(groupingTitleExpression)) {
280                MessageField groupingMessageField = ComponentFactory.getColGroupingField();
281                groupingMessageField.getMessage().getPropertyExpressions().put("messageText", groupingTitleExpression);
282                groupingMessageField.setLabel("Group");
283    
284                view.assignComponentIds(groupingMessageField);
285    
286                List<Component> theItems = new ArrayList<Component>();
287                theItems.add(groupingMessageField);
288                theItems.addAll(collectionGroup.getItems());
289                collectionGroup.setItems(theItems);
290            }
291        }
292    
293        /**
294         * Setup the column calculations functionality and components
295         *
296         * @param view the view
297         * @param model the model
298         * @param container the parent container
299         * @param totalColumns total number of columns in the table
300         */
301        protected void setupColumnCalculations(View view, Object model, Container container, int totalColumns) {
302            footerCalculationComponents = new ArrayList<Component>(totalColumns);
303    
304            //add nulls for each column to start - nulls will be processed by the ftl as a blank cell
305            for (int i = 0; i < totalColumns; i++) {
306                footerCalculationComponents.add(null);
307            }
308    
309            int leftLabelColumnIndex = 0;
310            if (groupingPropertyNames != null || StringUtils.isNotBlank(this.getGroupingTitle())) {
311                leftLabelColumnIndex = 1;
312            }
313    
314            //process each column calculation
315            for (ColumnCalculationInfo cInfo : columnCalculations) {
316                //propertyName is REQUIRED throws exception if not set
317                if (StringUtils.isNotBlank(cInfo.getPropertyName())) {
318                    for (int i = 0; i < this.getNumberOfColumns(); i++) {
319                        Component component = this.getDataFields().get(i);
320                        if (component != null && component instanceof DataField &&
321                                ((DataField) component).getPropertyName().equals(cInfo.getPropertyName())) {
322                            cInfo.setColumnNumber(i);
323                        }
324                    }
325                    this.getColumnsToCalculate().add(cInfo.getColumnNumber().toString());
326                } else {
327                    throw new RuntimeException("TableLayoutManager(" + container.getId() + "->" + this.getId() +
328                            ") ColumnCalculationInfo MUST have a propertyName set");
329                }
330    
331                FieldGroup calculationFieldGroup;
332                List<Component> groupItems;
333                Component columnComponent = footerCalculationComponents.get(cInfo.getColumnNumber());
334    
335                //Create a new field group to hold the totals fields
336                calculationFieldGroup = ComponentFactory.getFieldGroup();
337                calculationFieldGroup.addDataAttribute("role", "totalsBlock");
338                groupItems = new ArrayList<Component>();
339    
340                //setup page total field and add it to footer's group for this column
341                if (cInfo.isShowPageTotal()) {
342                    Field pageTotalDataField = setupTotalField(cInfo.getPageTotalField(), cInfo, this.isShowPageTotal(),
343                            this.getPageTotalLabel(), "pageTotal", leftLabelColumnIndex);
344                    groupItems.add(pageTotalDataField);
345                }
346    
347                //setup total field and add it to footer's group for this column
348                if (cInfo.isShowTotal()) {
349                    Field totalDataField = setupTotalField(cInfo.getTotalField(), cInfo, this.isShowTotal(),
350                            this.getTotalLabel(), "total", leftLabelColumnIndex);
351                    /*                if(((MessageField)totalDataField).getMessage().getMessageText().contains("@{")){
352                        ((MessageField)totalDataField).getMessage().getPropertyExpressions().put("messageText",
353                                ((MessageField)totalDataField).getMessage().getMessageText());
354                    }*/
355                    if (!cInfo.isRecalculateTotalClientside()) {
356                        totalDataField.addDataAttribute("skipTotal", "true");
357                    }
358    
359                    groupItems.add(totalDataField);
360                }
361    
362                //setup total field and add it to footer's group for this column
363                //do not generate group total rows if group totals are not being shown
364                if (cInfo.isShowGroupTotal()) {
365                    Field groupTotalDataField = setupTotalField(cInfo.getGroupTotalFieldPrototype(), cInfo,
366                            this.isShowGroupTotal(), this.getGroupTotalLabelPrototype(), "groupTotal",
367                            leftLabelColumnIndex);
368                    groupTotalDataField.setId(container.getId() + "_gTotal" + cInfo.getColumnNumber());
369                    groupTotalDataField.setStyle("display: none;");
370                    groupItems.add(groupTotalDataField);
371    
372                    if (this.isRenderOnlyLeftTotalLabels() && !this.isShowGroupTotal()) {
373                        generateGroupTotalRows = false;
374                    } else {
375                        generateGroupTotalRows = true;
376                    }
377                }
378    
379                calculationFieldGroup.setItems(groupItems);
380                view.assignComponentIds(calculationFieldGroup);
381    
382                //Determine if there is already a fieldGroup present for this column's footer
383                //if so create a new group and add the new calculation fields to the already existing ones
384                //otherwise just add it
385                Component component = footerCalculationComponents.get(cInfo.getColumnNumber());
386                if (component != null && component instanceof FieldGroup) {
387                    Group verticalComboCalcGroup = ComponentFactory.getVerticalBoxGroup();
388                    view.assignComponentIds(verticalComboCalcGroup);
389                    List<Component> comboGroupItems = new ArrayList<Component>();
390                    comboGroupItems.add(component);
391                    comboGroupItems.add(calculationFieldGroup);
392                    verticalComboCalcGroup.setItems(comboGroupItems);
393                    footerCalculationComponents.set(cInfo.getColumnNumber(), verticalComboCalcGroup);
394                } else if (component != null && component instanceof Group) {
395                    List<Component> comboGroupItems = new ArrayList<Component>();
396                    comboGroupItems.addAll(((Group) component).getItems());
397                    comboGroupItems.add(calculationFieldGroup);
398                    ((Group) component).setItems(comboGroupItems);
399                    footerCalculationComponents.set(cInfo.getColumnNumber(), component);
400                } else {
401                    footerCalculationComponents.set(cInfo.getColumnNumber(), calculationFieldGroup);
402                }
403            }
404    
405            //special processing for the left labels - when there are no total fields in this column
406            //add the label to the column footer directly
407            if (this.renderOnlyLeftTotalLabels && footerCalculationComponents.get(leftLabelColumnIndex) == null) {
408                Group labelGroup = ComponentFactory.getVerticalBoxGroup();
409                view.assignComponentIds(labelGroup);
410                List<Component> groupItems = new ArrayList<Component>();
411    
412                if (this.isShowGroupTotal()) {
413                    //display none - this label is copied by the javascript
414                    groupTotalLabelPrototype.setStyle("display: none;");
415                    groupTotalLabelPrototype.addDataAttribute("role", "groupTotalLabel");
416                    view.assignComponentIds(groupTotalLabelPrototype);
417                    groupItems.add(groupTotalLabelPrototype);
418                }
419    
420                if (this.isShowPageTotal()) {
421                    view.assignComponentIds(pageTotalLabel);
422                    pageTotalLabel.addDataAttribute("role", "pageTotal");
423                    groupItems.add(pageTotalLabel);
424                }
425    
426                if (this.isShowTotal()) {
427                    view.assignComponentIds(totalLabel);
428                    groupItems.add(totalLabel);
429                }
430    
431                labelGroup.setItems(groupItems);
432    
433                footerCalculationComponents.set(leftLabelColumnIndex, labelGroup);
434            }
435    
436            //perform the lifecycle for all the newly generated components as a result of processing the
437            //column calculations
438            for (Component component : footerCalculationComponents) {
439                if (component != null) {
440                    component.performInitialization(view, model);
441                    component.performApplyModel(view, model, container);
442                    component.performFinalize(view, model, container);
443                }
444            }
445    
446        }
447    
448        /**
449         * Setup the totalField with the columnCalculationInfo(cInfo) passed in.  Param show represents the
450         * tableLayoutManager's setting for the type of total being processed.
451         *
452         * @param totalField the field to setup
453         * @param cInfo ColumnCalculation info to use to setup the field
454         * @param show show the field (if renderOnlyLeftTotalLabels is true, otherwise uses value in cInfo)
455         * @param leftLabel the leftLabel, not used if renderOnlyLeftTotalLabels is false
456         * @param type type used to set the dataAttribute role - used by the js for selection
457         * @param leftLabelColumnIndex index of the leftLabelColumn (0 or 1 if grouping enabled - hidden column)
458         * @return the field with cInfo and tableLayoutManager settings applied as appropriate
459         */
460        protected Field setupTotalField(Field totalField, ColumnCalculationInfo cInfo, boolean show, Label leftLabel,
461                String type, int leftLabelColumnIndex) {
462            //setup the totals field
463            Field totalDataField = totalField;
464            totalDataField.addDataAttribute("role", type);
465            totalDataField.addDataAttribute("function", cInfo.getCalculationFunctionName());
466            totalDataField.addDataAttribute("params", cInfo.getCalculationFunctionExtraData());
467    
468            if (cInfo.getColumnNumber() != leftLabelColumnIndex) {
469                //do not render labels for columns which have totals and the renderOnlyLeftTotalLabels
470                //flag is set
471                totalDataField.getFieldLabel().setRender(!this.isRenderOnlyLeftTotalLabels());
472            } else if (cInfo.getColumnNumber() == leftLabelColumnIndex && this.isRenderOnlyLeftTotalLabels()) {
473                //renderOnlyLeftTotalLabel is set to true, but the column has a total itself - set the layout
474                //manager settings directly into the field
475                totalDataField.setFieldLabel(leftLabel);
476            }
477    
478            if (this.isRenderOnlyLeftTotalLabels()) {
479                totalDataField.setRender(show);
480            }
481    
482            return totalDataField;
483        }
484    
485        /**
486         * Assembles the field instances for the collection line. The given sequence
487         * field prototype is copied for the line sequence field. Likewise a copy of
488         * the actionFieldPrototype is made and the given actions are set as the
489         * items for the action field. Finally the generated items are assembled
490         * together into the dataFields list with the given lineFields.
491         *
492         * @see org.kuali.rice.krad.uif.layout.CollectionLayoutManager#buildLine(org.kuali.rice.krad.uif.view.View,
493         *      java.lang.Object, org.kuali.rice.krad.uif.container.CollectionGroup,
494         *      java.util.List, java.util.List, java.lang.String, java.util.List,
495         *      java.lang.String, java.lang.Object, int)
496         */
497        public void buildLine(View view, Object model, CollectionGroup collectionGroup, List<Field> lineFields,
498                List<FieldGroup> subCollectionFields, String bindingPath, List<Action> actions, String idSuffix,
499                Object currentLine, int lineIndex) {
500    
501            // since expressions are not evaluated on child components yet, we need to evaluate any properties
502            // we are going to read for building the table
503            ExpressionEvaluatorService expressionEvaluatorService = KRADServiceLocatorWeb.getExpressionEvaluatorService();
504            for (Field lineField : lineFields) {
505                lineField.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, collectionGroup);
506                lineField.pushAllToContext(view.getViewHelperService().getCommonContext(view, lineField));
507    
508                expressionEvaluatorService.evaluatePropertyExpression(view, model, lineField.getContext(), lineField,
509                        UifPropertyPaths.ROW_SPAN, true);
510                expressionEvaluatorService.evaluatePropertyExpression(view, model, lineField.getContext(), lineField,
511                        UifPropertyPaths.COL_SPAN, true);
512                expressionEvaluatorService.evaluatePropertyExpression(view, model, lineField.getContext(), lineField,
513                        UifPropertyPaths.REQUIRED, true);
514                expressionEvaluatorService.evaluatePropertyExpression(view, model, lineField.getContext(), lineField,
515                        UifPropertyPaths.READ_ONLY, true);
516            }
517    
518            // if first line for table set number of data columns
519            if (dataFields.isEmpty()) {
520                if (isSuppressLineWrapping()) {
521                    setNumberOfDataColumns(lineFields.size());
522                } else {
523                    setNumberOfDataColumns(getNumberOfColumns());
524                }
525            }
526    
527            boolean isAddLine = false;
528    
529            // If first row or row wrap is happening
530            if (lineIndex == -1 || (lineFields.size() != numberOfDataColumns
531                    && ((lineIndex + 1) * numberOfDataColumns) < lineFields.size())) {
532                isAddLine = true;
533            }
534    
535            boolean renderActions = collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly();
536            int extraColumns = 0;
537    
538            if (collectionGroup.isHighlightNewItems() && ((UifFormBase) model).isAddedCollectionItem(currentLine)) {
539                getRowCssClasses().add(collectionGroup.getNewItemsCssClass());
540            } else if (isAddLine && collectionGroup.isRenderAddLine() && !collectionGroup.isReadOnly()
541                    && !isSeparateAddLine()) {
542                getRowCssClasses().add(collectionGroup.getAddItemCssClass());
543                this.addStyleClass("uif-hasAddLine");
544            } else if (lineIndex != -1) {
545                getRowCssClasses().add("");
546            }
547    
548            // if separate add line prepare the add line group
549            if (isAddLine && separateAddLine) {
550                if (StringUtils.isBlank(addLineGroup.getTitle()) && StringUtils.isBlank(
551                        addLineGroup.getHeader().getHeaderText())) {
552                    addLineGroup.getHeader().setHeaderText(collectionGroup.getAddLabel());
553                }
554                addLineGroup.setItems(lineFields);
555    
556                List<Component> footerItems = new ArrayList<Component>(actions);
557                footerItems.addAll(addLineGroup.getFooter().getItems());
558                addLineGroup.getFooter().setItems(footerItems);
559    
560                if (collectionGroup.isAddViaLightBox()) {
561                    String actionScript = "showLightboxComponent('" + addLineGroup.getId() + "');";
562                    if (StringUtils.isNotBlank(collectionGroup.getAddViaLightBoxAction().getActionScript())) {
563                        actionScript = collectionGroup.getAddViaLightBoxAction().getActionScript() + actionScript;
564                    }
565                    collectionGroup.getAddViaLightBoxAction().setActionScript(actionScript);
566                    addLineGroup.setStyle("display: none");
567                }
568    
569                return;
570            }
571    
572            // TODO: implement repeat header
573            if (!headerAdded) {
574                headerLabels = new ArrayList<Label>();
575                dataFields = new ArrayList<Component>();
576    
577                buildTableHeaderRows(collectionGroup, lineFields);
578                ComponentUtils.pushObjectToContext(headerLabels, UifConstants.ContextVariableNames.LINE, currentLine);
579                ComponentUtils.pushObjectToContext(headerLabels, UifConstants.ContextVariableNames.INDEX, new Integer(
580                        lineIndex));
581                headerAdded = true;
582            }
583    
584            // set label field rendered to true on line fields and adjust cell properties
585            for (Field field : lineFields) {
586                field.setLabelRendered(true);
587                field.setFieldLabel(null);
588    
589                setCellAttributes(field);
590            }
591    
592            int rowCount = calculateNumberOfRows(lineFields);
593            int rowSpan = rowCount + subCollectionFields.size();
594    
595            if (actionColumnIndex == 1 && renderActions) {
596                addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions);
597            }
598    
599            // sequence field is always first and should span all rows for the line
600            if (renderSequenceField) {
601                Component sequenceField = null;
602                if (!isAddLine) {
603                    sequenceField = ComponentUtils.copy(getSequenceFieldPrototype(), idSuffix);
604    
605                    //Ignore in validation processing
606                    sequenceField.addDataAttribute(UifConstants.DataAttributes.VIGNORE, "yes");
607    
608                    if (generateAutoSequence && (sequenceField instanceof MessageField)) {
609                        ((MessageField) sequenceField).setMessageText(Integer.toString(lineIndex + 1));
610                    }
611                } else {
612                    sequenceField = ComponentUtils.copy(collectionGroup.getAddLineLabel(), idSuffix);
613                    
614                    // adjusting add line label to match sequence prototype cells attributes
615                    sequenceField.setCellWidth(getSequenceFieldPrototype().getCellWidth());
616                    sequenceField.setCellStyle(getSequenceFieldPrototype().getCellStyle());
617                }
618    
619                sequenceField.setRowSpan(rowSpan);
620    
621                if (sequenceField instanceof DataBinding) {
622                    ((DataBinding) sequenceField).getBindingInfo().setBindByNamePrefix(bindingPath);
623                }
624    
625                setCellAttributes(sequenceField);
626    
627                ComponentUtils.updateContextForLine(sequenceField, currentLine, lineIndex, idSuffix);
628                dataFields.add(sequenceField);
629                extraColumns++;
630    
631                if (actionColumnIndex == 2 && renderActions) {
632                    addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions);
633                }
634            }
635    
636            // select field will come after sequence field (if enabled) or be first column
637            if (collectionGroup.isIncludeLineSelectionField()) {
638                Field selectField = ComponentUtils.copy(getSelectFieldPrototype(), idSuffix);
639                CollectionLayoutUtils.prepareSelectFieldForLine(selectField, collectionGroup, bindingPath, currentLine);
640    
641                ComponentUtils.updateContextForLine(selectField, currentLine, lineIndex, idSuffix);
642                setCellAttributes(selectField);
643    
644                dataFields.add(selectField);
645    
646                extraColumns++;
647    
648                if (renderActions) {
649                    if ((actionColumnIndex == 3 && renderSequenceField) || (actionColumnIndex == 2
650                            && !renderSequenceField)) {
651                        addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions);
652                    }
653                }
654            }
655    
656            // now add the fields in the correct position
657            int cellPosition = 0;
658            int columnNumber = 0;
659    
660            boolean renderActionsLast = actionColumnIndex == -1 || actionColumnIndex > lineFields.size() + extraColumns;
661            boolean hasGrouping = (groupingPropertyNames != null || StringUtils.isNotBlank(this.getGroupingTitle()));
662            boolean insertActionField = false;
663    
664            for (Field lineField : lineFields) {
665                //Check to see if ActionField needs to be inserted before this lineField because of wrapping.
666                // Since actionField has a colSpan of 1 add that to the previous cellPosition instead of the
667                // current lineField's colSpan.
668                // Only insert if ActionField has to be placed at the end. Else the specification of actionColumnIndex should
669                // take care of putting it in the right location
670                insertActionField = (cellPosition != 0 && lineFields.size() != numberOfDataColumns) && renderActions
671                        && renderActionsLast && ((cellPosition % numberOfDataColumns) == 0);
672    
673                cellPosition += lineField.getColSpan();
674                columnNumber++;
675    
676                //special handling for grouping field - this field MUST be first
677                if (hasGrouping && lineField instanceof MessageField &&
678                        lineField.getDataAttributes().get("role") != null && lineField.getDataAttributes().get("role")
679                        .equals("grouping")) {
680                    int groupFieldIndex = dataFields.size() - extraColumns;
681                    dataFields.add(groupFieldIndex, lineField);
682                    groupingColumnIndex = 0;
683                    if (isAddLine) {
684                        ((MessageField) lineField).getMessage().getPropertyExpressions().remove("messageText");
685                        ((MessageField) lineField).getMessage().setMessageText("addLine");
686                    }
687                } else {
688                    // If the row wraps before the last element
689                    if (insertActionField) {
690                        addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions);
691                    }
692    
693                    dataFields.add(lineField);
694                }
695    
696                // action field
697                if (!renderActionsLast && cellPosition == (actionColumnIndex - extraColumns - 1)) {
698                    addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions);
699                }
700    
701                //details action
702                if (lineField instanceof FieldGroup && ((FieldGroup) lineField).getItems() != null) {
703                    for (Component component : ((FieldGroup) lineField).getItems()) {
704                        if (component != null && component instanceof Action && component.getDataAttributes().get("role")
705                                != null && component.getDataAttributes().get("role").equals("detailsLink") &&
706                                StringUtils.isBlank(((Action) component).getActionScript())) {
707                            ((Action) component).setActionScript("rowDetailsActionHandler(this,'" + this.getId() + "');");
708                        }
709                    }
710                }
711    
712                //special column calculation handling to identify what type of handler will be attached
713                //and add special styling
714                if (lineField instanceof InputField && columnCalculations != null) {
715                    for (ColumnCalculationInfo cInfo : columnCalculations) {
716                        if (cInfo.getPropertyName().equals(((InputField) lineField).getPropertyName())) {
717                            if (cInfo.isCalculateOnKeyUp()) {
718                                lineField.addDataAttribute("total", "keyup");
719                            } else {
720                                lineField.addDataAttribute("total", "change");
721                            }
722                            lineField.addStyleClass("uif-calculationField");
723                        }
724                    }
725                }
726            }
727    
728            if (lineFields.size() == numberOfDataColumns && renderActions && renderActionsLast) {
729                addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions);
730            }
731    
732            // update colspan on sub-collection fields
733            for (FieldGroup subCollectionField : subCollectionFields) {
734                subCollectionField.setColSpan(numberOfDataColumns);
735            }
736    
737            // add sub-collection fields to end of data fields
738            dataFields.addAll(subCollectionFields);
739        }
740    
741        /**
742         * Adds the action field in a row
743         *
744         * @param idSuffix
745         * @param currentLine
746         * @param lineIndex
747         * @param rowSpan
748         * @param actions
749         */
750        private void addActionColumn(String idSuffix, Object currentLine, int lineIndex, int rowSpan,
751                List<Action> actions) {
752            FieldGroup lineActionsField = ComponentUtils.copy(getActionFieldPrototype(), idSuffix);
753    
754            ComponentUtils.updateContextForLine(lineActionsField, currentLine, lineIndex, idSuffix);
755            lineActionsField.setRowSpan(rowSpan);
756            lineActionsField.setItems(actions);
757    
758            setCellAttributes(lineActionsField);
759    
760            dataFields.add(lineActionsField);
761        }
762    
763        /**
764         * Create the {@code Label} instances that will be used to render
765         * the table header
766         *
767         * <p>
768         * For each column, a copy of headerLabelPrototype is made that determines
769         * the label configuration. The actual label text comes from the field for
770         * which the header applies to. The first column is always the sequence (if
771         * enabled) and the last column contains the actions. Both the sequence and
772         * action header fields will span all rows for the header.
773         * </p>
774         *
775         * <p>
776         * The headerLabels list will contain the final list of header fields built
777         * </p>
778         *
779         * @param collectionGroup - CollectionGroup container the table applies to
780         * @param lineFields - fields for the data columns from which the headers are pulled
781         */
782        protected void buildTableHeaderRows(CollectionGroup collectionGroup, List<Field> lineFields) {
783            // row count needed to determine the row span for the sequence and
784            // action fields, since they should span all rows for the line
785            int rowCount = calculateNumberOfRows(lineFields);
786            boolean renderActions = collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly();
787            int extraColumns = 0;
788    
789            if (actionColumnIndex == 1 && renderActions) {
790                addActionHeader(rowCount, 1);
791            }
792    
793            // first column is sequence label (if action column not 1)
794            if (renderSequenceField) {
795                getSequenceFieldPrototype().setLabelRendered(true);
796                getSequenceFieldPrototype().setRowSpan(rowCount);
797                addHeaderField(getSequenceFieldPrototype(), 1);
798                extraColumns++;
799    
800                if (actionColumnIndex == 2 && renderActions) {
801                    addActionHeader(rowCount, 2);
802                }
803            }
804    
805            // next is select field
806            if (collectionGroup.isIncludeLineSelectionField()) {
807                getSelectFieldPrototype().setLabelRendered(true);
808                getSelectFieldPrototype().setRowSpan(rowCount);
809                addHeaderField(getSelectFieldPrototype(), 1);
810                extraColumns++;
811    
812                if (actionColumnIndex == 3 && renderActions && renderSequenceField) {
813                    addActionHeader(rowCount, 3);
814                } else if (actionColumnIndex == 2 && renderActions) {
815                    addActionHeader(rowCount, 2);
816                }
817            }
818    
819            // pull out label fields from the container's items
820            int cellPosition = 0;
821            boolean renderActionsLast = actionColumnIndex == -1 || actionColumnIndex > lineFields.size() + extraColumns;
822            boolean insertActionHeader = false;
823            for (Field field : lineFields) {
824                if (!field.isRender() && StringUtils.isEmpty(field.getProgressiveRender())) {
825                    continue;
826                }
827    
828                //Check to see if ActionField needs to be inserted before this lineField because of wrapping.
829                // Since actionField has a colSpan of 1 add that to the previous cellPosition instead of the
830                // current lineField's colSpan.
831                // Only Insert if ActionField has to be placed at the end. Else the specification of actionColumnIndex
832                // should take care of putting it in the right location
833                insertActionHeader = (cellPosition != 0 && lineFields.size() != numberOfDataColumns && renderActions
834                        && renderActionsLast && ((cellPosition % numberOfDataColumns) == 0));
835    
836                if ( insertActionHeader) {
837                    addActionHeader(rowCount, cellPosition);
838                }
839    
840                cellPosition += field.getColSpan();
841                addHeaderField(field, cellPosition);
842    
843                // add action header
844                if (renderActions && !renderActionsLast && cellPosition == actionColumnIndex - extraColumns - 1) {
845                    cellPosition += 1;
846                    addActionHeader(rowCount, cellPosition);
847                }
848            }
849    
850            if (lineFields.size() == numberOfDataColumns && renderActions && renderActionsLast) {
851                cellPosition += 1;
852                addActionHeader(rowCount, cellPosition);
853            }
854        }
855    
856        /**
857         * Adds the action header
858         *
859         * @param rowCount
860         * @param cellPosition
861         */
862        private void addActionHeader(int rowCount, int cellPosition) {
863            getActionFieldPrototype().setLabelRendered(true);
864            getActionFieldPrototype().setRowSpan(rowCount);
865            addHeaderField(getActionFieldPrototype(), cellPosition);
866        }
867    
868        /**
869         * Creates a new instance of the header field prototype and then sets the
870         * label to the short (if useShortLabels is set to true) or long label of
871         * the given component. After created the header field is added to the list
872         * making up the table header
873         *
874         * @param field - field instance the header field is being created for
875         * @param column - column number for the header, used for setting the id
876         */
877        protected void addHeaderField(Field field, int column) {
878            Label headerLabel = ComponentUtils.copy(getHeaderLabelPrototype(), "_c" + column);
879            if (useShortLabels) {
880                headerLabel.setLabelText(field.getShortLabel());
881            } else {
882                headerLabel.setLabelText(field.getLabel());
883            }
884    
885            headerLabel.setRowSpan(field.getRowSpan());
886            headerLabel.setColSpan(field.getColSpan());
887    
888            if ((field.getRequired() != null) && field.getRequired().booleanValue()) {
889                headerLabel.getRequiredMessage().setRender(!field.isReadOnly());
890            } else {
891                headerLabel.getRequiredMessage().setRender(false);
892            }
893    
894            setCellAttributes(field);
895    
896            // copy cell attributes from the field to the label
897            headerLabel.setCellCssClasses(field.getCellCssClasses());
898            headerLabel.setCellStyle(field.getCellStyle());
899            headerLabel.setCellWidth(field.getCellWidth());
900    
901            headerLabels.add(headerLabel);
902        }
903    
904        /**
905         * Calculates how many rows will be needed per collection line to display
906         * the list of fields. Assumption is made that the total number of cells the
907         * fields take up is evenly divisible by the configured number of columns
908         *
909         * @param items - list of items that make up one collection line
910         * @return int number of rows
911         */
912        protected int calculateNumberOfRows(List<? extends Field> items) {
913            int rowCount = 0;
914    
915            // check flag that indicates only one row should be created
916            if (isSuppressLineWrapping()) {
917                return 1;
918            }
919    
920            // If Overflow is greater than 0 then calculate the col span for the last item in the overflowed row
921            if (items.size() % getNumberOfDataColumns() > 0) {
922                //get the last line item
923                Field field = items.get(items.size() - 1);
924    
925                int colSize = 0;
926                for (Field f : items) {
927                    colSize += f.getColSpan();
928                }
929    
930                field.setColSpan(1 + (numberOfDataColumns - (colSize % numberOfDataColumns)));
931                rowCount = ((items.size() / getNumberOfDataColumns()) + 1);
932            } else {
933                rowCount = items.size() / getNumberOfDataColumns();
934            }
935            return rowCount;
936        }
937    
938        /**
939         * @see CollectionLayoutManager#getSupportedContainer()
940         */
941        @Override
942        public Class<? extends Container> getSupportedContainer() {
943            return CollectionGroup.class;
944        }
945    
946        /**
947         * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#getComponentsForLifecycle()
948         */
949        @Override
950        public List<Component> getComponentsForLifecycle() {
951            List<Component> components = super.getComponentsForLifecycle();
952    
953            components.add(richTable);
954            components.add(addLineGroup);
955            components.addAll(headerLabels);
956            components.addAll(dataFields);
957    
958            for (ColumnCalculationInfo cInfo : columnCalculations) {
959                components.add(cInfo.getTotalField());
960                components.add(cInfo.getPageTotalField());
961                components.add(cInfo.getGroupTotalFieldPrototype());
962            }
963    
964            if (isShowToggleAllDetails()) {
965                components.add(toggleAllDetailsAction);
966            }
967    
968            return components;
969        }
970    
971        /**
972         * @see org.kuali.rice.krad.uif.layout.LayoutManager#getComponentPrototypes()
973         */
974        @Override
975        public List<Component> getComponentPrototypes() {
976            List<Component> components = super.getComponentPrototypes();
977    
978            components.add(getHeaderLabelPrototype());
979            components.add(getSequenceFieldPrototype());
980            components.add(getActionFieldPrototype());
981            components.add(getSubCollectionFieldGroupPrototype());
982            components.add(getSelectFieldPrototype());
983    
984            return components;
985        }
986    
987        /**
988         * Indicates whether the short label for the collection field should be used
989         * as the table header or the regular label
990         *
991         * @return boolean true if short label should be used, false if long label
992         *         should be used
993         */
994        @BeanTagAttribute(name = "useShortLabels")
995        public boolean isUseShortLabels() {
996            return this.useShortLabels;
997        }
998    
999        /**
1000         * Setter for the use short label indicator
1001         *
1002         * @param useShortLabels
1003         */
1004        public void setUseShortLabels(boolean useShortLabels) {
1005            this.useShortLabels = useShortLabels;
1006        }
1007    
1008        /**
1009         * Indicates whether the header should be repeated before each collection
1010         * row. If false the header is only rendered at the beginning of the table
1011         *
1012         * @return boolean true if header should be repeated, false if it should
1013         *         only be rendered once
1014         */
1015        @BeanTagAttribute(name = "repeatHeader")
1016        public boolean isRepeatHeader() {
1017            return this.repeatHeader;
1018        }
1019    
1020        /**
1021         * Setter for the repeat header indicator
1022         *
1023         * @param repeatHeader
1024         */
1025        public void setRepeatHeader(boolean repeatHeader) {
1026            this.repeatHeader = repeatHeader;
1027        }
1028    
1029        /**
1030         * {@code Label} instance to use as a prototype for creating the
1031         * tables header fields. For each header field the prototype will be copied
1032         * and adjusted as necessary
1033         *
1034         * @return Label instance to serve as prototype
1035         */
1036        @BeanTagAttribute(name = "headerLabelPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1037        public Label getHeaderLabelPrototype() {
1038            return this.headerLabelPrototype;
1039        }
1040    
1041        /**
1042         * Setter for the header field prototype
1043         *
1044         * @param headerLabelPrototype
1045         */
1046        public void setHeaderLabelPrototype(Label headerLabelPrototype) {
1047            this.headerLabelPrototype = headerLabelPrototype;
1048        }
1049    
1050        /**
1051         * List of {@code Label} instances that should be rendered to make
1052         * up the tables header
1053         *
1054         * @return List of label field instances
1055         */
1056        public List<Label> getHeaderLabels() {
1057            return this.headerLabels;
1058        }
1059    
1060        /**
1061         * Indicates whether the sequence field should be rendered for the
1062         * collection
1063         *
1064         * @return boolean true if sequence field should be rendered, false if not
1065         */
1066        @BeanTagAttribute(name = "renderSequenceField")
1067        public boolean isRenderSequenceField() {
1068            return this.renderSequenceField;
1069        }
1070    
1071        /**
1072         * Setter for the render sequence field indicator
1073         *
1074         * @param renderSequenceField
1075         */
1076        public void setRenderSequenceField(boolean renderSequenceField) {
1077            this.renderSequenceField = renderSequenceField;
1078        }
1079    
1080        /**
1081         * Attribute name to use as sequence value. For each collection line the
1082         * value of this field on the line will be retrieved and used as the
1083         * sequence value
1084         *
1085         * @return String sequence property name
1086         */
1087        @BeanTagAttribute(name = "sequencePropertyName")
1088        public String getSequencePropertyName() {
1089            if ((getSequenceFieldPrototype() != null) && (getSequenceFieldPrototype() instanceof DataField)) {
1090                return ((DataField) getSequenceFieldPrototype()).getPropertyName();
1091            }
1092    
1093            return null;
1094        }
1095    
1096        /**
1097         * Setter for the sequence property name
1098         *
1099         * @param sequencePropertyName
1100         */
1101        public void setSequencePropertyName(String sequencePropertyName) {
1102            if ((getSequenceFieldPrototype() != null) && (getSequenceFieldPrototype() instanceof DataField)) {
1103                ((DataField) getSequenceFieldPrototype()).setPropertyName(sequencePropertyName);
1104            }
1105        }
1106    
1107        /**
1108         * Indicates whether the sequence field should be generated with the current
1109         * line number
1110         *
1111         * <p>
1112         * If set to true the sequence field prototype will be changed to a message
1113         * field (if not already a message field) and the text will be set to the
1114         * current line number
1115         * </p>
1116         *
1117         * @return boolean true if the sequence field should be generated from the
1118         *         line number, false if not
1119         */
1120        @BeanTagAttribute(name = "generateAutoSequence")
1121        public boolean isGenerateAutoSequence() {
1122            return this.generateAutoSequence;
1123        }
1124    
1125        /**
1126         * Setter for the generate auto sequence field
1127         *
1128         * @param generateAutoSequence
1129         */
1130        public void setGenerateAutoSequence(boolean generateAutoSequence) {
1131            this.generateAutoSequence = generateAutoSequence;
1132        }
1133    
1134        /**
1135         * {@code Field} instance to serve as a prototype for the
1136         * sequence field. For each collection line this instance is copied and
1137         * adjusted as necessary
1138         *
1139         * @return Attribute field instance
1140         */
1141        @BeanTagAttribute(name = "sequenceFieldPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1142        public Field getSequenceFieldPrototype() {
1143            return this.sequenceFieldPrototype;
1144        }
1145    
1146        /**
1147         * Setter for the sequence field prototype
1148         *
1149         * @param sequenceFieldPrototype
1150         */
1151        public void setSequenceFieldPrototype(Field sequenceFieldPrototype) {
1152            this.sequenceFieldPrototype = sequenceFieldPrototype;
1153        }
1154    
1155        /**
1156         * {@code FieldGroup} instance to serve as a prototype for the actions
1157         * column. For each collection line this instance is copied and adjusted as
1158         * necessary. Note the actual actions for the group come from the collection
1159         * groups actions List
1160         * (org.kuali.rice.krad.uif.container.CollectionGroup.getActions()). The
1161         * FieldGroup prototype is useful for setting styling of the actions column
1162         * and for the layout of the action fields. Note also the label associated
1163         * with the prototype is used for the action column header
1164         *
1165         * @return GroupField instance
1166         */
1167        @BeanTagAttribute(name = "actionFieldPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1168        public FieldGroup getActionFieldPrototype() {
1169            return this.actionFieldPrototype;
1170        }
1171    
1172        /**
1173         * Setter for the action field prototype
1174         *
1175         * @param actionFieldPrototype
1176         */
1177        public void setActionFieldPrototype(FieldGroup actionFieldPrototype) {
1178            this.actionFieldPrototype = actionFieldPrototype;
1179        }
1180    
1181        /**
1182         * @see org.kuali.rice.krad.uif.layout.CollectionLayoutManager#getSubCollectionFieldGroupPrototype()
1183         */
1184        @BeanTagAttribute(name = "subCollectionFieldGroupPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1185        public FieldGroup getSubCollectionFieldGroupPrototype() {
1186            return this.subCollectionFieldGroupPrototype;
1187        }
1188    
1189        /**
1190         * Setter for the sub-collection field group prototype
1191         *
1192         * @param subCollectionFieldGroupPrototype
1193         */
1194        public void setSubCollectionFieldGroupPrototype(FieldGroup subCollectionFieldGroupPrototype) {
1195            this.subCollectionFieldGroupPrototype = subCollectionFieldGroupPrototype;
1196        }
1197    
1198        /**
1199         * Field instance that serves as a prototype for creating the select field on each line when
1200         * {@link org.kuali.rice.krad.uif.container.CollectionGroup#isIncludeLineSelectionField()} is true
1201         *
1202         * <p>
1203         * This prototype can be used to set the control used for the select field (generally will be a checkbox control)
1204         * in addition to styling and other setting. The binding path will be formed with using the
1205         * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getLineSelectPropertyName()} or if not set the
1206         * framework
1207         * will use {@link org.kuali.rice.krad.web.form.UifFormBase#getSelectedCollectionLines()}
1208         * </p>
1209         *
1210         * @return Field select field prototype instance
1211         */
1212        @BeanTagAttribute(name = "selectFieldPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1213        public Field getSelectFieldPrototype() {
1214            return selectFieldPrototype;
1215        }
1216    
1217        /**
1218         * Setter for the prototype instance for select fields
1219         *
1220         * @param selectFieldPrototype
1221         */
1222        public void setSelectFieldPrototype(Field selectFieldPrototype) {
1223            this.selectFieldPrototype = selectFieldPrototype;
1224        }
1225    
1226        /**
1227         * Indicates whether the add line should be rendered in a separate group, or as part of the table (first line)
1228         *
1229         * <p>
1230         * When separate add line is enabled, the fields for the add line will be placed in the {@link #getAddLineGroup()}.
1231         * This group can be used to configure the add line presentation. In addition to the fields, the header on the
1232         * group (unless already set) will be set to
1233         * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLabel()} and the add line actions will
1234         * be placed into the group's footer.
1235         * </p>
1236         *
1237         * @return boolean true if add line should be separated, false if it should be placed into the table
1238         */
1239        @BeanTagAttribute(name = "separateAddLine")
1240        public boolean isSeparateAddLine() {
1241            return separateAddLine;
1242        }
1243    
1244        /**
1245         * Setter for the separate add line indicator
1246         *
1247         * @param separateAddLine
1248         */
1249        public void setSeparateAddLine(boolean separateAddLine) {
1250            this.separateAddLine = separateAddLine;
1251        }
1252    
1253        /**
1254         * When {@link #isSeparateAddLine()} is true, this group will be used to render the add line
1255         *
1256         * <p>
1257         * This group can be used to configure how the add line will be rendered. For example the layout manager configured
1258         * on the group will be used to rendered the add line fields. If the header (title) is not set on the group, it
1259         * will be set from
1260         * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLabel()}. In addition,
1261         * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLineActions()} will be added to the group
1262         * footer items.
1263         * </p>
1264         *
1265         * @return Group instance for the collection add line
1266         */
1267        @BeanTagAttribute(name = "addLineGroup", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1268        public Group getAddLineGroup() {
1269            return addLineGroup;
1270        }
1271    
1272        /**
1273         * Setter for the add line Group
1274         *
1275         * @param addLineGroup
1276         */
1277        public void setAddLineGroup(Group addLineGroup) {
1278            this.addLineGroup = addLineGroup;
1279        }
1280    
1281        /**
1282         * List of {@code Component} instances that make up the tables body. Pulled
1283         * by the layout manager template to send through the Grid layout
1284         *
1285         * @return List<Component> table body fields
1286         */
1287        public List<Component> getDataFields() {
1288            return this.dataFields;
1289        }
1290    
1291        /**
1292         * Widget associated with the table to add functionality such as sorting,
1293         * paging, and export
1294         *
1295         * @return RichTable instance
1296         */
1297        @BeanTagAttribute(name = "richTable", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1298        public RichTable getRichTable() {
1299            return this.richTable;
1300        }
1301    
1302        /**
1303         * Setter for the rich table widget
1304         *
1305         * @param richTable
1306         */
1307        public void setRichTable(RichTable richTable) {
1308            this.richTable = richTable;
1309        }
1310    
1311        /**
1312         * @return the numberOfDataColumns
1313         */
1314        @BeanTagAttribute(name = "numberOfDataColumns")
1315        public int getNumberOfDataColumns() {
1316            return this.numberOfDataColumns;
1317        }
1318    
1319        /**
1320         * @param numberOfDataColumns the numberOfDataColumns to set
1321         */
1322        public void setNumberOfDataColumns(int numberOfDataColumns) {
1323            this.numberOfDataColumns = numberOfDataColumns;
1324        }
1325    
1326        /**
1327         * @see org.kuali.rice.krad.uif.widget.RichTable#getHiddenColumns()
1328         */
1329        @BeanTagAttribute(name = "hiddenColumns", type = BeanTagAttribute.AttributeType.SETVALUE)
1330        public Set<String> getHiddenColumns() {
1331            if (richTable != null) {
1332                return richTable.getHiddenColumns();
1333            }
1334    
1335            return null;
1336        }
1337    
1338        /**
1339         * @see org.kuali.rice.krad.uif.widget.RichTable#setHiddenColumns(java.util.Set<java.lang.String>)
1340         */
1341        public void setHiddenColumns(Set<String> hiddenColumns) {
1342            if (richTable != null) {
1343                richTable.setHiddenColumns(hiddenColumns);
1344            }
1345        }
1346    
1347        /**
1348         * @see org.kuali.rice.krad.uif.widget.RichTable#getSortableColumns()
1349         */
1350        @BeanTagAttribute(name = "sortableColumns", type = BeanTagAttribute.AttributeType.SETVALUE)
1351        public Set<String> getSortableColumns() {
1352            if (richTable != null) {
1353                return richTable.getSortableColumns();
1354            }
1355    
1356            return null;
1357        }
1358    
1359        /**
1360         * @see org.kuali.rice.krad.uif.widget.RichTable#setSortableColumns(java.util.Set<java.lang.String>)
1361         */
1362        public void setSortableColumns(Set<String> sortableColumns) {
1363            if (richTable != null) {
1364                richTable.setSortableColumns(sortableColumns);
1365            }
1366        }
1367    
1368        /**
1369         * Indicates the index of the action column
1370         *
1371         * @return int = the action column index
1372         */
1373        @BeanTagAttribute(name = "actionColumnIndex")
1374        public int getActionColumnIndex() {
1375            return actionColumnIndex;
1376        }
1377    
1378        /**
1379         * Indicates the actions column placement
1380         *
1381         * <p>
1382         * Valid values are 'LEFT', 'RIGHT' or any valid number. The default is 'RIGHT' or '-1'. The column placement index
1383         * takes all displayed columns, including sequence and selection columns, into account.
1384         * </p>
1385         *
1386         * @return String - the action column placement
1387         */
1388        @BeanTagAttribute(name = "actionColumnPlacement")
1389        public String getActionColumnPlacement() {
1390            return actionColumnPlacement;
1391        }
1392    
1393        /**
1394         * Setter for the action column placement
1395         *
1396         * @param actionColumnPlacement - action column placement string
1397         */
1398        public void setActionColumnPlacement(String actionColumnPlacement) {
1399            this.actionColumnPlacement = actionColumnPlacement;
1400    
1401            if ("LEFT".equals(actionColumnPlacement)) {
1402                actionColumnIndex = 1;
1403            } else if ("RIGHT".equals(actionColumnPlacement)) {
1404                actionColumnIndex = -1;
1405            } else if (StringUtils.isNumeric(actionColumnPlacement)) {
1406                actionColumnIndex = Integer.parseInt(actionColumnPlacement);
1407            }
1408        }
1409    
1410        /**
1411         * The row details info group to use when using a TableLayoutManager with the a richTable.
1412         *
1413         * <p>This group will be displayed when the user clicks the "Details" link/image on a row.
1414         * This allows extra/long data to be hidden in table rows and then revealed during interaction
1415         * with the table without the need to leave the page.  Allows for any group content.</p>
1416         *
1417         * <p>Does not currently work with javascript required content.</p>
1418         *
1419         * @return rowDetailsGroup component
1420         */
1421        @BeanTagAttribute(name = "rowDetailsGroup", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1422        public Group getRowDetailsGroup() {
1423            return rowDetailsGroup;
1424        }
1425    
1426        /**
1427         * Set the row details info group
1428         *
1429         * @param rowDetailsGroup row details group
1430         */
1431        public void setRowDetailsGroup(Group rowDetailsGroup) {
1432            this.rowDetailsGroup = rowDetailsGroup;
1433        }
1434    
1435        /**
1436         * Name of the link for displaying row details in a TableLayoutManager CollectionGroup
1437         *
1438         * @return name of the link
1439         */
1440        @BeanTagAttribute(name = "rowDetailsLinkName")
1441        public String getRowDetailsLinkName() {
1442            return rowDetailsLinkName;
1443        }
1444    
1445        /**
1446         * Row details link name
1447         *
1448         * @param rowDetailsLinkName name of the details link
1449         */
1450        public void setRowDetailsLinkName(String rowDetailsLinkName) {
1451            this.rowDetailsLinkName = rowDetailsLinkName;
1452        }
1453    
1454        /**
1455         * If true, the row details link will use an image instead of a link to display row details in
1456         * a TableLayoutManager CollectionGroup
1457         *
1458         * @return true if displaying an image instead of a link for row details
1459         */
1460        @BeanTagAttribute(name = "rowDetailsSwapActionImage")
1461        public boolean isRowDetailsSwapActionImage() {
1462            return rowDetailsSwapActionImage;
1463        }
1464    
1465        /**
1466         * Sets row details link use image flag
1467         *
1468         * @param rowDetailsSwapActionImage true to use image for details, false otherwise
1469         */
1470        public void setRowDetailsSwapActionImage(boolean rowDetailsSwapActionImage) {
1471            this.rowDetailsSwapActionImage = rowDetailsSwapActionImage;
1472        }
1473    
1474        /**
1475         * Creates the details group for the line using the information setup through the setter methods of this
1476         * interface.  Line details are currently only supported in TableLayoutManagers which use richTable.
1477         *
1478         * @param collectionGroup the CollectionGroup for this TableLayoutManager
1479         * @param view the current view
1480         */
1481        public void setupDetails(CollectionGroup collectionGroup, View view) {
1482            if (getRowDetailsGroup() == null || this.getRichTable() == null || !this.getRichTable().isRender()) {
1483                return;
1484            }
1485    
1486            //data attribute to mark this group to open itself when rendered
1487            collectionGroup.addDataAttribute("detailsDefaultOpen", Boolean.toString(this.rowDetailsOpen));
1488    
1489            toggleAllDetailsAction.addDataAttribute("open", Boolean.toString(this.rowDetailsOpen));
1490            toggleAllDetailsAction.addDataAttribute("tableid", this.getId());
1491    
1492            this.getRowDetailsGroup().setHidden(true);
1493    
1494            FieldGroup detailsFieldGroup = ComponentFactory.getFieldGroup();
1495    
1496            TreeMap<String, String> dataAttributes = new TreeMap<String, String>();
1497            dataAttributes.put("role", "detailsFieldGroup");
1498            detailsFieldGroup.setDataAttributes(dataAttributes);
1499    
1500            Action rowDetailsAction = this.getExpandDetailsActionPrototype();
1501            rowDetailsAction.addDataAttribute("role", "detailsLink");
1502            rowDetailsAction.addDataAttribute("swap", Boolean.toString(this.isRowDetailsSwapActionImage()));
1503            rowDetailsAction.setId(collectionGroup.getId() + "_detLink");
1504    
1505            List<Component> detailsItems = new ArrayList<Component>();
1506            detailsItems.add(rowDetailsAction);
1507    
1508            dataAttributes = new TreeMap<String, String>();
1509            dataAttributes.put("role", "details");
1510            this.getRowDetailsGroup().setDataAttributes(dataAttributes);
1511    
1512            if (ajaxDetailsRetrieval) {
1513                this.getRowDetailsGroup().setRender(false);
1514                this.getRowDetailsGroup().setDisclosedByAction(true);
1515            }
1516    
1517            detailsItems.add(getRowDetailsGroup());
1518            detailsFieldGroup.setItems(detailsItems);
1519            detailsFieldGroup.setId(collectionGroup.getId() + "_detGroup");
1520            view.assignComponentIds(detailsFieldGroup);
1521    
1522    
1523            List<Component> theItems = new ArrayList<Component>();
1524            theItems.add(detailsFieldGroup);
1525            theItems.addAll(collectionGroup.getItems());
1526            collectionGroup.setItems(theItems);
1527        }
1528    
1529        /**
1530         * A list of all the columns to be calculated
1531         *
1532         * <p>
1533         * The list must contain valid column indexes. The indexes takes all displayed columns into account.
1534         * </p>
1535         *
1536         * @return List<String> the total columns list
1537         */
1538        public List<String> getColumnsToCalculate() {
1539            return columnsToCalculate;
1540        }
1541    
1542        /**
1543         * Validates different requirements of component compiling a series of reports detailing information on errors
1544         * found in the component.  Used by the RiceDictionaryValidator.
1545         *
1546         * @param tracer Record of component's location
1547         * @return A list of ErrorReports detailing errors found within the component and referenced within it
1548         */
1549        public void completeValidation(ValidationTrace tracer) {
1550            tracer.addBean("TableLayoutManager", getId());
1551    
1552            if (getRowDetailsGroup() != null) {
1553                boolean validTable = false;
1554                if (getRichTable() != null) {
1555                    if (getRichTable().isRender()) {
1556                        validTable = true;
1557                    }
1558                }
1559                if (!validTable) {
1560                    String currentValues[] = {"rowDetailsGroup =" + getRowDetailsGroup(), "richTable =" + getRichTable()};
1561                    tracer.createError("If rowDetailsGroup is set richTable must be set and its render true",
1562                            currentValues);
1563                }
1564    
1565            }
1566        }
1567    
1568        /**
1569         * Gets showTotal. showTotal shows/calculates the total field when true, otherwise it is not rendered.
1570         * <br/>
1571         * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting.
1572         * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b>
1573         *
1574         * @return true if showing the total, false otherwise.
1575         */
1576        @BeanTagAttribute(name = "showTotal")
1577        public boolean isShowTotal() {
1578            return showTotal;
1579        }
1580    
1581        /**
1582         * Sets showTotal. showTotal shows/calculates the total field when true, otherwise it is not rendered.
1583         * <br/>
1584         * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting.
1585         * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b>
1586         *
1587         * @param showTotal
1588         */
1589        public void setShowTotal(boolean showTotal) {
1590            this.showTotal = showTotal;
1591        }
1592    
1593        /**
1594         * Gets showTotal. showTotal shows/calculates the total field when true, otherwise it is not rendered.
1595         * <br/>
1596         * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting.
1597         * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b>
1598         *
1599         * @return true if showing the page total, false otherwise.
1600         */
1601        @BeanTagAttribute(name = "showPageTotal")
1602        public boolean isShowPageTotal() {
1603            return showPageTotal;
1604        }
1605    
1606        /**
1607         * Sets showPageTotal. showPageTotal shows/calculates the total field for the page when true (and only
1608         * when the table actually has pages), otherwise it is not rendered.
1609         * <br/>
1610         * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting.
1611         * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b>
1612         *
1613         * @param showPageTotal
1614         */
1615        public void setShowPageTotal(boolean showPageTotal) {
1616            this.showPageTotal = showPageTotal;
1617        }
1618    
1619        /**
1620         * Gets showGroupTotal. showGroupTotal shows/calculates the total field for each grouping when true (and only
1621         * when the table actually has grouping turned on), otherwise it is not rendered.
1622         * <br/>
1623         * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting.
1624         * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b>
1625         *
1626         * @return true if showing the group total, false otherwise.
1627         */
1628        @BeanTagAttribute(name = "showGroupTotal")
1629        public boolean isShowGroupTotal() {
1630            return showGroupTotal;
1631        }
1632    
1633        /**
1634         * Sets showGroupTotal. showGroupTotal shows/calculates the total field for each grouping when true (and only
1635         * when the table actually has grouping turned on), otherwise it is not rendered.
1636         * <br/>
1637         * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting.
1638         * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b>
1639         *
1640         * @param showGroupTotal
1641         */
1642        public void setShowGroupTotal(boolean showGroupTotal) {
1643            this.showGroupTotal = showGroupTotal;
1644        }
1645    
1646        /**
1647         * The total label to use when renderOnlyLeftTotalLabels is TRUE for total.
1648         * This label will appear in the left most column.
1649         *
1650         * @return the totalLabel
1651         */
1652        @BeanTagAttribute(name = "totalLabel", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1653        public Label getTotalLabel() {
1654            return totalLabel;
1655        }
1656    
1657        /**
1658         * Sets the total label to use when renderOnlyLeftTotalLabels is TRUE for total.
1659         *
1660         * @param totalLabel
1661         */
1662        public void setTotalLabel(Label totalLabel) {
1663            this.totalLabel = totalLabel;
1664        }
1665    
1666        /**
1667         * The pageTotal label to use when renderOnlyLeftTotalLabels is TRUE for total.  This label will appear in the
1668         * left most column.
1669         *
1670         * @return the totalLabel
1671         */
1672        @BeanTagAttribute(name = "pageTotalLabel", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1673        public Label getPageTotalLabel() {
1674            return pageTotalLabel;
1675        }
1676    
1677        /**
1678         * Sets the pageTotal label to use when renderOnlyLeftTotalLabels is TRUE for total.
1679         *
1680         * @param pageTotalLabel
1681         */
1682        public void setPageTotalLabel(Label pageTotalLabel) {
1683            this.pageTotalLabel = pageTotalLabel;
1684        }
1685    
1686        /**
1687         * The groupTotal label to use when renderOnlyLeftTotalLabels is TRUE.  This label will appear in the left most
1688         * column.
1689         *
1690         * @return the totalLabel
1691         */
1692        @BeanTagAttribute(name = "groupTotalLabelPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
1693        public Label getGroupTotalLabelPrototype() {
1694            return groupTotalLabelPrototype;
1695        }
1696    
1697        /**
1698         * Sets the groupTotal label to use when renderOnlyLeftTotalLabels is TRUE.
1699         *
1700         * @param groupTotalLabelPrototype
1701         */
1702        public void setGroupTotalLabelPrototype(Label groupTotalLabelPrototype) {
1703            this.groupTotalLabelPrototype = groupTotalLabelPrototype;
1704        }
1705    
1706        /**
1707         * Gets the column calculations.  This is a list of ColumnCalcuationInfo that when set provides calculations
1708         * to be performed on the columns they specify.  These calculations appear in the table's footer.  This feature is
1709         * only available when using richTable functionality.
1710         *
1711         * @return the columnCalculations to use
1712         */
1713        @BeanTagAttribute(name = "columnCalculations", type = BeanTagAttribute.AttributeType.LISTBEAN)
1714        public List<ColumnCalculationInfo> getColumnCalculations() {
1715            return columnCalculations;
1716        }
1717    
1718        /**
1719         * Sets the columnCalculations.
1720         *
1721         * @param columnCalculations
1722         */
1723        public void setColumnCalculations(List<ColumnCalculationInfo> columnCalculations) {
1724            this.columnCalculations = columnCalculations;
1725        }
1726    
1727        /**
1728         * When true, labels for the totals fields will only appear in the left most column.  Showing of the totals
1729         * is controlled by the settings on the TableLayoutManager itself when this property is true.
1730         *
1731         * @return true when rendering totals footer labels in the left-most column, false otherwise
1732         */
1733        @BeanTagAttribute(name = "renderOnlyLeftTotalLabels")
1734        public boolean isRenderOnlyLeftTotalLabels() {
1735            return renderOnlyLeftTotalLabels;
1736        }
1737    
1738        /**
1739         * Set the renderOnlyLeftTotalLabels flag for rendring total labels in the left-most column
1740         *
1741         * @param renderOnlyLeftTotalLabels
1742         */
1743        public void setRenderOnlyLeftTotalLabels(boolean renderOnlyLeftTotalLabels) {
1744            this.renderOnlyLeftTotalLabels = renderOnlyLeftTotalLabels;
1745        }
1746    
1747        /**
1748         * Gets the footer calculation components to be used by the layout.  These are set by the framework and cannot
1749         * be set directly.
1750         *
1751         * @return the list of components for the footer
1752         */
1753        public List<Component> getFooterCalculationComponents() {
1754            return footerCalculationComponents;
1755        }
1756    
1757        /**
1758         * Gets the list of property names to use for grouping.
1759         *
1760         * <p>
1761         * When this property is set, grouping for this
1762         * collection will be enabled and the lines of the collection will be grouped by the propertyName(s) supplied.
1763         * Supplying multiple property names will cause the grouping to be on multiple fields and ordered
1764         * alphabetically on "propetyValue1, propertyValue2" (this is also how the group title will display for each
1765         * group).
1766         * The property names supplied must be relative to the line, so #lp
1767         * SHOULD NOT be used (it is assumed automatically).
1768         * </p>
1769         *
1770         * @return propertyNames to group on
1771         */
1772        @BeanTagAttribute(name = "groupingPropertyNames", type = BeanTagAttribute.AttributeType.LISTVALUE)
1773        public List<String> getGroupingPropertyNames() {
1774            return groupingPropertyNames;
1775        }
1776    
1777        /**
1778         * Sets the list of property names to use for grouping.
1779         *
1780         * @param groupingPropertyNames
1781         */
1782        public void setGroupingPropertyNames(List<String> groupingPropertyNames) {
1783            this.groupingPropertyNames = groupingPropertyNames;
1784        }
1785    
1786        /**
1787         * Get the groupingTitle.  The groupingTitle MUST contain a SpringEL expression to uniquely identify a
1788         * group's line (ie it cannot be a static string because each group must be identified by some value).
1789         * <b>This overrides groupingPropertyNames(if set) because it provides full control of grouping value used by
1790         * the collection.  SpringEL defined here must use #lp if referencing values of the line.</b>
1791         *
1792         * @return groupingTitle to be used
1793         */
1794        @BeanTagAttribute(name = "groupingTitle")
1795        public String getGroupingTitle() {
1796            return groupingTitle;
1797        }
1798    
1799        /**
1800         * Set the groupingTitle.  This will throw an exception if the title does not contain a SpringEL expression.
1801         *
1802         * @param groupingTitle
1803         */
1804        public void setGroupingTitle(String groupingTitle) {
1805            if (groupingTitle != null && !groupingTitle.contains("@{")) {
1806                throw new RuntimeException("groupingTitle MUST contain a springEL expression to uniquely"
1807                        + " identify a collection group (often related to some value of the line). "
1808                        + "Value provided: "
1809                        + this.getGroupingTitle());
1810            }
1811            this.groupingTitle = groupingTitle;
1812        }
1813    
1814        /**
1815         * Get the groupingPrefix.  The groupingPrefix is used to prefix the generated title (not used when
1816         * groupingTitle is set directly) when using groupingPropertyNames.
1817         *
1818         * @return String
1819         */
1820        @BeanTagAttribute(name = "groupingPrefix")
1821        public String getGroupingPrefix() {
1822            return groupingPrefix;
1823        }
1824    
1825        /**
1826         * Set the groupingPrefix.  This is not used when groupingTitle is set directly.
1827         *
1828         * @param groupingPrefix
1829         */
1830        public void setGroupingPrefix(String groupingPrefix) {
1831            this.groupingPrefix = groupingPrefix;
1832        }
1833    
1834        /**
1835         * If true, all details will be opened by default when the table loads.  Can only be used on tables that have
1836         * row details setup.
1837         *
1838         * @return true if row details
1839         */
1840        public boolean isRowDetailsOpen() {
1841            return rowDetailsOpen;
1842        }
1843    
1844        /**
1845         * Set if row details should be open on table load
1846         *
1847         * @param rowDetailsOpen
1848         */
1849        public void setRowDetailsOpen(boolean rowDetailsOpen) {
1850            this.rowDetailsOpen = rowDetailsOpen;
1851        }
1852    
1853        /**
1854         * If true, the toggleAllDetailsAction will be shown.  This button allows all details to
1855         * be open/closed simultaneously.
1856         *
1857         * @return true if the action button to toggle all row details opened/closed
1858         */
1859        public boolean isShowToggleAllDetails() {
1860            return showToggleAllDetails;
1861        }
1862    
1863        /**
1864         * Set if the toggleAllDetailsAction should be shown
1865         *
1866         * @param showToggleAllDetails
1867         */
1868        public void setShowToggleAllDetails(boolean showToggleAllDetails) {
1869            this.showToggleAllDetails = showToggleAllDetails;
1870        }
1871    
1872        /**
1873         * The toggleAllDetailsAction action component used to toggle all row details open/closed.  This property is set
1874         * by the default configuration and should not be reset in most cases.
1875         *
1876         * @return Action component to use for the toggle action button
1877         */
1878        public Action getToggleAllDetailsAction() {
1879            return toggleAllDetailsAction;
1880        }
1881    
1882        /**
1883         * Set the toggleAllDetailsAction action component used to toggle all row details open/closed.  This property is
1884         * set
1885         * by the default configuration and should not be reset in most cases.
1886         *
1887         * @param toggleAllDetailsAction
1888         */
1889        public void setToggleAllDetailsAction(Action toggleAllDetailsAction) {
1890            this.toggleAllDetailsAction = toggleAllDetailsAction;
1891        }
1892    
1893        /**
1894         * If true, when a row details open action is performed, it will get the details content from the server the first
1895         * time it is opened.  The methodToCall will be a component "refresh" call by default (this can be set on
1896         * expandDetailsActionPrototype) and the additional action parameters sent to the server will be those set
1897         * on the expandDetailsActionPrototype (lineIndex will be sent by default).
1898         *
1899         * @return true if ajax row details retrieval will be used
1900         */
1901        public boolean isAjaxDetailsRetrieval() {
1902            return ajaxDetailsRetrieval;
1903        }
1904    
1905        /**
1906         * Set if row details content should be retrieved fromt he server
1907         *
1908         * @param ajaxDetailsRetrieval
1909         */
1910        public void setAjaxDetailsRetrieval(boolean ajaxDetailsRetrieval) {
1911            this.ajaxDetailsRetrieval = ajaxDetailsRetrieval;
1912        }
1913    
1914        /**
1915         * The Action prototype used for the row details expand link.  Should be set to "Uif-ExpandDetailsAction" or
1916         * "Uif-ExpandDetailsImageAction".  Properties can be configured to allow for different methodToCall and
1917         * actionParameters to be set for ajax row details retrieval.
1918         *
1919         * @return the Action details link prototype
1920         */
1921        public Action getExpandDetailsActionPrototype() {
1922            return expandDetailsActionPrototype;
1923        }
1924    
1925        /**
1926         * Set the expand details Action prototype link
1927         *
1928         * @param expandDetailsActionPrototype
1929         */
1930        public void setExpandDetailsActionPrototype(Action expandDetailsActionPrototype) {
1931            this.expandDetailsActionPrototype = expandDetailsActionPrototype;
1932        }
1933    }