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