001/** 002 * Copyright 2005-2018 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 */ 016package org.kuali.rice.krad.uif.layout.collections; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krad.uif.CssConstants; 020import org.kuali.rice.krad.uif.UifConstants; 021import org.kuali.rice.krad.uif.component.DataBinding; 022import org.kuali.rice.krad.uif.container.CollectionGroup; 023import org.kuali.rice.krad.uif.container.collections.LineBuilderContext; 024import org.kuali.rice.krad.uif.element.Message; 025import org.kuali.rice.krad.uif.field.Field; 026import org.kuali.rice.krad.uif.field.FieldGroup; 027import org.kuali.rice.krad.uif.field.MessageField; 028import org.kuali.rice.krad.uif.field.SpaceField; 029import org.kuali.rice.krad.uif.layout.CollectionLayoutUtils; 030import org.kuali.rice.krad.uif.layout.TableLayoutManager; 031import org.kuali.rice.krad.uif.util.ComponentFactory; 032import org.kuali.rice.krad.uif.util.ComponentUtils; 033import org.kuali.rice.krad.uif.util.ContextUtils; 034import org.kuali.rice.krad.uif.util.ObjectPropertyUtils; 035 036import java.io.Serializable; 037import java.util.ArrayList; 038import java.util.Arrays; 039import java.util.List; 040import java.util.Map; 041 042/** 043 * Builds out a collection line into a table row. 044 * 045 * TODO: This have duplicate logic from table layout manager, the goal is to move all logic from the layout 046 * manager to this class 047 * 048 * @author Kuali Rice Team (rice.collab@kuali.org) 049 * @see org.kuali.rice.krad.uif.layout.TableLayoutManager 050 * @see org.kuali.rice.krad.uif.layout.collections.TableRow 051 */ 052public class TableRowBuilder implements Serializable { 053 private static final long serialVersionUID = 5098939594340088940L; 054 055 private CollectionGroup collectionGroup; 056 private TableLayoutManager tableLayoutManager; 057 058 private LineBuilderContext lineBuilderContext; 059 060 /** 061 * Empty Constructor. 062 */ 063 public TableRowBuilder() { 064 065 } 066 067 /** 068 * Constructor taking the collection group instance and context for the line. 069 * 070 * @param collectionGroup collection group the table row is being built for 071 * @param lineBuilderContext components and other configuration for the line to build 072 */ 073 public TableRowBuilder(CollectionGroup collectionGroup, LineBuilderContext lineBuilderContext) { 074 this.collectionGroup = collectionGroup; 075 076 if (collectionGroup != null) { 077 this.tableLayoutManager = (TableLayoutManager) collectionGroup.getLayoutManager(); 078 } 079 080 this.lineBuilderContext = lineBuilderContext; 081 } 082 083 /** 084 * Takes the context given for the line and builds out a table row instance. 085 * 086 * <p>The row is built out based on a determined order of special columns (sequence, line selection) and 087 * then each field from the configured items list. Since the placement of the action column is configurable, 088 * it is handled by the {@link org.kuali.rice.krad.uif.layout.collections.TableRowBuilder.ColumnCollector}</p> 089 * 090 * @return table row instance for the line 091 */ 092 public TableRow buildRow() { 093 ColumnCollector columnCollector = new ColumnCollector(tableLayoutManager.getActionColumnIndex()); 094 095 if (tableLayoutManager.isRenderSequenceField()) { 096 addSequenceColumn(columnCollector); 097 } 098 099 if (collectionGroup.isIncludeLineSelectionField()) { 100 addLineSelectColumn(columnCollector); 101 } 102 103 boolean hasGrouping = (tableLayoutManager.getGroupingPropertyNames() != null) || StringUtils.isNotBlank( 104 tableLayoutManager.getGroupingTitle()); 105 if (hasGrouping) { 106 addGroupingColumn(columnCollector); 107 } 108 109 // now add field configured on the collection group 110 for (Field lineField : lineBuilderContext.getLineFields()) { 111 Map<String, String> fieldDataAttributes = lineField.getDataAttributes(); 112 113 // skip grouping column for now (until logic is pulled from layout manager to use this) 114 boolean hasRoleAttribute = (fieldDataAttributes != null) && fieldDataAttributes.containsKey( 115 UifConstants.DataAttributes.ROLE); 116 if (hasRoleAttribute && UifConstants.RoleTypes.ROW_GROUPING.equals(fieldDataAttributes.get( 117 UifConstants.DataAttributes.ROLE))) { 118 continue; 119 } 120 121 columnCollector.addColumn(lineField); 122 } 123 124 columnCollector.finishRow(); 125 126 return new TableRow(columnCollector.getColumns()); 127 } 128 129 /** 130 * Adds the sequence column to the given column collector. 131 * 132 * <p>Sequence column is created with a new message component for the add line, and by copying 133 * {@link org.kuali.rice.krad.uif.layout.TableLayoutManager#getSequenceFieldPrototype()} for existing rows.</p> 134 * 135 * @param columnCollector object collecting the columns for the row 136 */ 137 protected void addSequenceColumn(ColumnCollector columnCollector) { 138 Field sequenceField; 139 140 if (lineBuilderContext.isAddLine()) { 141 sequenceField = ComponentFactory.getMessageField(); 142 143 Message sequenceMessage = ComponentUtils.copy(collectionGroup.getAddLineLabel(), 144 lineBuilderContext.getIdSuffix()); 145 ((MessageField) sequenceField).setMessage(sequenceMessage); 146 } else { 147 sequenceField = ComponentUtils.copy(tableLayoutManager.getSequenceFieldPrototype(), 148 lineBuilderContext.getIdSuffix()); 149 150 // ignore in validation processing 151 sequenceField.addDataAttribute(UifConstants.DataAttributes.VIGNORE, "yes"); 152 153 if (tableLayoutManager.isGenerateAutoSequence() && (sequenceField instanceof MessageField)) { 154 ((MessageField) sequenceField).setMessageText(Integer.toString(lineBuilderContext.getLineIndex() + 1)); 155 } 156 157 if (sequenceField instanceof DataBinding) { 158 ((DataBinding) sequenceField).getBindingInfo().setBindByNamePrefix(lineBuilderContext.getBindingPath()); 159 } 160 } 161 162 // TODO: needed to convert full layout logic to use the builder 163 // sequenceField.setRowSpan(rowSpan); 164 // setCellAttributes(sequenceField); 165 166 ContextUtils.updateContextForLine(sequenceField, collectionGroup, lineBuilderContext.getCurrentLine(), 167 lineBuilderContext.getLineIndex(), lineBuilderContext.getIdSuffix()); 168 169 columnCollector.addColumn(sequenceField); 170 } 171 172 /** 173 * Adds the line select column to the given column collector. 174 * 175 * <p>The line select column is used to select rows for an action (such as lookup return).</p> 176 * 177 * @param columnCollector object collecting the columns for the row 178 */ 179 protected void addLineSelectColumn(ColumnCollector columnCollector) { 180 Field selectField = ComponentUtils.copy(tableLayoutManager.getSelectFieldPrototype(), 181 lineBuilderContext.getIdSuffix()); 182 183 CollectionLayoutUtils.prepareSelectFieldForLine(selectField, collectionGroup, 184 lineBuilderContext.getBindingPath(), lineBuilderContext.getCurrentLine()); 185 186 ContextUtils.updateContextForLine(selectField, collectionGroup, lineBuilderContext.getCurrentLine(), 187 lineBuilderContext.getLineIndex(), lineBuilderContext.getIdSuffix()); 188 189 //setCellAttributes(selectField); 190 191 columnCollector.addColumn(selectField); 192 } 193 194 /** 195 * Adds the grouping column to the given column collector. 196 * 197 * <p>The grouping column is used when table grouping is on to render a header for the group. The data 198 * tables plugin will pull the value from this column and render the header row.</p> 199 * 200 * @param columnCollector object collecting the columns for the row 201 */ 202 protected void addGroupingColumn(ColumnCollector columnCollector) { 203 // no grouping on add line, so just add blank field 204 if (lineBuilderContext.isAddLine()) { 205 SpaceField spaceField = ComponentFactory.getSpaceField(); 206 columnCollector.addColumn(spaceField); 207 208 return; 209 } 210 211 MessageField groupingMessageField = ComponentFactory.getColGroupingField(); 212 213 StringBuilder groupingTitle = new StringBuilder(); 214 if (StringUtils.isNotBlank(tableLayoutManager.getGroupingTitle())) { 215 groupingTitle.append(tableLayoutManager.getGroupingTitle()); 216 } else if (tableLayoutManager.getGroupingPropertyNames() != null) { 217 for (String propertyName : tableLayoutManager.getGroupingPropertyNames()) { 218 Object propertyValue = ObjectPropertyUtils.getPropertyValue(lineBuilderContext.getCurrentLine(), 219 propertyName); 220 221 if (propertyValue == null) { 222 propertyValue = "Null"; 223 } 224 225 if (groupingTitle.length() != 0) { 226 groupingTitle.append(", "); 227 } 228 229 groupingTitle.append(propertyValue); 230 } 231 232 } 233 234 groupingMessageField.setMessageText(groupingTitle.toString()); 235 groupingMessageField.addDataAttribute(UifConstants.DataAttributes.ROLE, UifConstants.RoleTypes.ROW_GROUPING); 236 237 columnCollector.addColumn(groupingMessageField); 238 } 239 240 /** 241 * Helper class for collecting columsn that will make up a table row. 242 */ 243 public class ColumnCollector implements Serializable { 244 private static final long serialVersionUID = 7129699106011942622L; 245 246 private int actionColumnIndex; 247 248 private int currentIndex = 0; 249 private List<Field> columns; 250 251 /** 252 * Constructor taking the column index for the action column. 253 * 254 * @param actionColumnIndex index for action column 255 */ 256 public ColumnCollector(int actionColumnIndex) { 257 this.actionColumnIndex = actionColumnIndex; 258 this.columns = new ArrayList<Field>(); 259 } 260 261 /** 262 * Adds the given field instance as a column. 263 * 264 * <p>A check is made to see if actions should be rendered at the current position first, then the 265 * field is added</p> 266 * 267 * @param column field instance to add 268 */ 269 public void addColumn(Field column) { 270 if (isRenderActions() && (actionColumnIndex == (currentIndex + 1))) { 271 Field actionColumn = buildActionColumn(); 272 273 columns.add(actionColumn); 274 currentIndex++; 275 } 276 277 columns.add(column); 278 currentIndex++; 279 } 280 281 /** 282 * Should be invoked after there are no more columns to add, so that the action column can be added 283 * when it is configured to be the last column. 284 */ 285 public void finishRow() { 286 if (isRenderActions() && (actionColumnIndex == (currentIndex + 1)) || (actionColumnIndex == -1)) { 287 Field actionColumn = buildActionColumn(); 288 289 columns.add(actionColumn); 290 } 291 } 292 293 /** 294 * Creates a field group instance that contains the actions for the row. 295 * 296 * <p>Field group is created by copying 297 * {@link org.kuali.rice.krad.uif.layout.TableLayoutManager#getActionFieldPrototype()}, then the line 298 * actions from the line context are moved to the field group</p> 299 * 300 * @return field group instance containing the actions 301 */ 302 protected FieldGroup buildActionColumn() { 303 FieldGroup lineActionsField = ComponentUtils.copy(tableLayoutManager.getActionFieldPrototype(), 304 lineBuilderContext.getIdSuffix()); 305 306 ContextUtils.updateContextForLine(lineActionsField, collectionGroup, lineBuilderContext.getCurrentLine(), 307 lineBuilderContext.getLineIndex(), lineBuilderContext.getIdSuffix()); 308 309 // lineActionsField.setRowSpan(rowSpan); 310 lineActionsField.setItems(lineBuilderContext.getLineActions()); 311 312 if (lineActionsField.getWrapperCssClasses() != null && !lineActionsField.getWrapperCssClasses().contains( 313 CssConstants.Classes.ACTION_COLUMN_STYLE_CLASS)) { 314 lineActionsField.getWrapperCssClasses().add(CssConstants.Classes.ACTION_COLUMN_STYLE_CLASS); 315 } else { 316 lineActionsField.setWrapperCssClasses(Arrays.asList(CssConstants.Classes.ACTION_COLUMN_STYLE_CLASS)); 317 } 318 319 // setCellAttributes(lineActionsField); 320 321 return lineActionsField; 322 } 323 324 /** 325 * Indicates whether actions should be rendered based on the collection group configuration. 326 * 327 * @return boolean true if actions should be rendered, false if not 328 */ 329 public boolean isRenderActions() { 330 return collectionGroup.isRenderLineActions() && !Boolean.TRUE.equals(collectionGroup.getReadOnly()); 331 } 332 333 /** 334 * Returns the field instances that make up the row columns. 335 * 336 * @return list of fields 337 */ 338 public List<Field> getColumns() { 339 return columns; 340 } 341 } 342 343}