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 }