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 }