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