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    }