1 /**
2 * Copyright 2005-2013 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 java.util.ArrayList;
19 import java.util.List;
20
21 import org.apache.commons.lang.StringUtils;
22 import org.kuali.rice.krad.datadictionary.parse.BeanTag;
23 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
24 import org.kuali.rice.krad.datadictionary.parse.BeanTags;
25 import org.kuali.rice.krad.uif.CssConstants;
26 import org.kuali.rice.krad.uif.component.Component;
27 import org.kuali.rice.krad.uif.container.Container;
28 import org.kuali.rice.krad.uif.container.Group;
29
30 /**
31 * Layout manager that organizes its components in a table based grid
32 *
33 * <p>
34 * Items are laid out from left to right (with each item taking up one column)
35 * until the configured number of columns is reached. If the item count is
36 * greater than the number of columns, a new row will be created to render the
37 * remaining items (and so on until all items are placed). Labels for the fields
38 * can be pulled out (default) and rendered as a separate column. The manager
39 * also supports the column span and row span options for the field items. If
40 * not specified the default is 1.
41 * </p>
42 *
43 * @author Kuali Rice Team (rice.collab@kuali.org)
44 */
45 @BeanTags({@BeanTag(name = "gridLayout-bean", parent = "Uif-GridLayoutBase"),
46 @BeanTag(name = "twoColumnGridLayout-bean", parent = "Uif-TwoColumnGridLayout"),
47 @BeanTag(name = "fourColumnGridLayout-bean", parent = "Uif-FourColumnGridLayout"),
48 @BeanTag(name = "sixColumnGridLayout-bean", parent = "Uif-SixColumnGridLayout")})
49 public class GridLayoutManager extends LayoutManagerBase {
50 private static final long serialVersionUID = 1890011900375071128L;
51
52 private int numberOfColumns;
53
54 private boolean suppressLineWrapping;
55 private boolean applyAlternatingRowStyles;
56 private boolean applyDefaultCellWidths;
57 private boolean renderFirstRowHeader;
58 private boolean renderAlternatingHeaderColumns;
59 private boolean renderRowFirstCellHeader;
60
61 private List<String> rowCssClasses;
62
63 public GridLayoutManager() {
64 super();
65
66 rowCssClasses = new ArrayList<String>();
67 }
68
69 /**
70 * The following finalization is performed:
71 *
72 * <ul>
73 * <li>If suppressLineWrapping is true, sets the number of columns to the
74 * container's items list size</li>
75 * <li>Adjust the cell attributes for the container items</li>
76 * </ul>
77 *
78 * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#performFinalize(org.kuali.rice.krad.uif.view.View,
79 * java.lang.Object, org.kuali.rice.krad.uif.container.Container)
80 */
81 @Override
82 public void performFinalize(Object model, Component component) {
83 super.performFinalize(model, component);
84
85 Container container = (Container) component;
86
87 if (suppressLineWrapping) {
88 numberOfColumns = container.getItems().size();
89 }
90
91 for (Component item : container.getItems()) {
92 if (!(this instanceof TableLayoutManager)) {
93 item.addWrapperCssClass("uif-gridLayoutCell");
94 }
95 setCellAttributes(item);
96 }
97 }
98
99 /**
100 * Moves the width, align, and valign settings of the component to the corresponding cell properties (if not
101 * already configured)
102 *
103 * @param component instance to adjust settings for
104 */
105 protected void setCellAttributes(Component component) {
106 if (StringUtils.isNotBlank(component.getWidth()) && StringUtils.isBlank(component.getCellWidth())) {
107 component.setCellWidth(component.getWidth());
108 component.setWidth("");
109 }
110
111 if (StringUtils.isNotBlank(component.getAlign()) && !StringUtils.contains(component.getWrapperStyle(),
112 CssConstants.TEXT_ALIGN)) {
113 if (component.getWrapperStyle() == null) {
114 component.setWrapperStyle("");
115 }
116
117 component.setWrapperStyle(
118 component.getWrapperStyle() + CssConstants.TEXT_ALIGN + component.getAlign() + ";");
119 component.setAlign("");
120 }
121
122 if (StringUtils.isNotBlank(component.getValign()) && !StringUtils.contains(component.getWrapperStyle(),
123 CssConstants.VERTICAL_ALIGN)) {
124 if (component.getWrapperStyle() == null) {
125 component.setWrapperStyle("");
126 }
127
128 component.setWrapperStyle(
129 component.getWrapperStyle() + CssConstants.VERTICAL_ALIGN + component.getValign() + ";");
130 component.setValign("");
131 }
132 }
133
134 /**
135 * @see LayoutManagerBase#getSupportedContainer()
136 */
137 @Override
138 public Class<? extends Container> getSupportedContainer() {
139 return Group.class;
140 }
141
142 /**
143 * Indicates the number of columns that should make up one row of data
144 *
145 * <p>
146 * If the item count is greater than the number of columns, a new row will
147 * be created to render the remaining items (and so on until all items are
148 * placed).
149 * </p>
150 *
151 * <p>
152 * Note this does not include any generated columns by the layout manager,
153 * so the final column count could be greater (if label fields are
154 * separate).
155 * </p>
156 *
157 * @return int
158 */
159 @BeanTagAttribute(name = "numberOfColumns")
160 public int getNumberOfColumns() {
161 return this.numberOfColumns;
162 }
163
164 /**
165 * Setter for the number of columns (each row)
166 *
167 * @param numberOfColumns
168 */
169 public void setNumberOfColumns(int numberOfColumns) {
170 this.numberOfColumns = numberOfColumns;
171 }
172
173 /**
174 * Indicates whether the number of columns for the table data should match
175 * the number of fields given in the container's items list (so that each
176 * field takes up one column without wrapping), this overrides the configured
177 * numberOfColumns
178 *
179 * <p>
180 * If set to true during the initialize phase the number of columns will be
181 * set to the size of the container's field list, if false the configured
182 * number of columns is used
183 * </p>
184 *
185 * @return true if the column count should match the container's
186 * field count, false to use the configured number of columns
187 */
188 @BeanTagAttribute(name = "suppressLineWrapping")
189 public boolean isSuppressLineWrapping() {
190 return this.suppressLineWrapping;
191 }
192
193 /**
194 * Setter for the suppressLineWrapping indicator
195 *
196 * @param suppressLineWrapping
197 */
198 public void setSuppressLineWrapping(boolean suppressLineWrapping) {
199 this.suppressLineWrapping = suppressLineWrapping;
200 }
201
202 /**
203 * Indicates whether alternating row styles should be applied
204 *
205 * <p>
206 * Indicator to layout manager templates to apply alternating row styles.
207 * See the configured template for the actual style classes used
208 * </p>
209 *
210 * @return true if alternating styles should be applied, false if
211 * all rows should have the same style
212 */
213 @BeanTagAttribute(name = "applyAlternatingRowStyles")
214 public boolean isApplyAlternatingRowStyles() {
215 return this.applyAlternatingRowStyles;
216 }
217
218 /**
219 * Setter for the alternating row styles indicator
220 *
221 * @param applyAlternatingRowStyles
222 */
223 public void setApplyAlternatingRowStyles(boolean applyAlternatingRowStyles) {
224 this.applyAlternatingRowStyles = applyAlternatingRowStyles;
225 }
226
227 /**
228 * Indicates whether the manager should default the cell widths
229 *
230 * <p>
231 * If true, the manager will set the cell width by equally dividing by the
232 * number of columns
233 * </p>
234 *
235 * @return true if default cell widths should be applied, false if
236 * no defaults should be applied
237 */
238 @BeanTagAttribute(name = "applyDefaultCellWidths")
239 public boolean isApplyDefaultCellWidths() {
240 return this.applyDefaultCellWidths;
241 }
242
243 /**
244 * Setter for the default cell width indicator
245 *
246 * @param applyDefaultCellWidths
247 */
248 public void setApplyDefaultCellWidths(boolean applyDefaultCellWidths) {
249 this.applyDefaultCellWidths = applyDefaultCellWidths;
250 }
251
252 /**
253 * Indicates whether the first cell of each row should be rendered as a header cell (th)
254 *
255 * <p>
256 * When this flag is turned on, the first cell for each row will be rendered as a header cell. If
257 * {@link #isRenderAlternatingHeaderColumns()} is false, the remaining cells for the row will be rendered
258 * as data cells, else they will alternate between cell headers
259 * </p>
260 *
261 * @return true if first cell of each row should be rendered as a header cell
262 */
263 @BeanTagAttribute(name = "renderRowFirstCellHeader")
264 public boolean isRenderRowFirstCellHeader() {
265 return renderRowFirstCellHeader;
266 }
267
268 /**
269 * Setter for render first row column as header indicator
270 *
271 * @param renderRowFirstCellHeader
272 */
273 public void setRenderRowFirstCellHeader(boolean renderRowFirstCellHeader) {
274 this.renderRowFirstCellHeader = renderRowFirstCellHeader;
275 }
276
277 /**
278 * Indicates whether the first row of items rendered should all be rendered as table header (th) cells
279 *
280 * <p>
281 * Generally when using a grid layout all the cells will be tds or alternating th/td (with the label in the
282 * th cell). However in some cases it might be desired to display the labels in one row as table header cells (th)
283 * followed by a row with the corresponding fields in td cells. When this is enabled this type of layout is
284 * possible
285 * </p>
286 *
287 * @return true if first row should be rendered as header cells
288 */
289 @BeanTagAttribute(name = "renderFirstRowHeader")
290 public boolean isRenderFirstRowHeader() {
291 return renderFirstRowHeader;
292 }
293
294 /**
295 * Setter for the first row as header indicator
296 *
297 * @param renderFirstRowHeader
298 */
299 public void setRenderFirstRowHeader(boolean renderFirstRowHeader) {
300 this.renderFirstRowHeader = renderFirstRowHeader;
301 }
302
303 /**
304 * Indicates whether header columns (th for tables) should be rendered for
305 * every other item (alternating)
306 *
307 * <p>
308 * If true the first cell of each row will be rendered as an header, with
309 * every other cell in the row as a header
310 * </p>
311 *
312 * @return true if alternating headers should be rendered, false if not
313 */
314 @BeanTagAttribute(name = "renderAlternatingHeaderColumns")
315 public boolean isRenderAlternatingHeaderColumns() {
316 return this.renderAlternatingHeaderColumns;
317 }
318
319 /**
320 * Setter for the render alternating header columns indicator
321 *
322 * @param renderAlternatingHeaderColumns
323 */
324 public void setRenderAlternatingHeaderColumns(boolean renderAlternatingHeaderColumns) {
325 this.renderAlternatingHeaderColumns = renderAlternatingHeaderColumns;
326 }
327
328 /**
329 * The list of styles for each row
330 *
331 * <p>
332 * Each entry in the list gives the style for the row with the same index. This style will be added the the <tr>
333 * tag
334 * when the table rows are rendered in the grid.tag. This is used to store the styles for newly added lines and
335 * other special cases like the add item row.
336 * </p>
337 *
338 * @return list of styles for the rows
339 */
340 @BeanTagAttribute(name = "rowCssClasses", type = BeanTagAttribute.AttributeType.LISTVALUE)
341 public List<String> getRowCssClasses() {
342 return rowCssClasses;
343 }
344
345 /**
346 * Setter for the list that stores the css style names of each row
347 *
348 * @param rowCssClasses
349 */
350 public void setRowCssClasses(List<String> rowCssClasses) {
351 this.rowCssClasses = rowCssClasses;
352 }
353
354 /**
355 * @see org.kuali.rice.krad.datadictionary.DictionaryBeanBase#copyProperties(Object)
356 */
357 @Override
358 protected <T> void copyProperties(T layoutManager) {
359 super.copyProperties(layoutManager);
360
361 GridLayoutManager gridLayoutManagerCopy = (GridLayoutManager) layoutManager;
362
363 gridLayoutManagerCopy.setNumberOfColumns(this.numberOfColumns);
364 gridLayoutManagerCopy.setSuppressLineWrapping(this.suppressLineWrapping);
365 gridLayoutManagerCopy.setApplyAlternatingRowStyles(this.applyAlternatingRowStyles);
366 gridLayoutManagerCopy.setApplyDefaultCellWidths(this.applyDefaultCellWidths);
367 gridLayoutManagerCopy.setRenderFirstRowHeader(this.renderFirstRowHeader);
368 gridLayoutManagerCopy.setRenderAlternatingHeaderColumns(this.renderAlternatingHeaderColumns);
369 gridLayoutManagerCopy.setRenderRowFirstCellHeader(this.renderRowFirstCellHeader);
370
371 if (rowCssClasses != null) {
372 gridLayoutManagerCopy.setRowCssClasses(new ArrayList<String>(rowCssClasses));
373 }
374 }
375 }