View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.layout;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.framework.util.ReflectionUtils;
20  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
22  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
23  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
24  import org.kuali.rice.krad.uif.CssConstants;
25  import org.kuali.rice.krad.uif.UifConstants;
26  import org.kuali.rice.krad.uif.UifPropertyPaths;
27  import org.kuali.rice.krad.uif.component.Component;
28  import org.kuali.rice.krad.uif.component.DataBinding;
29  import org.kuali.rice.krad.uif.component.KeepExpression;
30  import org.kuali.rice.krad.uif.container.CollectionGroup;
31  import org.kuali.rice.krad.uif.container.Container;
32  import org.kuali.rice.krad.uif.container.DialogGroup;
33  import org.kuali.rice.krad.uif.container.Group;
34  import org.kuali.rice.krad.uif.container.collections.LineBuilderContext;
35  import org.kuali.rice.krad.uif.element.Action;
36  import org.kuali.rice.krad.uif.element.Label;
37  import org.kuali.rice.krad.uif.element.Message;
38  import org.kuali.rice.krad.uif.field.DataField;
39  import org.kuali.rice.krad.uif.field.Field;
40  import org.kuali.rice.krad.uif.field.FieldGroup;
41  import org.kuali.rice.krad.uif.field.InputField;
42  import org.kuali.rice.krad.uif.field.MessageField;
43  import org.kuali.rice.krad.uif.layout.collections.CollectionLayoutManagerBase;
44  import org.kuali.rice.krad.uif.layout.collections.CollectionPagingHelper;
45  import org.kuali.rice.krad.uif.layout.collections.DataTablesPagingHelper;
46  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
47  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleRestriction;
48  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleUtils;
49  import org.kuali.rice.krad.uif.util.ColumnCalculationInfo;
50  import org.kuali.rice.krad.uif.util.ComponentFactory;
51  import org.kuali.rice.krad.uif.util.ComponentUtils;
52  import org.kuali.rice.krad.uif.util.ContextUtils;
53  import org.kuali.rice.krad.uif.util.CopyUtils;
54  import org.kuali.rice.krad.uif.util.LifecycleElement;
55  import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
56  import org.kuali.rice.krad.uif.view.View;
57  import org.kuali.rice.krad.uif.view.ViewModel;
58  import org.kuali.rice.krad.uif.widget.RichTable;
59  import org.kuali.rice.krad.util.KRADConstants;
60  import org.kuali.rice.krad.util.KRADUtils;
61  import org.kuali.rice.krad.web.form.UifFormBase;
62  
63  import java.util.ArrayList;
64  import java.util.Arrays;
65  import java.util.Collections;
66  import java.util.HashMap;
67  import java.util.List;
68  import java.util.Map;
69  import java.util.Set;
70  import java.util.TreeMap;
71  
72  /**
73   * Implementation of table layout manager.
74   *
75   * <p>Based on the fields defined, the {@code TableLayoutManager} will dynamically create instances of
76   * the fields for each collection row. In addition, the manager can create standard fields like the
77   * action and sequence fields for each row. The manager supports options inherited from the
78   * {@code GridLayoutManager} such as rowSpan, colSpan, and cell width settings.</p>
79   *
80   * {@inheritDoc}
81   *
82   * @author Kuali Rice Team (rice.collab@kuali.org)
83   */
84  @BeanTag(name = "tableCollectionLayout-bean", parent = "Uif-TableCollectionLayout")
85  public class TableLayoutManagerBase extends CollectionLayoutManagerBase implements TableLayoutManager {
86  
87      private static final long serialVersionUID = 3622267585541524208L;
88  
89      private int numberOfColumns;
90      private boolean suppressLineWrapping;
91      private Boolean autoTruncateColumns;
92  
93      private boolean applyAlternatingRowStyles;
94      private boolean applyDefaultCellWidths;
95  
96      private List<String> rowCssClasses;
97      private List<String> rowDataAttributes;
98  
99      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 }