View Javadoc
1   /**
2    * Copyright 2005-2015 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.uif.CssConstants;
22  import org.kuali.rice.krad.uif.component.Component;
23  import org.kuali.rice.krad.uif.container.Container;
24  import org.kuali.rice.krad.uif.element.Label;
25  import org.kuali.rice.krad.uif.field.Field;
26  import org.kuali.rice.krad.uif.field.InputField;
27  import org.kuali.rice.krad.uif.util.LifecycleElement;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  
32  /**
33   * A Css Grid Layout which only takes fields as its content and separates out the field's labels into
34   * separate columns
35   *
36   * <p>This layout does not use the container's items' colspan property to influence column size.</p>
37   *
38   * @author Kuali Rice Team (rice.collab@kuali.org)
39   */
40  @BeanTag(name = "cssGridLabelColumnLayout", parent = "Uif-CssGridLabelColumnLayout")
41  public class CssGridLabelColumnLayoutManager extends CssGridLayoutManagerBase {
42      private static final long serialVersionUID = 3100360397450755904L;
43  
44      private int numberOfLabelColumns = 1;
45      private String labelColumnCssClass = "";
46  
47      private CssGridSizes labelColumnSizes;
48      private CssGridSizes fieldColumnSizes;
49  
50      // Internal local variables
51      protected int xsCurrentFieldSize;
52      protected int smCurrentFieldSize;
53      protected int mdCurrentFieldSize;
54      protected int lgCurrentFieldSize;
55  
56      public CssGridLabelColumnLayoutManager() {
57          super();
58          labelColumnSizes = new CssGridSizes();
59          fieldColumnSizes = new CssGridSizes();
60      }
61  
62      /**
63       * CssGridLabelColumnLayoutManager's performFinalize method calculates and separates the items into rows
64       *
65       * {@inheritDoc}
66       */
67      @Override
68      public void performFinalize(Object model, LifecycleElement component) {
69          super.performFinalize(model, component);
70  
71          Container container = (Container) component;
72          cellItems = new ArrayList<Component>();
73          processSeparateLabelLayout(container);
74      }
75  
76      /**
77       * Separates the labels and field content into the appropriate number of rows and div "cells" based on
78       * the numberOfLabelColumns property, by making making labels take up their own column and turning off rendering
79       * them for the fields
80       *
81       * @param container the container using this layout manager
82       */
83      private void processSeparateLabelLayout(Container container) {
84          // Defaults if label and field column sizes are not set directly
85          int labelColumnSize = 3;
86          int fieldColumnSize = 9;
87          if (numberOfLabelColumns > 1) {
88              labelColumnSize = (NUMBER_OF_COLUMNS / numberOfLabelColumns) * 1 / 3;
89              fieldColumnSize = (NUMBER_OF_COLUMNS / numberOfLabelColumns) * 2 / 3;
90          }
91  
92          for (Component item : container.getItems()) {
93              // Throw exception for non-fields or fields without labels
94              if (!(item instanceof Field)) {
95                  throw new RuntimeException("Must use fields "
96                          + " for CssGridLabelColumnLayouts. Item class: "
97                          + item.getClass().getName()
98                          +
99                          " 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 }