001/** 002 * Copyright 2005-2015 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 */ 016package org.kuali.rice.krad.uif.layout; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krad.datadictionary.parse.BeanTag; 020import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute; 021import org.kuali.rice.krad.datadictionary.parse.BeanTags; 022import org.kuali.rice.krad.uif.component.Component; 023import org.kuali.rice.krad.uif.container.Container; 024import org.kuali.rice.krad.uif.view.View; 025import org.kuali.rice.krad.util.KRADUtils; 026 027import java.util.ArrayList; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031 032/** 033 * Css Grid Layout manager is a layout manager which creates div "rows" and "cells" to replicate a table look by 034 * using div elements for its items. Items are added into rows based on their colSpan setting, while each row has a 035 * max 036 * size of 9 columns. By default, if colSpan is not set on an item, that item will take a full row. 037 */ 038@BeanTags({@BeanTag(name = "cssGridLayout-bean", parent = "Uif-CssGridLayoutBase"), 039 @BeanTag(name = "fixedCssGridLayout-bean", parent = "Uif-FixedCssGridLayout"), 040 @BeanTag(name = "fluidCssGridLayout-bean", parent = "Uif-FluidCssGridLayout")}) 041public class CssGridLayoutManager extends LayoutManagerBase { 042 private static final long serialVersionUID = 1830635073147703757L; 043 044 private static final int NUMBER_OF_COLUMNS = 9; 045 private static final String BOOTSTRAP_SPAN_PREFIX = "span"; 046 047 private Map<String, String> conditionalRowCssClasses; 048 private String rowLayoutCssClass; 049 private int defaultItemColSpan; 050 051 // non-settable 052 protected List<List<Component>> rows; 053 protected List<String> rowCssClassAttributes; 054 protected List<String> cellCssClassAttributes; 055 056 public CssGridLayoutManager() { 057 rows = new ArrayList<List<Component>>(); 058 conditionalRowCssClasses = new HashMap<String, String>(); 059 cellCssClassAttributes = new ArrayList<String>(); 060 rowCssClassAttributes = new ArrayList<String>(); 061 } 062 063 /** 064 * CssGridLayoutManager's performFinalize method calculates and separates the items into rows 065 * based on their colSpan settings and the defaultItemColSpan setting 066 * 067 * @see Component#performFinalize(org.kuali.rice.krad.uif.view.View, Object, 068 * org.kuali.rice.krad.uif.component.Component) 069 */ 070 @Override 071 public void performFinalize(View view, Object model, Container container) { 072 super.performFinalize(view, model, container); 073 074 int rowSpaceLeft = NUMBER_OF_COLUMNS; 075 int rowIndex = 0; 076 boolean isOdd = true; 077 List<Component> currentRow = new ArrayList<Component>(); 078 for (Component item : container.getItems()) { 079 if (item == null) { 080 continue; 081 } 082 isOdd = rowIndex % 2 == 0; 083 084 // set colSpan to default setting (9 is the default) 085 int colSpan = this.defaultItemColSpan; 086 087 // if the item's set colSpan is greater than 1 set it to that number; 1 is the default colSpan for Component 088 if (item.getColSpan() > 1 && item.getColSpan() <= NUMBER_OF_COLUMNS) { 089 colSpan = item.getColSpan(); 090 } 091 092 // determine "cell" div css 093 List<String> cellCssClasses = item.getCellCssClasses(); 094 if (cellCssClasses == null) { 095 item.setCellCssClasses(cellCssClasses = new ArrayList<String>()); 096 } 097 cellCssClasses.add(0, BOOTSTRAP_SPAN_PREFIX + colSpan); 098 cellCssClassAttributes.add(getCellStyleClassesAsString(cellCssClasses)); 099 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}