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}