Java tutorial
/** * Copyright 2005-2013 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.rice.krad.uif.layout; import com.google.common.collect.Lists; import org.apache.commons.lang.StringUtils; import org.kuali.rice.krad.datadictionary.parse.BeanTag; import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; import org.kuali.rice.krad.datadictionary.validator.ValidationTrace; import org.kuali.rice.krad.uif.CssConstants; import org.kuali.rice.krad.uif.UifConstants; import org.kuali.rice.krad.uif.UifPropertyPaths; import org.kuali.rice.krad.uif.component.Component; import org.kuali.rice.krad.uif.component.DataBinding; import org.kuali.rice.krad.uif.component.KeepExpression; import org.kuali.rice.krad.uif.container.CollectionGroup; import org.kuali.rice.krad.uif.container.Container; import org.kuali.rice.krad.uif.container.Group; import org.kuali.rice.krad.uif.element.Action; import org.kuali.rice.krad.uif.element.Label; import org.kuali.rice.krad.uif.element.Message; import org.kuali.rice.krad.uif.field.DataField; import org.kuali.rice.krad.uif.field.Field; import org.kuali.rice.krad.uif.field.FieldGroup; import org.kuali.rice.krad.uif.field.InputField; import org.kuali.rice.krad.uif.field.MessageField; import org.kuali.rice.krad.uif.util.ColumnCalculationInfo; import org.kuali.rice.krad.uif.util.ComponentFactory; import org.kuali.rice.krad.uif.util.ComponentUtils; import org.kuali.rice.krad.uif.util.ExpressionUtils; import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; import org.kuali.rice.krad.uif.view.ExpressionEvaluator; import org.kuali.rice.krad.uif.view.View; import org.kuali.rice.krad.uif.widget.Pager; import org.kuali.rice.krad.uif.widget.RichTable; import org.kuali.rice.krad.util.KRADUtils; import org.kuali.rice.krad.web.form.UifFormBase; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * Layout manager that works with {@code CollectionGroup} components and * renders the collection as a Table * * <p> * Based on the fields defined, the {@code TableLayoutManager} will * dynamically create instances of the fields for each collection row. In * addition, the manager can create standard fields like the action and sequence * fields for each row. The manager supports options inherited from the * {@code GridLayoutManager} such as rowSpan, colSpan, and cell width * settings. * </p> * * @author Kuali Rice Team (rice.collab@kuali.org) */ @BeanTag(name = "tableCollectionLayout-bean", parent = "Uif-TableCollectionLayout") public class TableLayoutManager extends GridLayoutManager implements CollectionLayoutManager { private static final long serialVersionUID = 3622267585541524208L; private boolean useShortLabels; private boolean repeatHeader; private Label headerLabelPrototype; private boolean renderSequenceField; private boolean generateAutoSequence; private Field sequenceFieldPrototype; private FieldGroup actionFieldPrototype; private FieldGroup subCollectionFieldGroupPrototype; private Field selectFieldPrototype; private boolean separateAddLine; private Group addLineGroup; // internal counter for the data columns (not including sequence, action) private int numberOfDataColumns; private List<Label> headerLabels; private List<Field> allRowFields; private List<Field> firstRowFields; private Pager pagerWidget; private RichTable richTable; private boolean headerAdded; private int actionColumnIndex = -1; private String actionColumnPlacement; //row details properties private Group rowDetailsGroup; private boolean rowDetailsOpen; private boolean showToggleAllDetails; private Action toggleAllDetailsAction; private boolean ajaxDetailsRetrieval; private Action expandDetailsActionPrototype; //grouping properties @KeepExpression private String groupingTitle; private String groupingPrefix; private int groupingColumnIndex; private List<String> groupingPropertyNames; //total properties private boolean renderOnlyLeftTotalLabels; private boolean showTotal; private boolean showPageTotal; private boolean showGroupTotal; private boolean generateGroupTotalRows; private Label totalLabel; private Label pageTotalLabel; private Label groupTotalLabelPrototype; private List<String> columnsToCalculate; private List<ColumnCalculationInfo> columnCalculations; private List<Component> footerCalculationComponents; //row css private Map<String, String> conditionalRowCssClasses; public TableLayoutManager() { useShortLabels = false; repeatHeader = false; renderSequenceField = true; generateAutoSequence = false; separateAddLine = false; rowDetailsOpen = false; headerLabels = new ArrayList<Label>(); allRowFields = new ArrayList<Field>(); firstRowFields = new ArrayList<Field>(); columnsToCalculate = new ArrayList<String>(); columnCalculations = new ArrayList<ColumnCalculationInfo>(); conditionalRowCssClasses = new HashMap<String, String>(); } /** * The following actions are performed: * * <ul> * <li>Sets sequence field prototype if auto sequence is true</li> * <li>Initializes the prototypes</li> * </ul> * * @see org.kuali.rice.krad.uif.layout.BoxLayoutManager#performInitialization(org.kuali.rice.krad.uif.view.View, * java.lang.Object, org.kuali.rice.krad.uif.container.Container) */ @Override public void performInitialization(View view, Object model, Container container) { CollectionGroup collectionGroup = (CollectionGroup) container; this.setupDetails(collectionGroup, view); this.setupGrouping(collectionGroup, view); if (collectionGroup.isAddViaLightBox()) { setSeparateAddLine(true); } super.performInitialization(view, model, container); getRowCssClasses().clear(); if (generateAutoSequence && !(getSequenceFieldPrototype() instanceof MessageField)) { sequenceFieldPrototype = ComponentFactory.getMessageField(); view.assignComponentIds(getSequenceFieldPrototype()); } } /** * performApplyModel override. Takes expressions that may be set in the columnCalculation objects * and populates them correctly into those component's propertyExpressions. * * @param view view instance to which the layout manager belongs * @param model Top level object containing the data (could be the form or a * top level business object, dto) * @param container */ @Override public void performApplyModel(View view, Object model, Container container) { super.performApplyModel(view, model, container); for (ColumnCalculationInfo cInfo : columnCalculations) { ExpressionUtils.populatePropertyExpressionsFromGraph(cInfo, false); } } /** * Sets up the final column count for rendering based on whether the * sequence and action fields have been generated, sets up column calculations, and richTable rowGrouping * options * * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#performFinalize(org.kuali.rice.krad.uif.view.View, * java.lang.Object, org.kuali.rice.krad.uif.container.Container) */ @Override public void performFinalize(View view, Object model, Container container) { super.performFinalize(view, model, container); UifFormBase formBase = (UifFormBase) model; CollectionGroup collectionGroup = (CollectionGroup) container; int totalColumns = getNumberOfDataColumns(); if (renderSequenceField) { totalColumns++; } if (collectionGroup.isIncludeLineSelectionField()) { totalColumns++; } if (collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly()) { totalColumns++; } setNumberOfColumns(totalColumns); // if add line event, add highlighting for added row if (UifConstants.ActionEvents.ADD_LINE.equals(formBase.getActionEvent())) { String highlightScript = "jQuery(\"#" + container.getId() + " tr:first\").effect(\"highlight\",{}, 6000);"; String onReadyScript = collectionGroup.getOnDocumentReadyScript(); if (StringUtils.isNotBlank(onReadyScript)) { highlightScript = onReadyScript + highlightScript; } collectionGroup.setOnDocumentReadyScript(highlightScript); } //setup the column calculations functionality and components if (columnCalculations != null && richTable != null && this.getAllRowFields() != null && !this.getAllRowFields().isEmpty()) { setupColumnCalculations(view, model, container, totalColumns); } //set the js properties for rowGrouping on richTables if ((groupingPropertyNames != null || StringUtils.isNotBlank(this.getGroupingTitle())) && richTable != null) { richTable.setGroupingOptionsJSString("{iGroupingColumnIndex: " + groupingColumnIndex + ", bGenerateGroupTotalRows:" + this.generateGroupTotalRows + ", bSetGroupingClassOnTR: true" + ", sGroupingClass: 'uif-groupRow'" + (this.getGroupingPrefix() != null ? ", sGroupLabelPrefix: '" + this.getGroupingPrefix() + "'" : "") + "}"); } // Calculate the number of pages for the pager widget if we are using server paging if ((this.getRichTable() == null || !this.getRichTable().isRender()) && ((CollectionGroup) container).isUseServerPaging() && this.getPagerWidget() != null) { // Set the appropriate page, total pages, and link script into the Pager CollectionLayoutUtils.setupPagerWidget(pagerWidget, collectionGroup, model); } } /** * Sets up the grouping MessageField to be used in the first column of the table layout for grouping * collection content into groups based on values of the line's fields * * @param collectionGroup collection group for this layout * @param view the view */ private void setupGrouping(CollectionGroup collectionGroup, View view) { //Grouping setup String groupingTitleExpression = ""; if (StringUtils.isNotBlank(this.getPropertyExpression(UifPropertyPaths.GROUPING_TITLE))) { groupingTitleExpression = this.getPropertyExpression(UifPropertyPaths.GROUPING_TITLE); this.setGroupingTitle(this.getPropertyExpression(UifPropertyPaths.GROUPING_TITLE)); } else if (this.getGroupingPropertyNames() != null) { for (String propertyName : this.getGroupingPropertyNames()) { groupingTitleExpression = groupingTitleExpression + ", " + propertyName; } groupingTitleExpression = groupingTitleExpression.replaceFirst(", ", "@{" + UifConstants.LINE_PATH_BIND_ADJUST_PREFIX); groupingTitleExpression = groupingTitleExpression.replace(", ", "}, @{" + UifConstants.LINE_PATH_BIND_ADJUST_PREFIX); groupingTitleExpression = groupingTitleExpression.trim() + "}"; } if (StringUtils.isNotBlank(groupingTitleExpression)) { MessageField groupingMessageField = ComponentFactory.getColGroupingField(); groupingMessageField.getMessage().getPropertyExpressions().put(UifPropertyPaths.MESSAGE_TEXT, groupingTitleExpression); groupingMessageField.addDataAttribute(UifConstants.DataAttributes.ROLE, UifConstants.RoleTypes.ROW_GROUPING); view.assignComponentIds(groupingMessageField); List<Component> theItems = new ArrayList<Component>(); theItems.add(groupingMessageField); theItems.addAll(collectionGroup.getItems()); collectionGroup.setItems(theItems); } } /** * Setup the column calculations functionality and components * * @param view the view * @param model the model * @param container the parent container * @param totalColumns total number of columns in the table */ protected void setupColumnCalculations(View view, Object model, Container container, int totalColumns) { footerCalculationComponents = new ArrayList<Component>(totalColumns); //add nulls for each column to start - nulls will be processed by the ftl as a blank cell for (int i = 0; i < totalColumns; i++) { footerCalculationComponents.add(null); } int leftLabelColumnIndex = 0; if (groupingPropertyNames != null || StringUtils.isNotBlank(this.getGroupingTitle())) { leftLabelColumnIndex = 1; } //process each column calculation for (ColumnCalculationInfo cInfo : columnCalculations) { //propertyName is REQUIRED throws exception if not set if (StringUtils.isNotBlank(cInfo.getPropertyName())) { for (int i = 0; i < this.getNumberOfColumns(); i++) { Component component = this.getAllRowFields().get(i); if (component != null && component instanceof DataField && ((DataField) component).getPropertyName().equals(cInfo.getPropertyName())) { cInfo.setColumnNumber(i); } } this.getColumnsToCalculate().add(cInfo.getColumnNumber().toString()); } else { throw new RuntimeException("TableLayoutManager(" + container.getId() + "->" + this.getId() + ") ColumnCalculationInfo MUST have a propertyName set"); } // create a new field group to hold the totals fields FieldGroup calculationFieldGroup = ComponentFactory.getFieldGroup(); calculationFieldGroup.addDataAttribute(UifConstants.DataAttributes.ROLE, "totalsBlock"); List<Component> calculationFieldGroupItems = new ArrayList<Component>(); //setup page total field and add it to footer's group for this column if (cInfo.isShowPageTotal()) { Field pageTotalDataField = setupTotalField(cInfo.getPageTotalField(), cInfo, this.isShowPageTotal(), this.getPageTotalLabel(), "pageTotal", leftLabelColumnIndex); calculationFieldGroupItems.add(pageTotalDataField); } //setup total field and add it to footer's group for this column if (cInfo.isShowTotal()) { Field totalDataField = setupTotalField(cInfo.getTotalField(), cInfo, this.isShowTotal(), this.getTotalLabel(), "total", leftLabelColumnIndex); if (!cInfo.isRecalculateTotalClientSide()) { totalDataField.addDataAttribute(UifConstants.DataAttributes.SKIP_TOTAL, "true"); } calculationFieldGroupItems.add(totalDataField); } //setup total field and add it to footer's group for this column //do not generate group total rows if group totals are not being shown if (cInfo.isShowGroupTotal()) { Field groupTotalDataField = setupTotalField(cInfo.getGroupTotalFieldPrototype(), cInfo, this.isShowGroupTotal(), this.getGroupTotalLabelPrototype(), "groupTotal", leftLabelColumnIndex); groupTotalDataField.setId(container.getId() + "_gTotal" + cInfo.getColumnNumber()); groupTotalDataField.setStyle("display: none;"); calculationFieldGroupItems.add(groupTotalDataField); if (this.isRenderOnlyLeftTotalLabels() && !this.isShowGroupTotal()) { generateGroupTotalRows = false; } else { generateGroupTotalRows = true; } } calculationFieldGroup.setItems(calculationFieldGroupItems); view.assignComponentIds(calculationFieldGroup); //Determine if there is already a fieldGroup present for this column's footer //if so create a new group and add the new calculation fields to the already existing ones //otherwise just add it Component component = footerCalculationComponents.get(cInfo.getColumnNumber()); if (component != null && component instanceof FieldGroup) { Group verticalComboCalcGroup = ComponentFactory.getVerticalBoxGroup(); view.assignComponentIds(verticalComboCalcGroup); List<Component> comboGroupItems = new ArrayList<Component>(); comboGroupItems.add(component); comboGroupItems.add(calculationFieldGroup); verticalComboCalcGroup.setItems(comboGroupItems); footerCalculationComponents.set(cInfo.getColumnNumber(), verticalComboCalcGroup); } else if (component != null && component instanceof Group) { List<Component> comboGroupItems = new ArrayList<Component>(); comboGroupItems.addAll(((Group) component).getItems()); comboGroupItems.add(calculationFieldGroup); ((Group) component).setItems(comboGroupItems); footerCalculationComponents.set(cInfo.getColumnNumber(), component); } else { footerCalculationComponents.set(cInfo.getColumnNumber(), calculationFieldGroup); } } //special processing for the left labels - when there are no total fields in this column //add the label to the column footer directly if (this.renderOnlyLeftTotalLabels && footerCalculationComponents.get(leftLabelColumnIndex) == null) { Group labelGroup = ComponentFactory.getVerticalBoxGroup(); view.assignComponentIds(labelGroup); List<Component> groupItems = new ArrayList<Component>(); if (this.isShowGroupTotal()) { //display none - this label is copied by the javascript groupTotalLabelPrototype.setStyle("display: none;"); groupTotalLabelPrototype.addDataAttribute(UifConstants.DataAttributes.ROLE, "groupTotalLabel"); view.assignComponentIds(groupTotalLabelPrototype); groupItems.add(groupTotalLabelPrototype); } if (this.isShowPageTotal()) { view.assignComponentIds(pageTotalLabel); pageTotalLabel.addDataAttribute(UifConstants.DataAttributes.ROLE, "pageTotal"); groupItems.add(pageTotalLabel); } if (this.isShowTotal()) { view.assignComponentIds(totalLabel); groupItems.add(totalLabel); } labelGroup.setItems(groupItems); footerCalculationComponents.set(leftLabelColumnIndex, labelGroup); } // perform the lifecycle for all the newly generated components as a result of processing the // column calculations for (Component component : footerCalculationComponents) { view.getViewHelperService().spawnSubLifecyle(view, model, component, container, null, null); } } /** * Setup the totalField with the columnCalculationInfo(cInfo) passed in. Param show represents the * tableLayoutManager's setting for the type of total being processed. * * @param totalField the field to setup * @param cInfo ColumnCalculation info to use to setup the field * @param show show the field (if renderOnlyLeftTotalLabels is true, otherwise uses value in cInfo) * @param leftLabel the leftLabel, not used if renderOnlyLeftTotalLabels is false * @param type type used to set the dataAttribute role - used by the js for selection * @param leftLabelColumnIndex index of the leftLabelColumn (0 or 1 if grouping enabled - hidden column) * @return the field with cInfo and tableLayoutManager settings applied as appropriate */ protected Field setupTotalField(Field totalField, ColumnCalculationInfo cInfo, boolean show, Label leftLabel, String type, int leftLabelColumnIndex) { //setup the totals field Field totalDataField = totalField; totalDataField.addDataAttribute(UifConstants.DataAttributes.ROLE, type); totalDataField.addDataAttribute("function", cInfo.getCalculationFunctionName()); totalDataField.addDataAttribute("params", cInfo.getCalculationFunctionExtraData()); if (cInfo.getColumnNumber() != leftLabelColumnIndex) { //do not render labels for columns which have totals and the renderOnlyLeftTotalLabels //flag is set totalDataField.getFieldLabel().setRender(!this.isRenderOnlyLeftTotalLabels()); } else if (cInfo.getColumnNumber() == leftLabelColumnIndex && this.isRenderOnlyLeftTotalLabels()) { //renderOnlyLeftTotalLabel is set to true, but the column has a total itself - set the layout //manager settings directly into the field totalDataField.setFieldLabel(leftLabel); } if (this.isRenderOnlyLeftTotalLabels()) { totalDataField.setRender(show); } return totalDataField; } /** * Assembles the field instances for the collection line. The given sequence * field prototype is copied for the line sequence field. Likewise a copy of * the actionFieldPrototype is made and the given actions are set as the * items for the action field. Finally the generated items are assembled * together into the allRowFields list with the given lineFields. * * @see org.kuali.rice.krad.uif.layout.CollectionLayoutManager#buildLine(org.kuali.rice.krad.uif.view.View, * java.lang.Object, org.kuali.rice.krad.uif.container.CollectionGroup, * java.util.List, java.util.List, java.lang.String, java.util.List, * java.lang.String, java.lang.Object, int) */ public void buildLine(View view, Object model, CollectionGroup collectionGroup, List<Field> lineFields, List<FieldGroup> subCollectionFields, String bindingPath, List<Action> actions, String idSuffix, Object currentLine, int lineIndex) { // since expressions are not evaluated on child components yet, we need to evaluate any properties // we are going to read for building the table ExpressionEvaluator expressionEvaluator = view.getViewHelperService().getExpressionEvaluator(); for (Field lineField : lineFields) { lineField.pushObjectToContext(UifConstants.ContextVariableNames.PARENT, collectionGroup); lineField.pushAllToContext(view.getViewHelperService().getCommonContext(view, lineField)); expressionEvaluator.evaluatePropertyExpression(view, lineField.getContext(), lineField, UifPropertyPaths.ROW_SPAN, true); expressionEvaluator.evaluatePropertyExpression(view, lineField.getContext(), lineField, UifPropertyPaths.COL_SPAN, true); expressionEvaluator.evaluatePropertyExpression(view, lineField.getContext(), lineField, UifPropertyPaths.REQUIRED, true); expressionEvaluator.evaluatePropertyExpression(view, lineField.getContext(), lineField, UifPropertyPaths.READ_ONLY, true); } // if first line for table set number of data columns if (allRowFields.isEmpty()) { if (isSuppressLineWrapping()) { setNumberOfDataColumns(lineFields.size()); } else { setNumberOfDataColumns(getNumberOfColumns()); } } boolean isAddLine = false; // If first row or row wrap is happening if (lineIndex == -1 || (lineFields.size() != numberOfDataColumns && ((lineIndex + 1) * numberOfDataColumns) < lineFields.size())) { isAddLine = true; } // capture the first row of fields for widgets that build off the table if (lineIndex == 0 || this.firstRowFields.isEmpty()) { this.firstRowFields = lineFields; } boolean renderActions = collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly(); int extraColumns = 0; String rowCss = ""; boolean addLineInTable = collectionGroup.isRenderAddLine() && !collectionGroup.isReadOnly() && !isSeparateAddLine(); if (collectionGroup.isHighlightNewItems() && ((UifFormBase) model).isAddedCollectionItem(currentLine)) { rowCss = collectionGroup.getNewItemsCssClass(); } else if (isAddLine && addLineInTable) { rowCss = collectionGroup.getAddItemCssClass(); this.addStyleClass(CssConstants.Classes.HAS_ADD_LINE); } // do not allow null rowCss if (rowCss == null) { rowCss = ""; } // conditionalRowCssClass generation logic, if applicable if (conditionalRowCssClasses != null && !conditionalRowCssClasses.isEmpty()) { int oddRemainder = 1; if (!addLineInTable) { oddRemainder = 0; } boolean isOdd = lineIndex % 2 == oddRemainder || lineIndex == -1; Map<String, Object> lineContext = new HashMap<String, Object>(); lineContext.putAll(this.getContext()); lineContext.put(UifConstants.ContextVariableNames.LINE, currentLine); lineContext.put(UifConstants.ContextVariableNames.MANAGER, this); lineContext.put(UifConstants.ContextVariableNames.VIEW, view); lineContext.put(UifConstants.ContextVariableNames.LINE_SUFFIX, idSuffix); lineContext.put(UifConstants.ContextVariableNames.INDEX, Integer.valueOf(lineIndex)); lineContext.put(UifConstants.ContextVariableNames.COLLECTION_GROUP, collectionGroup); lineContext.put(UifConstants.ContextVariableNames.IS_ADD_LINE, isAddLine && !isSeparateAddLine()); lineContext.put(UifConstants.ContextVariableNames.READONLY_LINE, collectionGroup.isReadOnly()); // get row css based on conditionalRowCssClasses map rowCss = rowCss + " " + KRADUtils.generateRowCssClassString(conditionalRowCssClasses, lineIndex, isOdd, lineContext, expressionEvaluator); } rowCss = StringUtils.removeStart(rowCss, " "); this.getRowCssClasses().add(rowCss); // if separate add line prepare the add line group if (isAddLine && separateAddLine) { if (StringUtils.isBlank(addLineGroup.getTitle()) && StringUtils.isBlank(addLineGroup.getHeader().getHeaderText())) { addLineGroup.getHeader().setHeaderText(collectionGroup.getAddLabel()); } addLineGroup.setItems(lineFields); List<Component> footerItems = new ArrayList<Component>(actions); footerItems.addAll(addLineGroup.getFooter().getItems()); addLineGroup.getFooter().setItems(footerItems); if (collectionGroup.isAddViaLightBox()) { String actionScript = "showLightboxComponent('" + addLineGroup.getId() + "');"; if (StringUtils.isNotBlank(collectionGroup.getAddViaLightBoxAction().getActionScript())) { actionScript = collectionGroup.getAddViaLightBoxAction().getActionScript() + actionScript; } collectionGroup.getAddViaLightBoxAction().setActionScript(actionScript); addLineGroup.setStyle("display: none"); } return; } // TODO: implement repeat header if (!headerAdded) { headerLabels = new ArrayList<Label>(); allRowFields = new ArrayList<Field>(); buildTableHeaderRows(collectionGroup, lineFields); ComponentUtils.pushObjectToContext(headerLabels, UifConstants.ContextVariableNames.LINE, currentLine); ComponentUtils.pushObjectToContext(headerLabels, UifConstants.ContextVariableNames.INDEX, new Integer(lineIndex)); headerAdded = true; } // set label field rendered to true on line fields and adjust cell properties for (Field field : lineFields) { field.setLabelRendered(true); field.setFieldLabel(null); setCellAttributes(field); } int rowCount = calculateNumberOfRows(lineFields); int rowSpan = rowCount + subCollectionFields.size(); if (actionColumnIndex == 1 && renderActions) { addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions); } // sequence field is always first and should span all rows for the line if (renderSequenceField) { Field sequenceField = null; if (!isAddLine) { sequenceField = ComponentUtils.copy(getSequenceFieldPrototype(), idSuffix); //Ignore in validation processing sequenceField.addDataAttribute(UifConstants.DataAttributes.VIGNORE, "yes"); if (generateAutoSequence && (sequenceField instanceof MessageField)) { ((MessageField) sequenceField).setMessageText(Integer.toString(lineIndex + 1)); } } else { sequenceField = ComponentFactory.getMessageField(); view.assignComponentIds(sequenceField); Message sequenceMessage = ComponentUtils.copy(collectionGroup.getAddLineLabel(), idSuffix); ((MessageField) sequenceField).setMessage(sequenceMessage); // adjusting add line label to match sequence prototype cells attributes sequenceField.setCellWidth(getSequenceFieldPrototype().getCellWidth()); sequenceField.setCellStyle(getSequenceFieldPrototype().getCellStyle()); } sequenceField.setRowSpan(rowSpan); if (sequenceField instanceof DataBinding) { ((DataBinding) sequenceField).getBindingInfo().setBindByNamePrefix(bindingPath); } setCellAttributes(sequenceField); ComponentUtils.updateContextForLine(sequenceField, currentLine, lineIndex, idSuffix); allRowFields.add(sequenceField); extraColumns++; if (actionColumnIndex == 2 && renderActions) { addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions); } } // select field will come after sequence field (if enabled) or be first column if (collectionGroup.isIncludeLineSelectionField()) { Field selectField = ComponentUtils.copy(getSelectFieldPrototype(), idSuffix); CollectionLayoutUtils.prepareSelectFieldForLine(selectField, collectionGroup, bindingPath, currentLine); ComponentUtils.updateContextForLine(selectField, currentLine, lineIndex, idSuffix); setCellAttributes(selectField); allRowFields.add(selectField); extraColumns++; if (renderActions) { if ((actionColumnIndex == 3 && renderSequenceField) || (actionColumnIndex == 2 && !renderSequenceField)) { addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions); } } } // now add the fields in the correct position int cellPosition = 0; int columnNumber = 0; boolean renderActionsLast = actionColumnIndex == -1 || actionColumnIndex > lineFields.size() + extraColumns; boolean hasGrouping = (groupingPropertyNames != null || StringUtils.isNotBlank(this.getGroupingTitle())); boolean insertActionField = false; for (Field lineField : lineFields) { //Check to see if ActionField needs to be inserted before this lineField because of wrapping. // Since actionField has a colSpan of 1 add that to the previous cellPosition instead of the // current lineField's colSpan. // Only insert if ActionField has to be placed at the end. Else the specification of actionColumnIndex should // take care of putting it in the right location insertActionField = (cellPosition != 0 && lineFields.size() != numberOfDataColumns) && renderActions && renderActionsLast && ((cellPosition % numberOfDataColumns) == 0); cellPosition += lineField.getColSpan(); columnNumber++; //special handling for grouping field - this field MUST be first if (hasGrouping && lineField instanceof MessageField && lineField.getDataAttributes().get(UifConstants.DataAttributes.ROLE) != null && lineField.getDataAttributes().get(UifConstants.DataAttributes.ROLE) .equals(UifConstants.RoleTypes.ROW_GROUPING)) { int groupFieldIndex = allRowFields.size() - extraColumns; allRowFields.add(groupFieldIndex, lineField); groupingColumnIndex = 0; if (isAddLine) { ((MessageField) lineField).getMessage().getPropertyExpressions() .remove(UifPropertyPaths.MESSAGE_TEXT); ((MessageField) lineField).getMessage().setMessageText("addLine"); } } else { // If the row wraps before the last element if (insertActionField) { addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions); } allRowFields.add(lineField); } // action field if (!renderActionsLast && cellPosition == (actionColumnIndex - extraColumns - 1)) { addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions); } //details action if (lineField instanceof FieldGroup && ((FieldGroup) lineField).getItems() != null) { for (Component component : ((FieldGroup) lineField).getItems()) { if (component != null && component instanceof Action && component.getDataAttributes().get("role") != null && component.getDataAttributes().get("role").equals("detailsLink") && StringUtils.isBlank(((Action) component).getActionScript())) { ((Action) component) .setActionScript("rowDetailsActionHandler(this,'" + this.getId() + "');"); } } } //special column calculation handling to identify what type of handler will be attached //and add special styling if (lineField instanceof InputField && columnCalculations != null) { for (ColumnCalculationInfo cInfo : columnCalculations) { if (cInfo.getPropertyName().equals(((InputField) lineField).getPropertyName())) { if (cInfo.isCalculateOnKeyUp()) { lineField.addDataAttribute(UifConstants.DataAttributes.TOTAL, "keyup"); } else { lineField.addDataAttribute(UifConstants.DataAttributes.TOTAL, "change"); } lineField.addStyleClass("uif-calculationField"); } } } } if (lineFields.size() == numberOfDataColumns && renderActions && renderActionsLast) { addActionColumn(idSuffix, currentLine, lineIndex, rowSpan, actions); } // update colspan on sub-collection fields for (FieldGroup subCollectionField : subCollectionFields) { subCollectionField.setColSpan(numberOfDataColumns); } // add sub-collection fields to end of data fields allRowFields.addAll(subCollectionFields); } /** * Adds the action field in a row * * @param idSuffix * @param currentLine * @param lineIndex * @param rowSpan * @param actions */ private void addActionColumn(String idSuffix, Object currentLine, int lineIndex, int rowSpan, List<Action> actions) { FieldGroup lineActionsField = ComponentUtils.copy(getActionFieldPrototype(), idSuffix); ComponentUtils.updateContextForLine(lineActionsField, currentLine, lineIndex, idSuffix); lineActionsField.setRowSpan(rowSpan); lineActionsField.setItems(actions); setCellAttributes(lineActionsField); allRowFields.add(lineActionsField); } /** * Create the {@code Label} instances that will be used to render * the table header * * <p> * For each column, a copy of headerLabelPrototype is made that determines * the label configuration. The actual label text comes from the field for * which the header applies to. The first column is always the sequence (if * enabled) and the last column contains the actions. Both the sequence and * action header fields will span all rows for the header. * </p> * * <p> * The headerLabels list will contain the final list of header fields built * </p> * * @param collectionGroup CollectionGroup container the table applies to * @param lineFields fields for the data columns from which the headers are pulled */ protected void buildTableHeaderRows(CollectionGroup collectionGroup, List<Field> lineFields) { // row count needed to determine the row span for the sequence and // action fields, since they should span all rows for the line int rowCount = calculateNumberOfRows(lineFields); boolean renderActions = collectionGroup.isRenderLineActions() && !collectionGroup.isReadOnly(); String idSuffix = collectionGroup.getSubCollectionSuffix(); int extraColumns = 0; if (actionColumnIndex == 1 && renderActions) { addActionHeader(rowCount, idSuffix, 1); } // first column is sequence label (if action column not 1) if (renderSequenceField) { getSequenceFieldPrototype().setLabelRendered(true); getSequenceFieldPrototype().setRowSpan(rowCount); addHeaderField(getSequenceFieldPrototype(), idSuffix, 1); extraColumns++; if (actionColumnIndex == 2 && renderActions) { addActionHeader(rowCount, idSuffix, 2); } } // next is select field if (collectionGroup.isIncludeLineSelectionField()) { getSelectFieldPrototype().setLabelRendered(true); getSelectFieldPrototype().setRowSpan(rowCount); addHeaderField(getSelectFieldPrototype(), idSuffix, 1); extraColumns++; if (actionColumnIndex == 3 && renderActions && renderSequenceField) { addActionHeader(rowCount, idSuffix, 3); } else if (actionColumnIndex == 2 && renderActions) { addActionHeader(rowCount, idSuffix, 2); } } // pull out label fields from the container's items int cellPosition = 0; boolean renderActionsLast = actionColumnIndex == -1 || actionColumnIndex > lineFields.size() + extraColumns; boolean insertActionHeader = false; for (Field field : lineFields) { if (!field.isRender() && StringUtils.isEmpty(field.getProgressiveRender())) { continue; } //Check to see if ActionField needs to be inserted before this lineField because of wrapping. // Since actionField has a colSpan of 1 add that to the previous cellPosition instead of the // current lineField's colSpan. // Only Insert if ActionField has to be placed at the end. Else the specification of actionColumnIndex // should take care of putting it in the right location insertActionHeader = (cellPosition != 0 && lineFields.size() != numberOfDataColumns && renderActions && renderActionsLast && ((cellPosition % numberOfDataColumns) == 0)); if (insertActionHeader) { addActionHeader(rowCount, idSuffix, cellPosition); } cellPosition += field.getColSpan(); addHeaderField(field, idSuffix, cellPosition); // add action header if (renderActions && !renderActionsLast && cellPosition == actionColumnIndex - extraColumns - 1) { cellPosition += 1; addActionHeader(rowCount, idSuffix, cellPosition); } } if (lineFields.size() == numberOfDataColumns && renderActions && renderActionsLast) { cellPosition += 1; addActionHeader(rowCount, idSuffix, cellPosition); } } /** * Adds the action header * * @param rowCount * @param idSuffix suffix for the header id, also column will be added * @param cellPosition */ protected void addActionHeader(int rowCount, String idSuffix, int cellPosition) { getActionFieldPrototype().setLabelRendered(true); getActionFieldPrototype().setRowSpan(rowCount); addHeaderField(getActionFieldPrototype(), idSuffix, cellPosition); } /** * Creates a new instance of the header field prototype and then sets the * label to the short (if useShortLabels is set to true) or long label of * the given component. After created the header field is added to the list * making up the table header * * @param field field instance the header field is being created for * @param idSuffix suffix for the header id, also column will be added * @param column column number for the header, used for setting the id */ protected void addHeaderField(Field field, String idSuffix, int column) { String labelSuffix = UifConstants.IdSuffixes.COLUMN + column; if (StringUtils.isNotBlank(idSuffix)) { labelSuffix = idSuffix + labelSuffix; } Label headerLabel = ComponentUtils.copy(getHeaderLabelPrototype(), labelSuffix); if (useShortLabels) { headerLabel.setLabelText(field.getShortLabel()); } else { headerLabel.setLabelText(field.getLabel()); } headerLabel.setRowSpan(field.getRowSpan()); headerLabel.setColSpan(field.getColSpan()); if ((field.getRequired() != null) && field.getRequired().booleanValue()) { headerLabel.getRequiredMessage().setRender(!field.isReadOnly()); } else { headerLabel.getRequiredMessage().setRender(false); } setCellAttributes(field); // copy cell attributes from the field to the label headerLabel.setCellCssClasses(field.getCellCssClasses()); headerLabel.setCellStyle(field.getCellStyle()); headerLabel.setCellWidth(field.getCellWidth()); headerLabels.add(headerLabel); } /** * Calculates how many rows will be needed per collection line to display * the list of fields. Assumption is made that the total number of cells the * fields take up is evenly divisible by the configured number of columns * * @param items list of items that make up one collection line * @return number of rows */ protected int calculateNumberOfRows(List<? extends Field> items) { int rowCount = 0; // check flag that indicates only one row should be created if (isSuppressLineWrapping()) { return 1; } // If Overflow is greater than 0 then calculate the col span for the last item in the overflowed row if (items.size() % getNumberOfDataColumns() > 0) { //get the last line item Field field = items.get(items.size() - 1); int colSize = 0; for (Field f : items) { colSize += f.getColSpan(); } field.setColSpan(1 + (numberOfDataColumns - (colSize % numberOfDataColumns))); rowCount = ((items.size() / getNumberOfDataColumns()) + 1); } else { rowCount = items.size() / getNumberOfDataColumns(); } return rowCount; } /** * @see CollectionLayoutManager#getSupportedContainer() */ @Override public Class<? extends Container> getSupportedContainer() { return CollectionGroup.class; } /** * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#getComponentsForLifecycle() */ @Override public List<Component> getComponentsForLifecycle() { List<Component> components = super.getComponentsForLifecycle(); components.add(pagerWidget); components.add(richTable); components.add(addLineGroup); components.addAll(headerLabels); components.addAll(allRowFields); for (ColumnCalculationInfo cInfo : columnCalculations) { components.add(cInfo.getTotalField()); components.add(cInfo.getPageTotalField()); components.add(cInfo.getGroupTotalFieldPrototype()); } if (isShowToggleAllDetails()) { components.add(toggleAllDetailsAction); } return components; } /** * @see org.kuali.rice.krad.uif.layout.LayoutManager#getComponentPrototypes() */ @Override public List<Component> getComponentPrototypes() { List<Component> components = super.getComponentPrototypes(); components.add(getHeaderLabelPrototype()); components.add(getSequenceFieldPrototype()); components.add(getActionFieldPrototype()); components.add(getSubCollectionFieldGroupPrototype()); components.add(getSelectFieldPrototype()); return components; } /** * Indicates whether the short label for the collection field should be used * as the table header or the regular label * * @return true if short label should be used, false if long label * should be used */ @BeanTagAttribute(name = "useShortLabels") public boolean isUseShortLabels() { return this.useShortLabels; } /** * Setter for the use short label indicator * * @param useShortLabels */ public void setUseShortLabels(boolean useShortLabels) { this.useShortLabels = useShortLabels; } /** * Indicates whether the header should be repeated before each collection * row. If false the header is only rendered at the beginning of the table * * @return true if header should be repeated, false if it should * only be rendered once */ @BeanTagAttribute(name = "repeatHeader") public boolean isRepeatHeader() { return this.repeatHeader; } /** * Setter for the repeat header indicator * * @param repeatHeader */ public void setRepeatHeader(boolean repeatHeader) { this.repeatHeader = repeatHeader; } /** * {@code Label} instance to use as a prototype for creating the * tables header fields. For each header field the prototype will be copied * and adjusted as necessary * * @return Label instance to serve as prototype */ @BeanTagAttribute(name = "headerLabelPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public Label getHeaderLabelPrototype() { return this.headerLabelPrototype; } /** * Setter for the header field prototype * * @param headerLabelPrototype */ public void setHeaderLabelPrototype(Label headerLabelPrototype) { this.headerLabelPrototype = headerLabelPrototype; } /** * List of {@code Label} instances that should be rendered to make * up the tables header * * @return List of label field instances */ public List<Label> getHeaderLabels() { return this.headerLabels; } /** * Indicates whether the sequence field should be rendered for the * collection * * @return true if sequence field should be rendered, false if not */ @BeanTagAttribute(name = "renderSequenceField") public boolean isRenderSequenceField() { return this.renderSequenceField; } /** * Setter for the render sequence field indicator * * @param renderSequenceField */ public void setRenderSequenceField(boolean renderSequenceField) { this.renderSequenceField = renderSequenceField; } /** * Attribute name to use as sequence value. For each collection line the * value of this field on the line will be retrieved and used as the * sequence value * * @return sequence property name */ @BeanTagAttribute(name = "sequencePropertyName") public String getSequencePropertyName() { if ((getSequenceFieldPrototype() != null) && (getSequenceFieldPrototype() instanceof DataField)) { return ((DataField) getSequenceFieldPrototype()).getPropertyName(); } return null; } /** * Setter for the sequence property name * * @param sequencePropertyName */ public void setSequencePropertyName(String sequencePropertyName) { if ((getSequenceFieldPrototype() != null) && (getSequenceFieldPrototype() instanceof DataField)) { ((DataField) getSequenceFieldPrototype()).setPropertyName(sequencePropertyName); } } /** * Indicates whether the sequence field should be generated with the current * line number * * <p> * If set to true the sequence field prototype will be changed to a message * field (if not already a message field) and the text will be set to the * current line number * </p> * * @return true if the sequence field should be generated from the * line number, false if not */ @BeanTagAttribute(name = "generateAutoSequence") public boolean isGenerateAutoSequence() { return this.generateAutoSequence; } /** * Setter for the generate auto sequence field * * @param generateAutoSequence */ public void setGenerateAutoSequence(boolean generateAutoSequence) { this.generateAutoSequence = generateAutoSequence; } /** * {@code Field} instance to serve as a prototype for the * sequence field. For each collection line this instance is copied and * adjusted as necessary * * @return Attribute field instance */ @BeanTagAttribute(name = "sequenceFieldPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public Field getSequenceFieldPrototype() { return this.sequenceFieldPrototype; } /** * Setter for the sequence field prototype * * @param sequenceFieldPrototype */ public void setSequenceFieldPrototype(Field sequenceFieldPrototype) { this.sequenceFieldPrototype = sequenceFieldPrototype; } /** * {@code FieldGroup} instance to serve as a prototype for the actions * column. For each collection line this instance is copied and adjusted as * necessary. Note the actual actions for the group come from the collection * groups actions List * (org.kuali.rice.krad.uif.container.CollectionGroup.getActions()). The * FieldGroup prototype is useful for setting styling of the actions column * and for the layout of the action fields. Note also the label associated * with the prototype is used for the action column header * * @return GroupField instance */ @BeanTagAttribute(name = "actionFieldPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public FieldGroup getActionFieldPrototype() { return this.actionFieldPrototype; } /** * Setter for the action field prototype * * @param actionFieldPrototype */ public void setActionFieldPrototype(FieldGroup actionFieldPrototype) { this.actionFieldPrototype = actionFieldPrototype; } /** * @see org.kuali.rice.krad.uif.layout.CollectionLayoutManager#getSubCollectionFieldGroupPrototype() */ @BeanTagAttribute(name = "subCollectionFieldGroupPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public FieldGroup getSubCollectionFieldGroupPrototype() { return this.subCollectionFieldGroupPrototype; } /** * Setter for the sub-collection field group prototype * * @param subCollectionFieldGroupPrototype */ public void setSubCollectionFieldGroupPrototype(FieldGroup subCollectionFieldGroupPrototype) { this.subCollectionFieldGroupPrototype = subCollectionFieldGroupPrototype; } /** * Field instance that serves as a prototype for creating the select field on each line when * {@link org.kuali.rice.krad.uif.container.CollectionGroup#isIncludeLineSelectionField()} is true * * <p> * This prototype can be used to set the control used for the select field (generally will be a checkbox control) * in addition to styling and other setting. The binding path will be formed with using the * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getLineSelectPropertyName()} or if not set the * framework * will use {@link org.kuali.rice.krad.web.form.UifFormBase#getSelectedCollectionLines()} * </p> * * @return select field prototype instance */ @BeanTagAttribute(name = "selectFieldPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public Field getSelectFieldPrototype() { return selectFieldPrototype; } /** * Setter for the prototype instance for select fields * * @param selectFieldPrototype */ public void setSelectFieldPrototype(Field selectFieldPrototype) { this.selectFieldPrototype = selectFieldPrototype; } /** * Indicates whether the add line should be rendered in a separate group, or as part of the table (first line) * * <p> * When separate add line is enabled, the fields for the add line will be placed in the {@link #getAddLineGroup()}. * This group can be used to configure the add line presentation. In addition to the fields, the header on the * group (unless already set) will be set to * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLabel()} and the add line actions will * be placed into the group's footer. * </p> * * @return true if add line should be separated, false if it should be placed into the table */ @BeanTagAttribute(name = "separateAddLine") public boolean isSeparateAddLine() { return separateAddLine; } /** * Setter for the separate add line indicator * * @param separateAddLine */ public void setSeparateAddLine(boolean separateAddLine) { this.separateAddLine = separateAddLine; } /** * When {@link #isSeparateAddLine()} is true, this group will be used to render the add line * * <p> * This group can be used to configure how the add line will be rendered. For example the layout manager configured * on the group will be used to rendered the add line fields. If the header (title) is not set on the group, it * will be set from * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLabel()}. In addition, * {@link org.kuali.rice.krad.uif.container.CollectionGroup#getAddLineActions()} will be added to the group * footer items. * </p> * * @return Group instance for the collection add line */ @BeanTagAttribute(name = "addLineGroup", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public Group getAddLineGroup() { return addLineGroup; } /** * Setter for the add line Group * * @param addLineGroup */ public void setAddLineGroup(Group addLineGroup) { this.addLineGroup = addLineGroup; } /** * List of {@link Field} instances that make up all the table's rows of data * * @return table body fields */ public List<Field> getAllRowFields() { return this.allRowFields; } /** * List of {@link Field} instances that make us the table's first row of data * * @return list of field instances */ public List<Field> getFirstRowFields() { return firstRowFields; } /** * The Pager widget for this TableLayoutManager which defines settings for paging * * <p>The settings in this widget are only used by TableLayoutManagers which DO NOT take advantage of * the RichTable option (this has its own paging implementation). To turn off RichTable and use a basic * table with server paging set richTable.render="false" and useServerPaging="true" on the CollectionGroup * which uses this layout manager.</p> * * @return the Pager widget */ public Pager getPagerWidget() { return pagerWidget; } /** * Set the Pager widget * * @param pagerWidget */ public void setPagerWidget(Pager pagerWidget) { this.pagerWidget = pagerWidget; } /** * Widget associated with the table to add functionality such as sorting, * paging, and export * * @return RichTable instance */ @BeanTagAttribute(name = "richTable", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public RichTable getRichTable() { return this.richTable; } /** * Setter for the rich table widget * * @param richTable */ public void setRichTable(RichTable richTable) { this.richTable = richTable; } /** * @return the numberOfDataColumns */ @BeanTagAttribute(name = "numberOfDataColumns") public int getNumberOfDataColumns() { return this.numberOfDataColumns; } /** * @param numberOfDataColumns the numberOfDataColumns to set */ public void setNumberOfDataColumns(int numberOfDataColumns) { this.numberOfDataColumns = numberOfDataColumns; } /** * @see org.kuali.rice.krad.uif.widget.RichTable#getHiddenColumns() */ @BeanTagAttribute(name = "hiddenColumns", type = BeanTagAttribute.AttributeType.SETVALUE) public Set<String> getHiddenColumns() { if (richTable != null) { return richTable.getHiddenColumns(); } return null; } /** * @see org.kuali.rice.krad.uif.widget.RichTable#setHiddenColumns(java.util.Set<java.lang.String>) */ public void setHiddenColumns(Set<String> hiddenColumns) { if (richTable != null) { richTable.setHiddenColumns(hiddenColumns); } } /** * @see org.kuali.rice.krad.uif.widget.RichTable#getSortableColumns() */ @BeanTagAttribute(name = "sortableColumns", type = BeanTagAttribute.AttributeType.SETVALUE) public Set<String> getSortableColumns() { if (richTable != null) { return richTable.getSortableColumns(); } return null; } /** * @see org.kuali.rice.krad.uif.widget.RichTable#setSortableColumns(java.util.Set<java.lang.String>) */ public void setSortableColumns(Set<String> sortableColumns) { if (richTable != null) { richTable.setSortableColumns(sortableColumns); } } /** * Indicates the index of the action column * * @return the action column index */ @BeanTagAttribute(name = "actionColumnIndex") public int getActionColumnIndex() { return actionColumnIndex; } /** * Indicates the actions column placement * * <p> * Valid values are 'LEFT', 'RIGHT' or any valid number. The default is 'RIGHT' or '-1'. The column placement index * takes all displayed columns, including sequence and selection columns, into account. * </p> * * @return the action column placement */ @BeanTagAttribute(name = "actionColumnPlacement") public String getActionColumnPlacement() { return actionColumnPlacement; } /** * Setter for the action column placement * * @param actionColumnPlacement action column placement string */ public void setActionColumnPlacement(String actionColumnPlacement) { this.actionColumnPlacement = actionColumnPlacement; if ("LEFT".equals(actionColumnPlacement)) { actionColumnIndex = 1; } else if ("RIGHT".equals(actionColumnPlacement)) { actionColumnIndex = -1; } else if (StringUtils.isNumeric(actionColumnPlacement)) { actionColumnIndex = Integer.parseInt(actionColumnPlacement); } } /** * The row details info group to use when using a TableLayoutManager with the a richTable. * * <p>This group will be displayed when the user clicks the "Details" link/image on a row. * This allows extra/long data to be hidden in table rows and then revealed during interaction * with the table without the need to leave the page. Allows for any group content.</p> * * <p>Does not currently work with javascript required content.</p> * * @return rowDetailsGroup component */ @BeanTagAttribute(name = "rowDetailsGroup", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public Group getRowDetailsGroup() { return rowDetailsGroup; } /** * Set the row details info group * * @param rowDetailsGroup row details group */ public void setRowDetailsGroup(Group rowDetailsGroup) { this.rowDetailsGroup = rowDetailsGroup; } /** * Creates the details group for the line using the information setup through the setter methods of this * interface. Line details are currently only supported in TableLayoutManagers which use richTable. * * @param collectionGroup the CollectionGroup for this TableLayoutManager * @param view the current view */ public void setupDetails(CollectionGroup collectionGroup, View view) { if (getRowDetailsGroup() == null || this.getRichTable() == null || !this.getRichTable().isRender()) { return; } //data attribute to mark this group to open itself when rendered collectionGroup.addDataAttribute(UifConstants.DataAttributes.DETAILS_DEFAULT_OPEN, Boolean.toString(this.rowDetailsOpen)); toggleAllDetailsAction.addDataAttribute("open", Boolean.toString(this.rowDetailsOpen)); toggleAllDetailsAction.addDataAttribute("tableid", this.getId()); this.getRowDetailsGroup().setHidden(true); FieldGroup detailsFieldGroup = ComponentFactory.getFieldGroup(); TreeMap<String, String> dataAttributes = new TreeMap<String, String>(); dataAttributes.put(UifConstants.DataAttributes.ROLE, "detailsFieldGroup"); detailsFieldGroup.setDataAttributes(dataAttributes); Action rowDetailsAction = this.getExpandDetailsActionPrototype(); rowDetailsAction.addDataAttribute(UifConstants.DataAttributes.ROLE, "detailsLink"); rowDetailsAction.setId(collectionGroup.getId() + UifConstants.IdSuffixes.DETAIL_LINK); List<Component> detailsItems = new ArrayList<Component>(); detailsItems.add(rowDetailsAction); dataAttributes = new TreeMap<String, String>(); dataAttributes.put("role", "details"); dataAttributes.put("open", Boolean.toString(this.rowDetailsOpen)); this.getRowDetailsGroup().setDataAttributes(dataAttributes); if (ajaxDetailsRetrieval) { this.getRowDetailsGroup().setRender(false); this.getRowDetailsGroup().setDisclosedByAction(true); } detailsItems.add(getRowDetailsGroup()); detailsFieldGroup.setItems(detailsItems); detailsFieldGroup.setId(collectionGroup.getId() + UifConstants.IdSuffixes.DETAIL_GROUP); view.assignComponentIds(detailsFieldGroup); List<Component> theItems = new ArrayList<Component>(); theItems.add(detailsFieldGroup); theItems.addAll(collectionGroup.getItems()); collectionGroup.setItems(theItems); } /** * A list of all the columns to be calculated * * <p> * The list must contain valid column indexes. The indexes takes all displayed columns into account. * </p> * * @return the total columns list */ public List<String> getColumnsToCalculate() { return columnsToCalculate; } /** * Validates different requirements of component compiling a series of reports detailing information on errors * found in the component. Used by the RiceDictionaryValidator. * * @param tracer record of component's location * @return a list of ErrorReports detailing errors found within the component and referenced within it */ public void completeValidation(ValidationTrace tracer) { tracer.addBean("TableLayoutManager", getId()); if (getRowDetailsGroup() != null) { boolean validTable = false; if (getRichTable() != null) { if (getRichTable().isRender()) { validTable = true; } } if (!validTable) { String currentValues[] = { "rowDetailsGroup =" + getRowDetailsGroup(), "richTable =" + getRichTable() }; tracer.createError("If rowDetailsGroup is set richTable must be set and its render true", currentValues); } } } /** * Gets showTotal. showTotal shows/calculates the total field when true, otherwise it is not rendered. * <br/> * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting. * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b> * * @return true if showing the total, false otherwise. */ @BeanTagAttribute(name = "showTotal") public boolean isShowTotal() { return showTotal; } /** * Sets showTotal. showTotal shows/calculates the total field when true, otherwise it is not rendered. * <br/> * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting. * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b> * * @param showTotal */ public void setShowTotal(boolean showTotal) { this.showTotal = showTotal; } /** * Gets showTotal. showTotal shows/calculates the total field when true, otherwise it is not rendered. * <br/> * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting. * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b> * * @return true if showing the page total, false otherwise. */ @BeanTagAttribute(name = "showPageTotal") public boolean isShowPageTotal() { return showPageTotal; } /** * Sets showPageTotal. showPageTotal shows/calculates the total field for the page when true (and only * when the table actually has pages), otherwise it is not rendered. * <br/> * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting. * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b> * * @param showPageTotal */ public void setShowPageTotal(boolean showPageTotal) { this.showPageTotal = showPageTotal; } /** * Gets showGroupTotal. showGroupTotal shows/calculates the total field for each grouping when true (and only * when the table actually has grouping turned on), otherwise it is not rendered. * <br/> * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting. * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b> * * @return true if showing the group total, false otherwise. */ @BeanTagAttribute(name = "showGroupTotal") public boolean isShowGroupTotal() { return showGroupTotal; } /** * Sets showGroupTotal. showGroupTotal shows/calculates the total field for each grouping when true (and only * when the table actually has grouping turned on), otherwise it is not rendered. * <br/> * <b>Only used when renderOnlyLeftTotalLabels is TRUE, this overrides the ColumnConfigurationInfo setting. * Otherwise, the ColumnConfigurationInfo setting takes precedence.</b> * * @param showGroupTotal */ public void setShowGroupTotal(boolean showGroupTotal) { this.showGroupTotal = showGroupTotal; } /** * The total label to use when renderOnlyLeftTotalLabels is TRUE for total. * This label will appear in the left most column. * * @return the totalLabel */ @BeanTagAttribute(name = "totalLabel", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public Label getTotalLabel() { return totalLabel; } /** * Sets the total label to use when renderOnlyLeftTotalLabels is TRUE for total. * * @param totalLabel */ public void setTotalLabel(Label totalLabel) { this.totalLabel = totalLabel; } /** * The pageTotal label to use when renderOnlyLeftTotalLabels is TRUE for total. This label will appear in the * left most column. * * @return the totalLabel */ @BeanTagAttribute(name = "pageTotalLabel", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public Label getPageTotalLabel() { return pageTotalLabel; } /** * Sets the pageTotal label to use when renderOnlyLeftTotalLabels is TRUE for total. * * @param pageTotalLabel */ public void setPageTotalLabel(Label pageTotalLabel) { this.pageTotalLabel = pageTotalLabel; } /** * The groupTotal label to use when renderOnlyLeftTotalLabels is TRUE. This label will appear in the left most * column. * * @return the totalLabel */ @BeanTagAttribute(name = "groupTotalLabelPrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN) public Label getGroupTotalLabelPrototype() { return groupTotalLabelPrototype; } /** * Sets the groupTotal label to use when renderOnlyLeftTotalLabels is TRUE. * * @param groupTotalLabelPrototype */ public void setGroupTotalLabelPrototype(Label groupTotalLabelPrototype) { this.groupTotalLabelPrototype = groupTotalLabelPrototype; } /** * Gets the column calculations. This is a list of ColumnCalcuationInfo that when set provides calculations * to be performed on the columns they specify. These calculations appear in the table's footer. This feature is * only available when using richTable functionality. * * @return the columnCalculations to use */ @BeanTagAttribute(name = "columnCalculations", type = BeanTagAttribute.AttributeType.LISTBEAN) public List<ColumnCalculationInfo> getColumnCalculations() { return columnCalculations; } /** * Sets the columnCalculations. * * @param columnCalculations */ public void setColumnCalculations(List<ColumnCalculationInfo> columnCalculations) { this.columnCalculations = columnCalculations; } /** * When true, labels for the totals fields will only appear in the left most column. Showing of the totals * is controlled by the settings on the TableLayoutManager itself when this property is true. * * @return true when rendering totals footer labels in the left-most column, false otherwise */ @BeanTagAttribute(name = "renderOnlyLeftTotalLabels") public boolean isRenderOnlyLeftTotalLabels() { return renderOnlyLeftTotalLabels; } /** * Set the renderOnlyLeftTotalLabels flag for rendring total labels in the left-most column * * @param renderOnlyLeftTotalLabels */ public void setRenderOnlyLeftTotalLabels(boolean renderOnlyLeftTotalLabels) { this.renderOnlyLeftTotalLabels = renderOnlyLeftTotalLabels; } /** * Gets the footer calculation components to be used by the layout. These are set by the framework and cannot * be set directly. * * @return the list of components for the footer */ public List<Component> getFooterCalculationComponents() { return footerCalculationComponents; } /** * Gets the list of property names to use for grouping. * * <p> * When this property is set, grouping for this * collection will be enabled and the lines of the collection will be grouped by the propertyName(s) supplied. * Supplying multiple property names will cause the grouping to be on multiple fields and ordered * alphabetically on "propetyValue1, propertyValue2" (this is also how the group title will display for each * group). * The property names supplied must be relative to the line, so #lp * SHOULD NOT be used (it is assumed automatically). * </p> * * @return propertyNames to group on */ @BeanTagAttribute(name = "groupingPropertyNames", type = BeanTagAttribute.AttributeType.LISTVALUE) public List<String> getGroupingPropertyNames() { return groupingPropertyNames; } /** * Sets the list of property names to use for grouping. * * @param groupingPropertyNames */ public void setGroupingPropertyNames(List<String> groupingPropertyNames) { this.groupingPropertyNames = groupingPropertyNames; } /** * Get the groupingTitle. The groupingTitle MUST contain a SpringEL expression to uniquely identify a * group's line (ie it cannot be a static string because each group must be identified by some value). * <b>This overrides groupingPropertyNames(if set) because it provides full control of grouping value used by * the collection. SpringEL defined here must use #lp if referencing values of the line.</b> * * @return groupingTitle to be used */ @BeanTagAttribute(name = "groupingTitle") public String getGroupingTitle() { return groupingTitle; } /** * Set the groupingTitle. This will throw an exception if the title does not contain a SpringEL expression. * * @param groupingTitle */ public void setGroupingTitle(String groupingTitle) { if (groupingTitle != null && !groupingTitle.contains("@{")) { throw new RuntimeException("groupingTitle MUST contain a springEL expression to uniquely" + " identify a collection group (often related to some value of the line). " + "Value provided: " + this.getGroupingTitle()); } this.groupingTitle = groupingTitle; } /** * Get the groupingPrefix. The groupingPrefix is used to prefix the generated title (not used when * groupingTitle is set directly) when using groupingPropertyNames. * * @return String */ @BeanTagAttribute(name = "groupingPrefix") public String getGroupingPrefix() { return groupingPrefix; } /** * Set the groupingPrefix. This is not used when groupingTitle is set directly. * * @param groupingPrefix */ public void setGroupingPrefix(String groupingPrefix) { this.groupingPrefix = groupingPrefix; } /** * If true, all details will be opened by default when the table loads. Can only be used on tables that have * row details setup. * * @return true if row details */ public boolean isRowDetailsOpen() { return rowDetailsOpen; } /** * Set if row details should be open on table load * * @param rowDetailsOpen */ public void setRowDetailsOpen(boolean rowDetailsOpen) { this.rowDetailsOpen = rowDetailsOpen; } /** * If true, the toggleAllDetailsAction will be shown. This button allows all details to * be open/closed simultaneously. * * @return true if the action button to toggle all row details opened/closed */ public boolean isShowToggleAllDetails() { return showToggleAllDetails; } /** * Set if the toggleAllDetailsAction should be shown * * @param showToggleAllDetails */ public void setShowToggleAllDetails(boolean showToggleAllDetails) { this.showToggleAllDetails = showToggleAllDetails; } /** * The toggleAllDetailsAction action component used to toggle all row details open/closed. This property is set * by the default configuration and should not be reset in most cases. * * @return Action component to use for the toggle action button */ public Action getToggleAllDetailsAction() { return toggleAllDetailsAction; } /** * Set the toggleAllDetailsAction action component used to toggle all row details open/closed. This property is * set * by the default configuration and should not be reset in most cases. * * @param toggleAllDetailsAction */ public void setToggleAllDetailsAction(Action toggleAllDetailsAction) { this.toggleAllDetailsAction = toggleAllDetailsAction; } /** * If true, when a row details open action is performed, it will get the details content from the server the first * time it is opened. The methodToCall will be a component "refresh" call by default (this can be set on * expandDetailsActionPrototype) and the additional action parameters sent to the server will be those set * on the expandDetailsActionPrototype (lineIndex will be sent by default). * * @return true if ajax row details retrieval will be used */ public boolean isAjaxDetailsRetrieval() { return ajaxDetailsRetrieval; } /** * Set if row details content should be retrieved fromt he server * * @param ajaxDetailsRetrieval */ public void setAjaxDetailsRetrieval(boolean ajaxDetailsRetrieval) { this.ajaxDetailsRetrieval = ajaxDetailsRetrieval; } /** * The Action prototype used for the row details expand link. Should be set to "Uif-ExpandDetailsAction" or * "Uif-ExpandDetailsImageAction". Properties can be configured to allow for different methodToCall and * actionParameters to be set for ajax row details retrieval. * * @return the Action details link prototype */ public Action getExpandDetailsActionPrototype() { return expandDetailsActionPrototype; } /** * Gets the grouping column index * * @return the grouping column index */ public int getGroupingColumnIndex() { return groupingColumnIndex; } /** * Set the expand details Action prototype link * * @param expandDetailsActionPrototype */ public void setExpandDetailsActionPrototype(Action expandDetailsActionPrototype) { this.expandDetailsActionPrototype = expandDetailsActionPrototype; } /** * Set the header labels * * @param headerLabels */ public void setHeaderLabels(List<Label> headerLabels) { this.headerLabels = headerLabels; } /** * Set the row fields * * @param allRowFields */ public void setAllRowFields(List<Field> allRowFields) { this.allRowFields = allRowFields; } /** * Set the first row fields * * @param firstRowFields */ public void setFirstRowFields(List<Field> firstRowFields) { this.firstRowFields = firstRowFields; } /** * Set flag of whether a header is added * * @param headerAdded */ public void setHeaderAdded(boolean headerAdded) { this.headerAdded = headerAdded; } /** * Sets action column index * * @param actionColumnIndex */ public void setActionColumnIndex(int actionColumnIndex) { this.actionColumnIndex = actionColumnIndex; } /** * Set grouping column index * * @param groupingColumnIndex */ public void setGroupingColumnIndex(int groupingColumnIndex) { this.groupingColumnIndex = groupingColumnIndex; } /** * Set flag generate group total rows * * @param generateGroupTotalRows */ public void setGenerateGroupTotalRows(boolean generateGroupTotalRows) { this.generateGroupTotalRows = generateGroupTotalRows; } /** * Set columns to calculate * * @param columnsToCalculate */ public void setColumnsToCalculate(List<String> columnsToCalculate) { this.columnsToCalculate = columnsToCalculate; } /** * Set footer calculation components * * @param footerCalculationComponents */ public void setFooterCalculationComponents(List<Component> footerCalculationComponents) { this.footerCalculationComponents = footerCalculationComponents; } /** * The row css classes for the rows of this layout * * <p>To set a css class on all rows, use "all" as a key. To set a * class for even rows, use "even" as a key, for odd rows, use "odd". * Use a one-based index to target a specific row by index. SpringEL can be * used as a key and the expression will be evaluated; if evaluated to true, the * class(es) specified will be applied.</p> * * @return a map which represents the css classes of the rows of this layout */ @BeanTagAttribute(name = "conditionalRowCssClasses", type = BeanTagAttribute.AttributeType.MAPVALUE) public Map<String, String> getConditionalRowCssClasses() { return conditionalRowCssClasses; } /** * Set the conditionalRowCssClasses * * @param conditionalRowCssClasses */ public void setConditionalRowCssClasses(Map<String, String> conditionalRowCssClasses) { this.conditionalRowCssClasses = conditionalRowCssClasses; } /** * @see org.kuali.rice.krad.uif.component.ComponentBase#copy() */ @Override protected <T> void copyProperties(T layoutManager) { super.copyProperties(layoutManager); TableLayoutManager tableLayoutManagerCopy = (TableLayoutManager) layoutManager; tableLayoutManagerCopy.setUseShortLabels(this.isUseShortLabels()); tableLayoutManagerCopy.setRepeatHeader(this.isRepeatHeader()); if (this.headerLabelPrototype != null) { tableLayoutManagerCopy.setHeaderLabelPrototype((Label) this.getHeaderLabelPrototype().copy()); } tableLayoutManagerCopy.setRenderSequenceField(this.isRenderSequenceField()); tableLayoutManagerCopy.setGenerateAutoSequence(this.isGenerateAutoSequence()); if (this.sequenceFieldPrototype != null) { tableLayoutManagerCopy.setSequenceFieldPrototype((Field) this.getSequenceFieldPrototype().copy()); } if (this.actionFieldPrototype != null) { tableLayoutManagerCopy.setActionFieldPrototype((FieldGroup) this.getActionFieldPrototype().copy()); } if (this.subCollectionFieldGroupPrototype != null) { tableLayoutManagerCopy.setSubCollectionFieldGroupPrototype( (FieldGroup) this.getSubCollectionFieldGroupPrototype().copy()); } if (this.selectFieldPrototype != null) { tableLayoutManagerCopy.setSelectFieldPrototype((Field) this.getSelectFieldPrototype().copy()); } tableLayoutManagerCopy.setSeparateAddLine(this.isSeparateAddLine()); if (this.addLineGroup != null) { tableLayoutManagerCopy.setAddLineGroup((Group) this.getAddLineGroup().copy()); } tableLayoutManagerCopy.setNumberOfDataColumns(this.numberOfDataColumns); if (this.headerLabels != null) { List<Label> headerLabelsCopy = Lists.newArrayListWithExpectedSize(this.headerLabels.size()); for (Label headerLabel : headerLabels) { if (headerLabel != null) { headerLabelsCopy.add((Label) headerLabel.copy()); } } tableLayoutManagerCopy.setHeaderLabels(headerLabelsCopy); } if (this.allRowFields != null) { List<Field> allRowFieldsCopy = Lists.newArrayListWithExpectedSize(allRowFields.size()); for (Field allRowField : allRowFields) { if (allRowField != null) { allRowFieldsCopy.add((Field) allRowField.copy()); } } tableLayoutManagerCopy.setAllRowFields(allRowFieldsCopy); } if (this.firstRowFields != null) { List<Field> firstRowFieldsCopy = Lists.newArrayListWithExpectedSize(firstRowFields.size()); for (Field firstRowField : firstRowFields) { if (firstRowField != null) { firstRowFieldsCopy.add((Field) firstRowField.copy()); } } tableLayoutManagerCopy.setFirstRowFields(firstRowFieldsCopy); } if (this.pagerWidget != null) { tableLayoutManagerCopy.setPagerWidget((Pager) this.pagerWidget.copy()); } if (this.richTable != null) { tableLayoutManagerCopy.setRichTable((RichTable) this.getRichTable().copy()); } tableLayoutManagerCopy.setHeaderAdded(this.headerAdded); tableLayoutManagerCopy.setActionColumnIndex(this.getActionColumnIndex()); if (this.rowDetailsGroup != null) { tableLayoutManagerCopy.setRowDetailsGroup((Group) this.getRowDetailsGroup().copy()); } tableLayoutManagerCopy.setRowDetailsOpen(this.isRowDetailsOpen()); tableLayoutManagerCopy.setShowToggleAllDetails(this.isShowToggleAllDetails()); if (this.toggleAllDetailsAction != null) { tableLayoutManagerCopy.setToggleAllDetailsAction((Action) this.getToggleAllDetailsAction().copy()); } tableLayoutManagerCopy.setAjaxDetailsRetrieval(this.isAjaxDetailsRetrieval()); if (this.expandDetailsActionPrototype != null) { tableLayoutManagerCopy .setExpandDetailsActionPrototype((Action) this.getExpandDetailsActionPrototype().copy()); } tableLayoutManagerCopy.setGroupingTitle(this.getGroupingTitle()); tableLayoutManagerCopy.setGroupingPrefix(this.getGroupingPrefix()); tableLayoutManagerCopy.setGroupingColumnIndex(this.getGroupingColumnIndex()); if (this.groupingPropertyNames != null) { tableLayoutManagerCopy.setGroupingPropertyNames(new ArrayList<String>(groupingPropertyNames)); } tableLayoutManagerCopy.setRenderOnlyLeftTotalLabels(this.isRenderOnlyLeftTotalLabels()); tableLayoutManagerCopy.setShowTotal(this.isShowTotal()); tableLayoutManagerCopy.setShowPageTotal(this.isShowPageTotal()); tableLayoutManagerCopy.setShowGroupTotal(this.isShowGroupTotal()); tableLayoutManagerCopy.setGenerateGroupTotalRows(this.generateGroupTotalRows); if (this.totalLabel != null) { tableLayoutManagerCopy.setTotalLabel((Label) this.getTotalLabel().copy()); } if (this.pageTotalLabel != null) { tableLayoutManagerCopy.setPageTotalLabel((Label) this.getPageTotalLabel().copy()); } if (this.groupTotalLabelPrototype != null) { tableLayoutManagerCopy.setGroupTotalLabelPrototype((Label) this.getGroupTotalLabelPrototype().copy()); } if (this.columnsToCalculate != null) { tableLayoutManagerCopy.setColumnsToCalculate(new ArrayList<String>(columnsToCalculate)); } if (this.columnCalculations != null) { List<ColumnCalculationInfo> columnCalculationsCopy = Lists .newArrayListWithExpectedSize(columnCalculations.size()); for (ColumnCalculationInfo columnCalculation : columnCalculations) { columnCalculationsCopy.add((ColumnCalculationInfo) columnCalculation.copy()); } tableLayoutManagerCopy.setColumnCalculations(columnCalculationsCopy); } if (this.footerCalculationComponents != null) { List<Component> footerCalculationComponentsCopy = Lists .newArrayListWithExpectedSize(footerCalculationComponents.size()); for (Component footerCalculationComponent : footerCalculationComponents) { if (footerCalculationComponent != null) { footerCalculationComponentsCopy.add((Component) footerCalculationComponent.copy()); } } tableLayoutManagerCopy.setFooterCalculationComponents(footerCalculationComponentsCopy); } if (this.conditionalRowCssClasses != null) { tableLayoutManagerCopy .setConditionalRowCssClasses(new HashMap<String, String>(this.conditionalRowCssClasses)); } } }