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