View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.component;
21  
22  import org.apache.myfaces.tobago.apt.annotation.Preliminary;
23  import org.apache.myfaces.tobago.component.Attributes;
24  import org.apache.myfaces.tobago.component.RendererTypes;
25  import org.apache.myfaces.tobago.component.UIPanel;
26  import org.apache.myfaces.tobago.component.UIStyle;
27  import org.apache.myfaces.tobago.component.Visual;
28  import org.apache.myfaces.tobago.layout.GridSpan;
29  import org.apache.myfaces.tobago.layout.MeasureList;
30  import org.apache.myfaces.tobago.util.ComponentUtils;
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  import javax.faces.component.UIComponent;
35  import javax.faces.context.FacesContext;
36  import javax.faces.event.AbortProcessingException;
37  import javax.faces.event.ComponentSystemEvent;
38  import javax.faces.event.ComponentSystemEventListener;
39  import javax.faces.event.ListenerFor;
40  import javax.faces.event.PreRenderComponentEvent;
41  import java.util.List;
42  import java.util.Map;
43  
44  /**
45   * <p>
46   * A grid layout manager.
47   * </p>
48   * <p>
49   * {@link org.apache.myfaces.tobago.internal.taglib.component.GridLayoutTagDeclaration}
50   * </p>
51   */
52  @Preliminary
53  @ListenerFor(systemEventClass = PreRenderComponentEvent.class)
54  public abstract class AbstractUIGridLayout extends AbstractUILayoutBase
55      implements Visual, ComponentSystemEventListener {
56  
57    private static final Logger LOG = LoggerFactory.getLogger(AbstractUIGridLayout.class);
58  
59    public static final String COMPONENT_FAMILY = "org.apache.myfaces.tobago.GridLayout";
60  
61    protected static final UIComponent SPAN = new UIPanel();
62  
63    /**
64     * Initialize the grid and remove the current width and height values from the component, recursively.
65     */
66    @Override
67    public void processEvent(final ComponentSystemEvent event) throws AbortProcessingException {
68  
69      super.processEvent(event);
70  
71      if (!isRendered()) {
72        return;
73      }
74  
75      if (event instanceof PreRenderComponentEvent) {
76  
77        layout(
78            MeasureList.parse(getColumns()).getSize(),
79            MeasureList.parse(getRows()).getSize(),
80            ComponentUtils.findLayoutChildren(this));
81  
82      }
83    }
84  
85    public abstract String getRows();
86  
87    public abstract void setRows(String rows);
88  
89    public abstract String getColumns();
90  
91    public abstract void setColumns(String columns);
92  
93    protected UIComponent[][] layout(
94        final int columnsCount, final int initalRowsCount, final List<UIComponent> components) {
95      assert columnsCount > 0;
96      assert initalRowsCount > 0;
97  
98      final FacesContext facesContext = FacesContext.getCurrentInstance();
99      UIComponent[][] cells = new UIComponent[initalRowsCount][columnsCount];
100     int rowsCount = initalRowsCount;
101 
102     // #1 put all components with "gridRow" and "gridColumn" set into the grid cells
103     for (final UIComponent component : components) {
104       final Map<String, Object> attributes = component.getAttributes();
105       final Integer gridColumn = (Integer) attributes.get(Attributes.gridColumn.getName());
106       final Integer gridRow = (Integer) attributes.get(Attributes.gridRow.getName());
107       if (gridColumn != null && gridRow != null) {
108         if (gridColumn > columnsCount) {
109           // ignore wrong columns
110           LOG.warn("gridColumn {} > columnsCount {} in component '{}'!", gridColumn, columnsCount,
111               component.getClientId(facesContext));
112         } else {
113           if (gridRow > rowsCount) {
114             if (LOG.isDebugEnabled()) {
115               LOG.debug("expanding, because gridRow {} > rowCount {} in component '{}'!", gridRow, rowsCount,
116                   component.getClientId(facesContext));
117             }
118             // ensure enough rows
119             cells = expand(cells, gridRow, initalRowsCount);
120             rowsCount = cells.length;
121           }
122           cells = set(cells, gridColumn - 1, gridRow - 1, component, initalRowsCount);
123           rowsCount = cells.length;
124         }
125       } else if (gridColumn != null) {
126         LOG.warn("gridColumn is set to {}, but gridRow not in component '{}'!", gridColumn,
127             component.getClientId(facesContext));
128       } else if (gridRow != null) {
129         LOG.warn("gridRow is set to {}, but gridColumn not in component '{}'!", gridRow,
130             component.getClientId(facesContext));
131       }
132     }
133 
134     // #2 distribute the rest of the components to the free grid cells
135     int j = 0;
136     int i = 0;
137     for (final UIComponent component : components) {
138       final Map<String, Object> attributes = component.getAttributes();
139       final Integer gridColumn = (Integer) attributes.get(Attributes.gridColumn.getName());
140       final Integer gridRow = (Integer) attributes.get(Attributes.gridRow.getName());
141       // find a component without a position
142       // if only one value is defined, treat as undefined
143       if (gridColumn == null || gridRow == null) {
144         // find next free cell
145         while (cells[j][i] != null) {
146           i++;
147           if (i >= columnsCount) {
148             i = 0;
149             j++;
150           }
151           if (j >= rowsCount) {
152             cells = expand(cells, j + 1, initalRowsCount);
153             rowsCount = cells.length;
154           }
155         }
156         cells = set(cells, i, j, component, initalRowsCount);
157         rowsCount = cells.length;
158       }
159     }
160 
161     // #3 create UIStyle children. TODO: There might be a better way...
162     for (final UIComponent component : components) {
163       final Map<String, Object> attributes = component.getAttributes();
164 
165       UIStyle style = ComponentUtils.findChild(component, UIStyle.class);
166       if (style == null) {
167         style = (UIStyle) ComponentUtils.createComponent(
168             facesContext, UIStyle.COMPONENT_TYPE, RendererTypes.Style, null);
169         style.setTransient(true);
170         component.getChildren().add(style);
171       }
172 
173       final Integer gridColumn = (Integer) attributes.get(Attributes.gridColumn.getName());
174       final Integer columnSpan = (Integer) attributes.get(Attributes.columnSpan.getName());
175       if (gridColumn != null) {
176         style.setGridColumn(GridSpan.valueOf(gridColumn, columnSpan));
177       }
178 
179       final Integer gridRow = (Integer) attributes.get(Attributes.gridRow.getName());
180       final Integer rowSpan = (Integer) attributes.get(Attributes.rowSpan.getName());
181       if (gridRow != null) {
182         style.setGridRow(GridSpan.valueOf(gridRow, rowSpan));
183       }
184     }
185 
186     return cells;
187   }
188 
189   /**
190    * Set the component to the cells grid.
191    * Also mark the span-area as occupied.
192    *
193    * @param initialCells The cells area
194    * @param column       Zero based column position
195    * @param row          Zero based row position
196    * @param component    Component to set
197    */
198   private UIComponent[][] set(
199       final UIComponent[][] initialCells, final Integer column, final Integer row, final UIComponent component,
200       final int initalRowsCount) {
201 
202     UIComponent[][] cells = initialCells;
203 
204     final Map<String, Object> attributes = component.getAttributes();
205     Integer rowSpan = (Integer) attributes.get(Attributes.rowSpan.getName());
206     if (rowSpan == null) {
207       rowSpan = 1;
208     }
209     Integer columnSpan = (Integer) attributes.get(Attributes.columnSpan.getName());
210     if (columnSpan == null) {
211       columnSpan = 1;
212     }
213 
214     // the span area
215     for (int j = row; j < rowSpan + row; j++) {
216       for (int i = column; i < columnSpan + column; i++) {
217         if (i >= cells[0].length) {
218           LOG.warn("column {} + columnSpan {} - 1 >= columnsCount {} in component '{}'!",
219               column + 1, columnSpan, cells[0].length, component.getClientId(FacesContext.getCurrentInstance()));
220           break;
221         }
222         if (j >= cells.length) {
223           cells = expand(cells, j + 1, initalRowsCount);
224         }
225         if (j == row && i == column) {
226           cells[j][i] = component;
227           attributes.put(Attributes.gridRow.getName(), j + 1);
228           attributes.put(Attributes.gridColumn.getName(), i + 1);
229         } else {
230           cells[j][i] = SPAN;
231         }
232       }
233     }
234 
235     return cells;
236   }
237 
238   protected UIComponent[][] expand(final UIComponent[][] cells, final Integer minRows, final int step) {
239     final int rows = (int) Math.ceil((double) minRows / step) * step;
240     final int columns = cells[0].length;
241 
242     final UIComponent[][] result = new UIComponent[rows][columns];
243     for (int j = 0; j < cells.length; j++) {
244       System.arraycopy(cells[j], 0, result[j], 0, columns);
245     }
246     return result;
247   }
248 
249 }