View Javadoc
1   /**
2    * Copyright 2005-2016 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;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
20  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
21  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
22  import org.kuali.rice.krad.uif.component.Component;
23  import org.kuali.rice.krad.uif.container.Container;
24  import org.kuali.rice.krad.uif.view.View;
25  import org.kuali.rice.krad.util.KRADUtils;
26  
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  /**
33   * Css Grid Layout manager is a layout manager which creates div "rows" and "cells" to replicate a table look by
34   * using div elements for its items.  Items are added into rows based on their colSpan setting, while each row has a
35   * max
36   * size of 9 columns.  By default, if colSpan is not set on an item, that item will take a full row.
37   */
38  @BeanTags({@BeanTag(name = "cssGridLayout-bean", parent = "Uif-CssGridLayoutBase"),
39          @BeanTag(name = "fixedCssGridLayout-bean", parent = "Uif-FixedCssGridLayout"),
40          @BeanTag(name = "fluidCssGridLayout-bean", parent = "Uif-FluidCssGridLayout")})
41  public class CssGridLayoutManager extends LayoutManagerBase {
42      private static final long serialVersionUID = 1830635073147703757L;
43  
44      private static final int NUMBER_OF_COLUMNS = 9;
45      private static final String BOOTSTRAP_SPAN_PREFIX = "span";
46  
47      private Map<String, String> conditionalRowCssClasses;
48      private String rowLayoutCssClass;
49      private int defaultItemColSpan;
50  
51      // non-settable
52      protected List<List<Component>> rows;
53      protected List<String> rowCssClassAttributes;
54      protected List<String> cellCssClassAttributes;
55  
56      public CssGridLayoutManager() {
57          rows = new ArrayList<List<Component>>();
58          conditionalRowCssClasses = new HashMap<String, String>();
59          cellCssClassAttributes = new ArrayList<String>();
60          rowCssClassAttributes = new ArrayList<String>();
61      }
62  
63      /**
64       * CssGridLayoutManager's performFinalize method calculates and separates the items into rows
65       * based on their colSpan settings and the defaultItemColSpan setting
66       * 
67       * @see Component#performFinalize(org.kuali.rice.krad.uif.view.View, Object,
68       *      org.kuali.rice.krad.uif.component.Component)
69       */
70      @Override
71      public void performFinalize(View view, Object model, Container container) {
72          super.performFinalize(view, model, container);
73  
74          int rowSpaceLeft = NUMBER_OF_COLUMNS;
75          int rowIndex = 0;
76          boolean isOdd = true;
77          List<Component> currentRow = new ArrayList<Component>();
78          for (Component item : container.getItems()) {
79              if (item == null) {
80                  continue;
81              }
82              isOdd = rowIndex % 2 == 0;
83  
84              // set colSpan to default setting (9 is the default)
85              int colSpan = this.defaultItemColSpan;
86  
87              // if the item's set colSpan is greater than 1 set it to that number; 1 is the default colSpan for Component
88              if (item.getColSpan() > 1 && item.getColSpan() <= NUMBER_OF_COLUMNS) {
89                  colSpan = item.getColSpan();
90              }
91  
92              // determine "cell" div css
93              List<String> cellCssClasses = item.getCellCssClasses();
94              if (cellCssClasses == null) {
95                  item.setCellCssClasses(cellCssClasses = new ArrayList<String>());
96              }
97              cellCssClasses.add(0, BOOTSTRAP_SPAN_PREFIX + colSpan);
98              cellCssClassAttributes.add(getCellStyleClassesAsString(cellCssClasses));
99  
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 }