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}