View Javadoc
1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.uif.layout.collections;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.uif.CssConstants;
20  import org.kuali.rice.krad.uif.UifConstants;
21  import org.kuali.rice.krad.uif.component.DataBinding;
22  import org.kuali.rice.krad.uif.container.CollectionGroup;
23  import org.kuali.rice.krad.uif.container.collections.LineBuilderContext;
24  import org.kuali.rice.krad.uif.element.Message;
25  import org.kuali.rice.krad.uif.field.Field;
26  import org.kuali.rice.krad.uif.field.FieldGroup;
27  import org.kuali.rice.krad.uif.field.MessageField;
28  import org.kuali.rice.krad.uif.field.SpaceField;
29  import org.kuali.rice.krad.uif.layout.CollectionLayoutUtils;
30  import org.kuali.rice.krad.uif.layout.TableLayoutManager;
31  import org.kuali.rice.krad.uif.util.ComponentFactory;
32  import org.kuali.rice.krad.uif.util.ComponentUtils;
33  import org.kuali.rice.krad.uif.util.ContextUtils;
34  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
35  
36  import java.io.Serializable;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.List;
40  import java.util.Map;
41  
42  /**
43   * Builds out a collection line into a table row.
44   *
45   * TODO: This have duplicate logic from table layout manager, the goal is to move all logic from the layout
46   * manager to this class
47   *
48   * @author Kuali Rice Team (rice.collab@kuali.org)
49   * @see org.kuali.rice.krad.uif.layout.TableLayoutManager
50   * @see org.kuali.rice.krad.uif.layout.collections.TableRow
51   */
52  public class TableRowBuilder implements Serializable {
53      private static final long serialVersionUID = 5098939594340088940L;
54  
55      private CollectionGroup collectionGroup;
56      private TableLayoutManager tableLayoutManager;
57  
58      private LineBuilderContext lineBuilderContext;
59  
60      /**
61       * Empty Constructor.
62       */
63      public TableRowBuilder() {
64  
65      }
66  
67      /**
68       * Constructor taking the collection group instance and context for the line.
69       *
70       * @param collectionGroup collection group the table row is being built for
71       * @param lineBuilderContext components and other configuration for the line to build
72       */
73      public TableRowBuilder(CollectionGroup collectionGroup, LineBuilderContext lineBuilderContext) {
74          this.collectionGroup = collectionGroup;
75  
76          if (collectionGroup != null) {
77              this.tableLayoutManager = (TableLayoutManager) collectionGroup.getLayoutManager();
78          }
79  
80          this.lineBuilderContext = lineBuilderContext;
81      }
82  
83      /**
84       * Takes the context given for the line and builds out a table row instance.
85       *
86       * <p>The row is built out based on a determined order of special columns (sequence, line selection) and
87       * then each field from the configured items list. Since the placement of the action column is configurable,
88       * it is handled by the {@link org.kuali.rice.krad.uif.layout.collections.TableRowBuilder.ColumnCollector}</p>
89       *
90       * @return table row instance for the line
91       */
92      public TableRow buildRow() {
93          ColumnCollector columnCollector = new ColumnCollector(tableLayoutManager.getActionColumnIndex());
94  
95          if (tableLayoutManager.isRenderSequenceField()) {
96              addSequenceColumn(columnCollector);
97          }
98  
99          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 }