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    }