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