001 /** 002 * Copyright 2005-2013 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 */ 016 package org.kuali.rice.krad.uif.layout; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.kuali.rice.krad.datadictionary.parse.BeanTag; 020 import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 021 import org.kuali.rice.krad.datadictionary.parse.BeanTags; 022 import org.kuali.rice.krad.uif.CssConstants; 023 import org.kuali.rice.krad.uif.component.Component; 024 import org.kuali.rice.krad.uif.container.Container; 025 import org.kuali.rice.krad.uif.container.Group; 026 import org.kuali.rice.krad.uif.view.View; 027 028 import java.util.ArrayList; 029 import java.util.List; 030 031 /** 032 * Layout manager that organizes its components in a table based grid 033 * 034 * <p> 035 * Items are laid out from left to right (with each item taking up one column) 036 * until the configured number of columns is reached. If the item count is 037 * greater than the number of columns, a new row will be created to render the 038 * remaining items (and so on until all items are placed). Labels for the fields 039 * can be pulled out (default) and rendered as a separate column. The manager 040 * also supports the column span and row span options for the field items. If 041 * not specified the default is 1. 042 * </p> 043 * 044 * @author Kuali Rice Team (rice.collab@kuali.org) 045 */ 046 @BeanTags({@BeanTag(name = "gridLayout-bean", parent = "Uif-GridLayoutBase"), 047 @BeanTag(name = "twoColumnGridLayout-bean", parent = "Uif-TwoColumnGridLayout"), 048 @BeanTag(name = "fourColumnGridLayout-bean", parent = "Uif-FourColumnGridLayout"), 049 @BeanTag(name = "sixColumnGridLayout-bean", parent = "Uif-SixColumnGridLayout")}) 050 public class GridLayoutManager extends LayoutManagerBase { 051 private static final long serialVersionUID = 1890011900375071128L; 052 053 private int numberOfColumns; 054 055 private boolean suppressLineWrapping; 056 private boolean applyAlternatingRowStyles; 057 private boolean applyDefaultCellWidths; 058 private boolean renderFirstRowHeader; 059 private boolean renderAlternatingHeaderColumns; 060 private boolean renderRowFirstCellHeader; 061 062 private List<String> rowCssClasses; 063 064 public GridLayoutManager() { 065 super(); 066 067 rowCssClasses = new ArrayList<String>(); 068 } 069 070 /** 071 * The following finalization is performed: 072 * 073 * <ul> 074 * <li>If suppressLineWrapping is true, sets the number of columns to the 075 * container's items list size</li> 076 * <li>Adjust the cell attributes for the container items</li> 077 * </ul> 078 * 079 * @see org.kuali.rice.krad.uif.layout.LayoutManagerBase#performFinalize(org.kuali.rice.krad.uif.view.View, 080 * java.lang.Object, org.kuali.rice.krad.uif.container.Container) 081 */ 082 @Override 083 public void performFinalize(View view, Object model, Container container) { 084 super.performFinalize(view, model, container); 085 086 if (suppressLineWrapping) { 087 numberOfColumns = container.getItems().size(); 088 } 089 090 for (Component component : container.getItems()) { 091 setCellAttributes(component); 092 } 093 } 094 095 /** 096 * Moves the width, align, and valign settings of the component to the corresponding cell properties (if not 097 * already configured) 098 * 099 * @param component instance to adjust settings for 100 */ 101 protected void setCellAttributes(Component component) { 102 if (StringUtils.isNotBlank(component.getWidth()) && StringUtils.isBlank(component.getCellWidth())) { 103 component.setCellWidth(component.getWidth()); 104 component.setWidth(""); 105 } 106 107 if (StringUtils.isNotBlank(component.getAlign()) && !StringUtils.contains(component.getCellStyle(), 108 CssConstants.TEXT_ALIGN)) { 109 if (component.getCellStyle() == null) { 110 component.setCellStyle(""); 111 } 112 113 component.setCellStyle(component.getCellStyle() + CssConstants.TEXT_ALIGN + component.getAlign() + ";"); 114 component.setAlign(""); 115 } 116 117 if (StringUtils.isNotBlank(component.getValign()) && !StringUtils.contains(component.getCellStyle(), 118 CssConstants.VERTICAL_ALIGN)) { 119 if (component.getCellStyle() == null) { 120 component.setCellStyle(""); 121 } 122 123 component.setCellStyle( 124 component.getCellStyle() + CssConstants.VERTICAL_ALIGN + component.getValign() + ";"); 125 component.setValign(""); 126 } 127 } 128 129 /** 130 * @see LayoutManagerBase#getSupportedContainer() 131 */ 132 @Override 133 public Class<? extends Container> getSupportedContainer() { 134 return Group.class; 135 } 136 137 /** 138 * Indicates the number of columns that should make up one row of data 139 * 140 * <p> 141 * If the item count is greater than the number of columns, a new row will 142 * be created to render the remaining items (and so on until all items are 143 * placed). 144 * </p> 145 * 146 * <p> 147 * Note this does not include any generated columns by the layout manager, 148 * so the final column count could be greater (if label fields are 149 * separate). 150 * </p> 151 * 152 * @return int 153 */ 154 @BeanTagAttribute(name = "numberOfColumns") 155 public int getNumberOfColumns() { 156 return this.numberOfColumns; 157 } 158 159 /** 160 * Setter for the number of columns (each row) 161 * 162 * @param numberOfColumns 163 */ 164 public void setNumberOfColumns(int numberOfColumns) { 165 this.numberOfColumns = numberOfColumns; 166 } 167 168 /** 169 * Indicates whether the number of columns for the table data should match 170 * the number of fields given in the container's items list (so that each 171 * field takes up one column without wrapping), this overrides the configured 172 * numberOfColumns 173 * 174 * <p> 175 * If set to true during the initialize phase the number of columns will be 176 * set to the size of the container's field list, if false the configured 177 * number of columns is used 178 * </p> 179 * 180 * @return boolean true if the column count should match the container's 181 * field count, false to use the configured number of columns 182 */ 183 @BeanTagAttribute(name = "suppressLineWrapping") 184 public boolean isSuppressLineWrapping() { 185 return this.suppressLineWrapping; 186 } 187 188 /** 189 * Setter for the suppressLineWrapping indicator 190 * 191 * @param suppressLineWrapping 192 */ 193 public void setSuppressLineWrapping(boolean suppressLineWrapping) { 194 this.suppressLineWrapping = suppressLineWrapping; 195 } 196 197 /** 198 * Indicates whether alternating row styles should be applied 199 * 200 * <p> 201 * Indicator to layout manager templates to apply alternating row styles. 202 * See the configured template for the actual style classes used 203 * </p> 204 * 205 * @return boolean true if alternating styles should be applied, false if 206 * all rows should have the same style 207 */ 208 @BeanTagAttribute(name = "applyAlternatingRowStyles") 209 public boolean isApplyAlternatingRowStyles() { 210 return this.applyAlternatingRowStyles; 211 } 212 213 /** 214 * Setter for the alternating row styles indicator 215 * 216 * @param applyAlternatingRowStyles 217 */ 218 public void setApplyAlternatingRowStyles(boolean applyAlternatingRowStyles) { 219 this.applyAlternatingRowStyles = applyAlternatingRowStyles; 220 } 221 222 /** 223 * Indicates whether the manager should default the cell widths 224 * 225 * <p> 226 * If true, the manager will set the cell width by equally dividing by the 227 * number of columns 228 * </p> 229 * 230 * @return boolean true if default cell widths should be applied, false if 231 * no defaults should be applied 232 */ 233 @BeanTagAttribute(name = "applyDefaultCellWidths") 234 public boolean isApplyDefaultCellWidths() { 235 return this.applyDefaultCellWidths; 236 } 237 238 /** 239 * Setter for the default cell width indicator 240 * 241 * @param applyDefaultCellWidths 242 */ 243 public void setApplyDefaultCellWidths(boolean applyDefaultCellWidths) { 244 this.applyDefaultCellWidths = applyDefaultCellWidths; 245 } 246 247 /** 248 * Indicates whether the first cell of each row should be rendered as a header cell (th) 249 * 250 * <p> 251 * When this flag is turned on, the first cell for each row will be rendered as a header cell. If 252 * {@link #isRenderAlternatingHeaderColumns()} is false, the remaining cells for the row will be rendered 253 * as data cells, else they will alternate between cell headers 254 * </p> 255 * 256 * @return boolean true if first cell of each row should be rendered as a header cell 257 */ 258 @BeanTagAttribute(name = "renderRowFirstCellHeader") 259 public boolean isRenderRowFirstCellHeader() { 260 return renderRowFirstCellHeader; 261 } 262 263 /** 264 * Setter for render first row column as header indicator 265 * 266 * @param renderRowFirstCellHeader 267 */ 268 public void setRenderRowFirstCellHeader(boolean renderRowFirstCellHeader) { 269 this.renderRowFirstCellHeader = renderRowFirstCellHeader; 270 } 271 272 /** 273 * Indicates whether the first row of items rendered should all be rendered as table header (th) cells 274 * 275 * <p> 276 * Generally when using a grid layout all the cells will be tds or alternating th/td (with the label in the 277 * th cell). However in some cases it might be desired to display the labels in one row as table header cells (th) 278 * followed by a row with the corresponding fields in td cells. When this is enabled this type of layout is 279 * possible 280 * </p> 281 * 282 * @return boolean true if first row should be rendered as header cells 283 */ 284 @BeanTagAttribute(name = "renderFirstRowHeader") 285 public boolean isRenderFirstRowHeader() { 286 return renderFirstRowHeader; 287 } 288 289 /** 290 * Setter for the first row as header indicator 291 * 292 * @param renderFirstRowHeader 293 */ 294 public void setRenderFirstRowHeader(boolean renderFirstRowHeader) { 295 this.renderFirstRowHeader = renderFirstRowHeader; 296 } 297 298 /** 299 * Indicates whether header columns (th for tables) should be rendered for 300 * every other item (alternating) 301 * 302 * <p> 303 * If true the first cell of each row will be rendered as an header, with 304 * every other cell in the row as a header 305 * </p> 306 * 307 * @return boolean true if alternating headers should be rendered, false if 308 * not 309 */ 310 @BeanTagAttribute(name = "renderAlternatingHeaderColumns") 311 public boolean isRenderAlternatingHeaderColumns() { 312 return this.renderAlternatingHeaderColumns; 313 } 314 315 /** 316 * Setter for the render alternating header columns indicator 317 * 318 * @param renderAlternatingHeaderColumns 319 */ 320 public void setRenderAlternatingHeaderColumns(boolean renderAlternatingHeaderColumns) { 321 this.renderAlternatingHeaderColumns = renderAlternatingHeaderColumns; 322 } 323 324 /** 325 * The list of styles for each row 326 * 327 * <p> 328 * Each entry in the list gives the style for the row with the same index. This style will be added the the <tr> 329 * tag 330 * when the table rows are rendered in the grid.tag. This is used to store the styles for newly added lines and 331 * other special cases like the add item row. 332 * </p> 333 * 334 * @return List<String> list of styles for the rows 335 */ 336 @BeanTagAttribute(name = "rowCssClasses", type = BeanTagAttribute.AttributeType.LISTVALUE) 337 public List<String> getRowCssClasses() { 338 return rowCssClasses; 339 } 340 341 /** 342 * Setter for the list that stores the css style names of each row 343 * 344 * @param rowCssClasses 345 */ 346 public void setRowCssClasses(List<String> rowCssClasses) { 347 this.rowCssClasses = rowCssClasses; 348 } 349 }