001/** 002 * Copyright 2005-2018 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.uif.CssConstants; 022import org.kuali.rice.krad.uif.component.Component; 023import org.kuali.rice.krad.uif.container.Container; 024import org.kuali.rice.krad.uif.element.Label; 025import org.kuali.rice.krad.uif.field.Field; 026import org.kuali.rice.krad.uif.field.InputField; 027import org.kuali.rice.krad.uif.util.LifecycleElement; 028 029import java.util.ArrayList; 030import java.util.List; 031 032/** 033 * A Css Grid Layout which only takes fields as its content and separates out the field's labels into 034 * separate columns 035 * 036 * <p>This layout does not use the container's items' colspan property to influence column size.</p> 037 * 038 * @author Kuali Rice Team (rice.collab@kuali.org) 039 */ 040@BeanTag(name = "cssGridLabelColumnLayout", parent = "Uif-CssGridLabelColumnLayout") 041public class CssGridLabelColumnLayoutManager extends CssGridLayoutManagerBase { 042 private static final long serialVersionUID = 3100360397450755904L; 043 044 private int numberOfLabelColumns = 1; 045 private String labelColumnCssClass = ""; 046 047 private CssGridSizes labelColumnSizes; 048 private CssGridSizes fieldColumnSizes; 049 050 // Internal local variables 051 protected int xsCurrentFieldSize; 052 protected int smCurrentFieldSize; 053 protected int mdCurrentFieldSize; 054 protected int lgCurrentFieldSize; 055 056 public CssGridLabelColumnLayoutManager() { 057 super(); 058 labelColumnSizes = new CssGridSizes(); 059 fieldColumnSizes = new CssGridSizes(); 060 } 061 062 /** 063 * CssGridLabelColumnLayoutManager's performFinalize method calculates and separates the items into rows 064 * 065 * {@inheritDoc} 066 */ 067 @Override 068 public void performFinalize(Object model, LifecycleElement component) { 069 super.performFinalize(model, component); 070 071 Container container = (Container) component; 072 cellItems = new ArrayList<Component>(); 073 processSeparateLabelLayout(container); 074 } 075 076 /** 077 * Separates the labels and field content into the appropriate number of rows and div "cells" based on 078 * the numberOfLabelColumns property, by making making labels take up their own column and turning off rendering 079 * them for the fields 080 * 081 * @param container the container using this layout manager 082 */ 083 private void processSeparateLabelLayout(Container container) { 084 // Defaults if label and field column sizes are not set directly 085 int labelColumnSize = 3; 086 int fieldColumnSize = 9; 087 if (numberOfLabelColumns > 1) { 088 labelColumnSize = (NUMBER_OF_COLUMNS / numberOfLabelColumns) * 1 / 3; 089 fieldColumnSize = (NUMBER_OF_COLUMNS / numberOfLabelColumns) * 2 / 3; 090 } 091 092 for (Component item : container.getItems()) { 093 // Throw exception for non-fields or fields without labels 094 if (!(item instanceof Field)) { 095 throw new RuntimeException("Must use fields " 096 + " for CssGridLabelColumnLayouts. Item class: " 097 + item.getClass().getName() 098 + 099 " in Container id: " 100 + container.getId()); 101 } else if (((Field) item).getFieldLabel() == null) { 102 throw new RuntimeException( 103 "Label must exist on fields in CssGridLabelColumnLayoutManager. Item class: " + item.getClass() 104 .getName() + " in Container id: " + container.getId()); 105 } 106 107 xsCurrentFieldSize = 0; 108 smCurrentFieldSize = 0; 109 mdCurrentFieldSize = 0; 110 lgCurrentFieldSize = 0; 111 112 Field field = (Field) item; 113 Label label = separateLabel(field); 114 115 // Determine "cell" label div css 116 List<String> cellCssClasses = label.getWrapperCssClasses(); 117 if (cellCssClasses == null) { 118 label.setWrapperCssClasses(new ArrayList<String>()); 119 cellCssClasses = label.getWrapperCssClasses(); 120 } 121 122 cellCssClasses.add(0, labelColumnCssClass); 123 calculateCssClassAndSize(label, cellCssClasses, labelColumnSizes, labelColumnSize); 124 125 // Add dynamic left clear classes for potential wrapping content at each screen size 126 addLeftClearCssClass(cellCssClasses); 127 cellCssClassAttributes.add(getCellStyleClassesAsString(cellCssClasses)); 128 129 // Add label 130 cellItems.add(label); 131 132 // Determine "cell" field div css 133 cellCssClasses = field.getWrapperCssClasses(); 134 if (cellCssClasses == null) { 135 field.setWrapperCssClasses(new ArrayList<String>()); 136 cellCssClasses = field.getWrapperCssClasses(); 137 } 138 139 calculateCssClassAndSize(field, cellCssClasses, fieldColumnSizes, fieldColumnSize); 140 141 // Add dynamic float classes for each size, this is to make the label appear right when content is on 142 // the same "row" as the label, and left (default) when they are on separate lines 143 // assumption here is that content will take up more columns when becoming smaller, so if the float 144 // is right at the smallest level, assume that it will be right for the other levels 145 if (xsCurrentFieldSize > 0 && xsCurrentFieldSize <= CssGridLayoutManagerBase.NUMBER_OF_COLUMNS) { 146 label.addStyleClass(CssConstants.CssGrid.XS_FLOAT_RIGHT); 147 } else if (smCurrentFieldSize > 0 && smCurrentFieldSize <= CssGridLayoutManagerBase.NUMBER_OF_COLUMNS) { 148 label.addStyleClass(CssConstants.CssGrid.SM_FLOAT_RIGHT); 149 } else if (mdCurrentFieldSize > 0 && mdCurrentFieldSize <= CssGridLayoutManagerBase.NUMBER_OF_COLUMNS) { 150 label.addStyleClass(CssConstants.CssGrid.MD_FLOAT_RIGHT); 151 } else if (lgCurrentFieldSize > 0 && lgCurrentFieldSize <= CssGridLayoutManagerBase.NUMBER_OF_COLUMNS) { 152 label.addStyleClass(CssConstants.CssGrid.LG_FLOAT_RIGHT); 153 } 154 155 // Add dynamic left clear classes for potential wrapping content at each screen size 156 addLeftClearCssClass(cellCssClasses); 157 cellCssClassAttributes.add(getCellStyleClassesAsString(cellCssClasses)); 158 159 // Add field 160 cellItems.add(field); 161 } 162 163 } 164 165 /** 166 * Override is used to calculates total field and label size in addition to calculateCssClassAndSize functionality 167 * 168 * @see org.kuali.rice.krad.uif.layout.CssGridLayoutManagerBase#calculateCssClassAndSize(org.kuali.rice.krad.uif.component.Component, 169 * java.util.List, CssGridSizes, int) 170 */ 171 @Override 172 protected void calculateCssClassAndSize(Component item, List<String> cellCssClasses, CssGridSizes defaultSizes, 173 int basicSize) { 174 int xsPrevTotalSize = xsTotalSize; 175 int smPrevTotalSize = smTotalSize; 176 int mdPrevTotalSize = mdTotalSize; 177 int lgPrevTotalSize = lgTotalSize; 178 179 super.calculateCssClassAndSize(item, cellCssClasses, defaultSizes, basicSize); 180 181 xsCurrentFieldSize += xsTotalSize - xsPrevTotalSize; 182 smCurrentFieldSize += smTotalSize - smPrevTotalSize; 183 mdCurrentFieldSize += mdTotalSize - mdPrevTotalSize; 184 lgCurrentFieldSize += lgTotalSize - lgPrevTotalSize; 185 } 186 187 /** 188 * Returns the label on the field and sets the appropriate display settings and css classes to make it render 189 * correctly 190 * 191 * @param field the field to get the label from 192 * @return the label 193 */ 194 private Label separateLabel(Field field) { 195 Label label; 196 field.setLabelLeft(false); 197 198 // pull out label field 199 field.getFieldLabel().addStyleClass("displayWith-" + field.getId()); 200 if (!field.isRender() && StringUtils.isBlank(field.getProgressiveRender())) { 201 field.getFieldLabel().setRender(false); 202 } else if (!field.isRender() && StringUtils.isNotBlank(field.getProgressiveRender())) { 203 field.getFieldLabel().setRender(true); 204 String prefixStyle = ""; 205 if (StringUtils.isNotBlank(field.getFieldLabel().getStyle())) { 206 prefixStyle = field.getFieldLabel().getStyle(); 207 } 208 field.getFieldLabel().setStyle(prefixStyle + ";" + "display: none;"); 209 } 210 211 label = field.getFieldLabel(); 212 213 if (field instanceof InputField && field.getRequired() != null && field.getRequired()) { 214 label.setRenderRequiredIndicator(true); 215 } 216 217 // set boolean to indicate label field should not be 218 // rendered with the attribute 219 field.setLabelRendered(true); 220 221 return label; 222 } 223 224 /** 225 * The css class to use on the label column's div "cells" 226 * 227 * @return the css class to use on label column div "cells" 228 */ 229 @BeanTagAttribute 230 public String getLabelColumnCssClass() { 231 return labelColumnCssClass; 232 } 233 234 /** 235 * Setter for {@link #getLabelColumnCssClass()}. 236 * 237 * @param labelColumnCssClass property value 238 */ 239 public void setLabelColumnCssClass(String labelColumnCssClass) { 240 this.labelColumnCssClass = labelColumnCssClass; 241 } 242 243 /** 244 * The number of label columns used in this layout 245 * 246 * <p> 247 * The only supported values for this property are 1-3 which translates to 2-6 columns per a 248 * row. This property defines how many of the total columns are label columns. 249 * </p> 250 * 251 * @return the total number of label columns 252 */ 253 @BeanTagAttribute 254 public int getNumberOfLabelColumns() { 255 return numberOfLabelColumns; 256 } 257 258 /** 259 * Setter for {@link #getNumberOfLabelColumns()}. 260 * 261 * @param numberOfLabelColumns property value 262 */ 263 public void setNumberOfLabelColumns(int numberOfLabelColumns) { 264 this.numberOfLabelColumns = numberOfLabelColumns; 265 } 266 267 /** 268 * CssGridSizes that will be used by every label in this layout, unless the label itself has cssGridSizes 269 * explicitly set; note that this OVERRIDES any framework automation set by numberOfLabelColumns for label sizes. 270 * 271 * <p> 272 * Be careful with the usage of this setting, it's intent is to be set with fieldColumnSizes, or some 273 * combination of custom field and label cssGridSizes, or unintended behavior/layout may result. This is an 274 * advanced layout configuration setting and requires knowledge of bootstrap css grid layout/behavior. 275 * </p> 276 * 277 * @return the custom labelColumnSizes 278 */ 279 @BeanTagAttribute(name = "labelColumnSizes", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 280 public CssGridSizes getLabelColumnSizes() { 281 return labelColumnSizes; 282 } 283 284 /** 285 * @see CssGridLabelColumnLayoutManager#getLabelColumnSizes() 286 */ 287 public void setLabelColumnSizes(CssGridSizes labelColumnSizes) { 288 this.labelColumnSizes = labelColumnSizes; 289 } 290 291 /** 292 * CssGridSizes that will be used by every field in this layout, unless the field itself has cssGridSizes 293 * explicitly set; note that this OVERRIDES any framework automation set by numberOfLabelColumns for field sizes. 294 * 295 * <p> 296 * Be careful with the usage of this setting, it's intent is to be set with labelColumnSizes, or some 297 * combination of custom field and label cssGridSizes, or unintended behavior/layout may result. This is an 298 * advanced layout configuration setting and requires knowledge of bootstrap css grid layout/behavior. 299 * </p> 300 * 301 * @return 302 */ 303 @BeanTagAttribute(name = "fieldColumnSizes", type = BeanTagAttribute.AttributeType.SINGLEBEAN) 304 public CssGridSizes getFieldColumnSizes() { 305 return fieldColumnSizes; 306 } 307 308 /** 309 * @see CssGridLabelColumnLayoutManager#getFieldColumnSizes() 310 */ 311 public void setFieldColumnSizes(CssGridSizes fieldColumnSizes) { 312 this.fieldColumnSizes = fieldColumnSizes; 313 } 314}