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