001/**
002 * Copyright 2005-2015 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;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.krad.datadictionary.parse.BeanTag;
020import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
021import org.kuali.rice.krad.datadictionary.parse.BeanTags;
022import org.kuali.rice.krad.uif.component.Component;
023import org.kuali.rice.krad.uif.container.Container;
024import org.kuali.rice.krad.uif.view.View;
025import org.kuali.rice.krad.util.KRADUtils;
026
027import java.util.ArrayList;
028import java.util.HashMap;
029import java.util.List;
030import java.util.Map;
031
032/**
033 * Css Grid Layout manager is a layout manager which creates div "rows" and "cells" to replicate a table look by
034 * using div elements for its items.  Items are added into rows based on their colSpan setting, while each row has a
035 * max
036 * size of 9 columns.  By default, if colSpan is not set on an item, that item will take a full row.
037 */
038@BeanTags({@BeanTag(name = "cssGridLayout-bean", parent = "Uif-CssGridLayoutBase"),
039        @BeanTag(name = "fixedCssGridLayout-bean", parent = "Uif-FixedCssGridLayout"),
040        @BeanTag(name = "fluidCssGridLayout-bean", parent = "Uif-FluidCssGridLayout")})
041public class CssGridLayoutManager extends LayoutManagerBase {
042    private static final long serialVersionUID = 1830635073147703757L;
043
044    private static final int NUMBER_OF_COLUMNS = 9;
045    private static final String BOOTSTRAP_SPAN_PREFIX = "span";
046
047    private Map<String, String> conditionalRowCssClasses;
048    private String rowLayoutCssClass;
049    private int defaultItemColSpan;
050
051    // non-settable
052    protected List<List<Component>> rows;
053    protected List<String> rowCssClassAttributes;
054    protected List<String> cellCssClassAttributes;
055
056    public CssGridLayoutManager() {
057        rows = new ArrayList<List<Component>>();
058        conditionalRowCssClasses = new HashMap<String, String>();
059        cellCssClassAttributes = new ArrayList<String>();
060        rowCssClassAttributes = new ArrayList<String>();
061    }
062
063    /**
064     * CssGridLayoutManager's performFinalize method calculates and separates the items into rows
065     * based on their colSpan settings and the defaultItemColSpan setting
066     * 
067     * @see Component#performFinalize(org.kuali.rice.krad.uif.view.View, Object,
068     *      org.kuali.rice.krad.uif.component.Component)
069     */
070    @Override
071    public void performFinalize(View view, Object model, Container container) {
072        super.performFinalize(view, model, container);
073
074        int rowSpaceLeft = NUMBER_OF_COLUMNS;
075        int rowIndex = 0;
076        boolean isOdd = true;
077        List<Component> currentRow = new ArrayList<Component>();
078        for (Component item : container.getItems()) {
079            if (item == null) {
080                continue;
081            }
082            isOdd = rowIndex % 2 == 0;
083
084            // set colSpan to default setting (9 is the default)
085            int colSpan = this.defaultItemColSpan;
086
087            // if the item's set colSpan is greater than 1 set it to that number; 1 is the default colSpan for Component
088            if (item.getColSpan() > 1 && item.getColSpan() <= NUMBER_OF_COLUMNS) {
089                colSpan = item.getColSpan();
090            }
091
092            // determine "cell" div css
093            List<String> cellCssClasses = item.getCellCssClasses();
094            if (cellCssClasses == null) {
095                item.setCellCssClasses(cellCssClasses = new ArrayList<String>());
096            }
097            cellCssClasses.add(0, BOOTSTRAP_SPAN_PREFIX + colSpan);
098            cellCssClassAttributes.add(getCellStyleClassesAsString(cellCssClasses));
099
100            // calculate space left in row
101            rowSpaceLeft = rowSpaceLeft - colSpan;
102
103            if (rowSpaceLeft > 0) {
104                // space is left, just add item to row
105                currentRow.add(item);
106            } else if (rowSpaceLeft < 0) {
107                // went over, add item to next new row
108                rows.add(new ArrayList<Component>(currentRow));
109                currentRow = new ArrayList<Component>();
110                currentRow.add(item);
111
112                // determine "row" div css
113                String rowCss = rowLayoutCssClass + " " + KRADUtils.generateRowCssClassString(conditionalRowCssClasses,
114                        rowIndex, isOdd, null, null);
115                rowCssClassAttributes.add(rowCss);
116
117                rowIndex++;
118                rowSpaceLeft = NUMBER_OF_COLUMNS - colSpan;
119            } else if (rowSpaceLeft == 0) {
120                // last item in row, create new row
121                currentRow.add(item);
122                rows.add(new ArrayList<Component>(currentRow));
123                currentRow = new ArrayList<Component>();
124
125                // determine "row" div css
126                String rowCss = rowLayoutCssClass + " " + KRADUtils.generateRowCssClassString(conditionalRowCssClasses,
127                        rowIndex, isOdd, null, null);
128                rowCssClassAttributes.add(rowCss);
129
130                rowIndex++;
131                rowSpaceLeft = NUMBER_OF_COLUMNS;
132            }
133        }
134
135        isOdd = rowIndex % 2 == 0;
136        // add the last row if it wasn't full (but has items)
137        if (!currentRow.isEmpty()) {
138            // determine "row" div css
139            String rowCss = rowLayoutCssClass + " " + KRADUtils.generateRowCssClassString(conditionalRowCssClasses,
140                    rowIndex, isOdd, null, null);
141            rowCssClassAttributes.add(rowCss);
142
143            rows.add(currentRow);
144        }
145    }
146
147    /**
148     * Builds the HTML class attribute string by combining the cellStyleClasses list with a space
149     * delimiter
150     * 
151     * @return class attribute string
152     */
153    private String getCellStyleClassesAsString(List<String> cellCssClasses) {
154        if (cellCssClasses != null) {
155            return StringUtils.join(cellCssClasses, " ");
156        }
157
158        return "";
159    }
160
161    /**
162     * Get the rows (which are a list of components each)
163     * 
164     * @return the List of Lists of Components which represents rows for this layout
165     */
166    public List<List<Component>> getRows() {
167        return rows;
168    }
169
170    /**
171     * List of css class HTML attribute values ordered by index of row
172     * 
173     * @return the list of css class HTML attributes for rows
174     */
175    public List<String> getRowCssClassAttributes() {
176        return rowCssClassAttributes;
177    }
178
179    /**
180     * List of css class HTML attribute values ordered by the order in which the cell appears
181     * 
182     * @return the list of css class HTML attributes for cells
183     */
184    public List<String> getCellCssClassAttributes() {
185        return cellCssClassAttributes;
186    }
187
188    /**
189     * The default cell colSpan to use for this layout (max setting, and the bean default, is 9)
190     * 
191     * @return int representing the default colSpan for cells in this layout
192     */
193    @BeanTagAttribute(name = "defaultItemColSpan")
194    public int getDefaultItemColSpan() {
195        return defaultItemColSpan;
196    }
197
198    /**
199     * Set the default colSpan for this layout's items
200     * 
201     * @param defaultItemColSpan
202     */
203    public void setDefaultItemColSpan(int defaultItemColSpan) {
204        this.defaultItemColSpan = defaultItemColSpan;
205    }
206
207    /**
208     * The row css classes for the rows of this layout
209     * 
210     * <p>
211     * To set a css class on all rows, use "all" as a key. To set a class for even rows, use "even"
212     * as a key, for odd rows, use "odd". Use a one-based index to target a specific row by index.
213     * </p>
214     * 
215     * @return a map which represents the css classes of the rows of this layout
216     */
217    @BeanTagAttribute(name = "conditionalRowCssClasses", type = BeanTagAttribute.AttributeType.MAPVALUE)
218    public Map<String, String> getConditionalRowCssClasses() {
219        return conditionalRowCssClasses;
220    }
221
222    /**
223     * Set conditionalRowCssClasses
224     * 
225     * @param conditionalRowCssClasses
226     */
227    public void setConditionalRowCssClasses(Map<String, String> conditionalRowCssClasses) {
228        this.conditionalRowCssClasses = conditionalRowCssClasses;
229    }
230
231    /**
232     * The layout css class used by the framework to represent the row as a row visually (currently
233     * using a bootstrap class), which should not be manually reset in most situations
234     * 
235     * @return the css structure class for the rows of this layout
236     */
237    @BeanTagAttribute(name = "rowLayoutCssClass")
238    public String getRowLayoutCssClass() {
239        return rowLayoutCssClass;
240    }
241
242    /**
243     * Set the rowLayoutCssClass
244     * 
245     * @param rowLayoutCssClass
246     */
247    public void setRowLayoutCssClass(String rowLayoutCssClass) {
248        this.rowLayoutCssClass = rowLayoutCssClass;
249    }
250
251    /**
252     * @see org.kuali.rice.krad.uif.component.ComponentBase#copy()
253     */
254    @Override
255    protected <T> void copyProperties(T component) {
256        super.copyProperties(component);
257        CssGridLayoutManager cssGridLayoutManagerCopy = (CssGridLayoutManager) component;
258
259        if (this.rowLayoutCssClass != null) {
260            cssGridLayoutManagerCopy.setRowLayoutCssClass(this.rowLayoutCssClass);
261        }
262
263        cssGridLayoutManagerCopy.setDefaultItemColSpan(this.defaultItemColSpan);
264
265        if (this.conditionalRowCssClasses != null) {
266            cssGridLayoutManagerCopy.setConditionalRowCssClasses(new HashMap<String, String>(
267                    this.conditionalRowCssClasses));
268        }
269
270        if (this.cellCssClassAttributes != null) {
271            cssGridLayoutManagerCopy.cellCssClassAttributes = new ArrayList<String>(this.cellCssClassAttributes);
272        }
273
274        if (this.rowCssClassAttributes != null) {
275            cssGridLayoutManagerCopy.rowCssClassAttributes = new ArrayList<String>(this.rowCssClassAttributes);
276        }
277
278        if (this.rows != null) {
279            cssGridLayoutManagerCopy.rows = new ArrayList<List<Component>>();
280            for (List<Component> row : this.rows) {
281                List<Component> rowCopy = new ArrayList<Component>();
282
283                if (row == null) {
284                    cssGridLayoutManagerCopy.rows.add(row);
285                    continue;
286                }
287
288                for (Component cellComp : row) {
289                    if (cellComp == null) {
290                        rowCopy.add(cellComp);
291                        continue;
292                    }
293                    rowCopy.add((Component) cellComp.copy());
294                }
295
296                cssGridLayoutManagerCopy.rows.add(rowCopy);
297            }
298        }
299
300    }
301}