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