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