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 if (this instanceof TableLayoutManager) { 092 component.addCellCssClass("uif-gridLayoutCell"); 093 } 094 setCellAttributes(component); 095 } 096 } 097 098 /** 099 * 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 }