View Javadoc
1   /**
2    * Copyright 2011-2019 PrimeFaces Extensions
3    *
4    * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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.primefaces.extensions.component.dynaform;
17  
18  import java.io.IOException;
19  import java.util.Collections;
20  import java.util.List;
21  import java.util.logging.Logger;
22  
23  import javax.faces.component.ContextCallback;
24  import javax.faces.component.EditableValueHolder;
25  import javax.faces.component.UIComponent;
26  import javax.faces.context.FacesContext;
27  import javax.faces.context.ResponseWriter;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.primefaces.component.api.InputHolder;
31  import org.primefaces.component.row.Row;
32  import org.primefaces.extensions.model.dynaform.AbstractDynaFormElement;
33  import org.primefaces.extensions.model.dynaform.DynaFormControl;
34  import org.primefaces.extensions.model.dynaform.DynaFormLabel;
35  import org.primefaces.extensions.model.dynaform.DynaFormModel;
36  import org.primefaces.extensions.model.dynaform.DynaFormModelElement;
37  import org.primefaces.extensions.model.dynaform.DynaFormRow;
38  import org.primefaces.renderkit.CoreRenderer;
39  import org.primefaces.util.ComponentUtils;
40  import org.primefaces.util.CompositeUtils;
41  import org.primefaces.util.Constants;
42  import org.primefaces.util.WidgetBuilder;
43  
44  /**
45   * Renderer for {@link DynaForm} component.
46   *
47   * @author Oleg Varaksin / last modified by $Author$
48   * @version $Revision$
49   * @since 0.5
50   */
51  public class DynaFormRenderer extends CoreRenderer {
52  
53      public static final String FACET_HEADER_REGULAR = "headerRegular";
54      public static final String FACET_FOOTER_REGULAR = "footerRegular";
55      public static final String FACET_HEADER_EXTENDED = "headerExtended";
56      public static final String FACET_FOOTER_EXTENDED = "footerExtended";
57      public static final String FACET_BUTTON_BAR = "buttonBar";
58      public static final String FACET_STATIC_TOP = "staticTop";
59      public static final String FACET_STATIC_BOTTOM = "staticBottom";
60  
61      public static final String GRID_CLASS = "pe-dynaform-grid";
62      public static final String NESTED_GRID_CLASS = "pe-dynaform-nested-grid";
63      public static final String CELL_CLASS = "pe-dynaform-cell";
64      public static final String CELL_FIRST_CLASS = "pe-dynaform-cell-first";
65      public static final String CELL_LAST_CLASS = "pe-dynaform-cell-last";
66      public static final String LABEL_CLASS = "pe-dynaform-label";
67      public static final String LABEL_INVALID_CLASS = "ui-state-error ui-corner-all";
68      public static final String LABEL_INDICATOR_CLASS = "pe-dynaform-label-rfi";
69      public static final String LABEL_CONTROL_TYPE_CLASS_FORMAT = "pe-dynaform-%s-label";
70  
71      public static final String FACET_BUTTON_BAR_TOP_CLASS = "pe-dynaform-buttonbar-top";
72      public static final String FACET_BUTTON_BAR_BOTTOM_CLASS = "pe-dynaform-buttonbar-bottom";
73      public static final String FACET_HEADER_CLASS = "pe-dynaform-headerfacet";
74      public static final String FACET_FOOTER_CLASS = "pe-dynaform-footerfacet";
75      public static final String FACET_STATIC_TOP_CLASS = "pe-dynaform-static-top";
76      public static final String FACET_STATIC_BOTTOM_CLASS = "pe-dynaform-static-bottom";
77      public static final String EXTENDED_ROW_CLASS = "pe-dynaform-extendedrow";
78  
79      public static final String BUTTON_BAR_ROLE = "toolbar";
80      public static final String GRID_CELL_ROLE = "gridcell";
81  
82      protected static final String[] EMPTY_COLUMN_CLASSES = new String[] {Constants.EMPTY_STRING, Constants.EMPTY_STRING};
83  
84      private static final Logger LOGGER = Logger.getLogger(DynaFormRenderer.class.getName());
85  
86      @Override
87      public void encodeEnd(final FacesContext fc, final UIComponent component) throws IOException {
88          final DynaForm dynaForm = (DynaForm) component;
89  
90          // get model
91          final DynaFormModel/org/primefaces/extensions/model/dynaform/DynaFormModel.html#DynaFormModel">DynaFormModel dynaFormModel = (DynaFormModel) dynaForm.getValue();
92          encodeMarkup(fc, dynaForm, dynaFormModel, false);
93          encodeScript(fc, dynaForm, dynaFormModel);
94      }
95  
96      protected void encodeMarkup(final FacesContext fc, final DynaForm dynaForm, final DynaFormModel dynaFormModel, final boolean nestedGrid)
97                  throws IOException {
98          final ResponseWriter writer = fc.getResponseWriter();
99  
100         writer.startElement("table", dynaForm);
101 
102         if (!nestedGrid) {
103             final String clientId = dynaForm.getClientId(fc);
104             writer.writeAttribute("id", clientId, "id");
105         }
106 
107         String styleClass = nestedGrid ? NESTED_GRID_CLASS : GRID_CLASS;
108         styleClass += dynaForm.getStyleClass() == null ? Constants.EMPTY_STRING : " " + dynaForm.getStyleClass();
109 
110         writer.writeAttribute("cellspacing", "0", "cellspacing");
111         writer.writeAttribute("class", styleClass, "styleClass");
112         if (dynaForm.getStyle() != null) {
113             writer.writeAttribute("style", dynaForm.getStyle(), "style");
114         }
115 
116         writer.writeAttribute("role", "grid", null);
117 
118         // prepare labels with informations about corresponding control components
119         preRenderLabel(fc, dynaForm, dynaFormModel);
120 
121         final int totalColspan = getTotalColspan(dynaFormModel);
122         final String bbPosition = dynaForm.getButtonBarPosition();
123 
124         if (!nestedGrid) {
125             if ("top".equals(bbPosition) || "both".equals(bbPosition)) {
126                 encodeFacet(fc, dynaForm, FACET_BUTTON_BAR, totalColspan, FACET_BUTTON_BAR_TOP_CLASS, BUTTON_BAR_ROLE, false, true);
127             }
128 
129             encodeFacet(fc, dynaForm, FACET_HEADER_REGULAR, totalColspan, FACET_HEADER_CLASS, GRID_CELL_ROLE, false, true);
130             encodeStatic(fc, dynaForm, FACET_STATIC_TOP, totalColspan, FACET_STATIC_TOP_CLASS);
131         }
132 
133         // encode regular grid
134         encodeBody(fc, dynaForm, dynaFormModel.getRegularRows(), false, true);
135 
136         if (!nestedGrid) {
137             encodeFacet(fc, dynaForm, FACET_FOOTER_REGULAR, totalColspan, FACET_FOOTER_CLASS, GRID_CELL_ROLE, false, true);
138             encodeFacet(fc, dynaForm, FACET_HEADER_EXTENDED, totalColspan, FACET_HEADER_CLASS, GRID_CELL_ROLE, true,
139                         dynaForm.isOpenExtended());
140         }
141 
142         // encode extended grid
143         encodeBody(fc, dynaForm, dynaFormModel.getExtendedRows(), true, dynaForm.isOpenExtended());
144 
145         if (!nestedGrid) {
146             encodeStatic(fc, dynaForm, FACET_STATIC_BOTTOM, totalColspan, FACET_STATIC_BOTTOM_CLASS);
147             encodeFacet(fc, dynaForm, FACET_FOOTER_EXTENDED, totalColspan, FACET_FOOTER_CLASS, GRID_CELL_ROLE, true, dynaForm.isOpenExtended());
148 
149             if ("bottom".equals(bbPosition) || "both".equals(bbPosition)) {
150                 encodeFacet(fc, dynaForm, FACET_BUTTON_BAR, totalColspan, FACET_BUTTON_BAR_BOTTOM_CLASS, BUTTON_BAR_ROLE, false, true);
151             }
152         }
153 
154         writer.endElement("table");
155     }
156 
157     protected void encodeScript(final FacesContext fc, final DynaForm dynaForm, final DynaFormModel dynaFormModel) throws IOException {
158         final WidgetBuilder wb = getWidgetBuilder(fc);
159         final String clientId = dynaForm.getClientId(fc);
160         wb.init("ExtDynaForm", dynaForm.resolveWidgetVar(), clientId);
161         wb.attr("uuid", dynaFormModel.getUuid());
162         wb.attr("autoSubmit", dynaForm.isAutoSubmit());
163         wb.attr("isPostback", fc.isPostback());
164         wb.finish();
165     }
166 
167     protected void encodeFacet(final FacesContext fc, final DynaForm dynaForm, final String name, final int totalColspan, final String styleClass,
168                 final String role,
169                 final boolean extended, final boolean visible) throws IOException {
170         final UIComponent facet = dynaForm.getFacet(name);
171         if (ComponentUtils.shouldRenderFacet(facet)) {
172             final ResponseWriter writer = fc.getResponseWriter();
173             writer.startElement("tr", null);
174             if (extended) {
175                 writer.writeAttribute("class", EXTENDED_ROW_CLASS, null);
176             }
177 
178             if (!visible) {
179                 writer.writeAttribute("style", "display:none;", null);
180             }
181 
182             writer.writeAttribute("role", "row", null);
183             writer.startElement("td", null);
184             if (totalColspan > 1) {
185                 writer.writeAttribute("colspan", totalColspan, null);
186             }
187 
188             writer.writeAttribute("class", styleClass, null);
189             writer.writeAttribute("role", role, null);
190 
191             facet.encodeAll(fc);
192 
193             writer.endElement("td");
194             writer.endElement("tr");
195         }
196     }
197 
198     protected void encodeBody(final FacesContext fc, final DynaForm dynaForm, final List<DynaFormRow> dynaFormRows, final boolean extended,
199                 final boolean visible) throws IOException {
200         if (dynaFormRows == null || dynaFormRows.isEmpty()) {
201             return;
202         }
203 
204         final ResponseWriter writer = fc.getResponseWriter();
205 
206         final String columnClassesValue = dynaForm.getColumnClasses();
207         final String[] columnClasses = columnClassesValue == null ? EMPTY_COLUMN_CLASSES : columnClassesValue.split(",");
208         final String labelCommonClass = columnClasses[0].trim();
209         final String controlCommonClass = columnClasses.length > 1 ? columnClasses[1].trim() : EMPTY_COLUMN_CLASSES[1];
210 
211         for (final DynaFormRow dynaFormRow : dynaFormRows) {
212             writer.startElement("tr", null);
213             if (extended) {
214                 writer.writeAttribute("class", EXTENDED_ROW_CLASS, null);
215             }
216 
217             if (!visible) {
218                 writer.writeAttribute("style", "display:none;", null);
219             }
220 
221             writer.writeAttribute("role", "row", null);
222 
223             final List<AbstractDynaFormElement> elements = dynaFormRow.getElements();
224             final int size = elements.size();
225 
226             for (int i = 0; i < size; i++) {
227                 final AbstractDynaFormElement element = elements.get(i);
228 
229                 writer.startElement("td", null);
230                 if (element.getColspan() > 1) {
231                     writer.writeAttribute("colspan", element.getColspan(), null);
232                 }
233 
234                 if (element.getRowspan() > 1) {
235                     writer.writeAttribute("rowspan", element.getRowspan(), null);
236                 }
237 
238                 String styleClass = CELL_CLASS;
239                 if (i == 0 && element.getColspan() == 1) {
240                     styleClass = styleClass + " " + CELL_FIRST_CLASS;
241                 }
242 
243                 if (i == size - 1 && element.getColspan() == 1) {
244                     styleClass = styleClass + " " + CELL_LAST_CLASS;
245                 }
246 
247                 if (element instanceof DynaFormLabel) {
248                     renderLabel(writer, labelCommonClass, (DynaFormLabel) element, styleClass);
249                 }
250                 else if (element instanceof DynaFormControl) {
251                     renderControl(fc, dynaForm, writer, controlCommonClass, (DynaFormControl) element, styleClass);
252                 }
253                 else if (element instanceof DynaFormModelElement) {
254                     renderNestedModel(fc, dynaForm, writer, (DynaFormModelElement) element, styleClass);
255                 }
256 
257                 writer.endElement("td");
258             }
259 
260             writer.endElement("tr");
261         }
262 
263         dynaForm.resetData();
264     }
265 
266     protected void renderNestedModel(FacesContext fc, DynaForm dynaForm, ResponseWriter writer, DynaFormModelElement element, String styleClass)
267                 throws IOException {
268         final DynaFormModelElement nestedModel = element;
269 
270         writer.writeAttribute("class", styleClass, null);
271         writer.writeAttribute("role", GRID_CELL_ROLE, null);
272 
273         encodeMarkup(fc, dynaForm, nestedModel.getModel(), true);
274     }
275 
276     protected void renderControl(FacesContext fc, DynaForm dynaForm, ResponseWriter writer, String controlCommonClass, DynaFormControl element,
277                 String styleClass) throws IOException {
278         final DynaFormControl control = element;
279         dynaForm.setData(control);
280 
281         // find control's cell by type
282         final UIDynaFormControl cell = dynaForm.getControlCell(control.getType());
283 
284         if (cell.getStyle() != null) {
285             writer.writeAttribute("style", cell.getStyle(), null);
286         }
287 
288         if (cell.getStyleClass() != null) {
289             styleClass = styleClass + " " + cell.getStyleClass();
290         }
291 
292         writer.writeAttribute("class", (styleClass + " " + controlCommonClass).trim(), null);
293         writer.writeAttribute("role", GRID_CELL_ROLE, null);
294 
295         cell.encodeAll(fc);
296     }
297 
298     protected void renderLabel(ResponseWriter writer, String labelCommonClass, DynaFormLabel element, String styleClass) throws IOException {
299         final DynaFormLabel label = element;
300 
301         writer.writeAttribute("class", (styleClass
302                     + " " + LABEL_CLASS
303                     + " " + StringUtils.defaultIfBlank(label.getStyleClass(), Constants.EMPTY_STRING)
304                     + " " + labelCommonClass).trim(), null);
305         writer.writeAttribute("role", GRID_CELL_ROLE, null);
306 
307         writer.startElement("label", null);
308         if (!label.isTargetValid()) {
309             writer.writeAttribute("class", LABEL_INVALID_CLASS, null);
310         }
311 
312         writer.writeAttribute("for", label.getTargetClientId(), null);
313 
314         if (label.getValue() != null) {
315             if (label.isEscape()) {
316                 writer.writeText(label.getValue(), "value");
317             }
318             else {
319                 writer.write(label.getValue());
320             }
321         }
322 
323         if (label.isTargetRequired()) {
324             writer.startElement("span", null);
325             writer.writeAttribute("class", LABEL_INDICATOR_CLASS, null);
326             writer.write("*");
327             writer.endElement("span");
328         }
329 
330         writer.endElement("label");
331     }
332 
333     protected void encodeStatic(final FacesContext fc, final DynaForm dynaForm, final String name, final int totalColspan, final String styleClass)
334                 throws IOException {
335         final UIComponent facet = dynaForm.getFacet(name);
336         if (facet == null || !facet.isRendered()) {
337             return;
338         }
339 
340         final List<UIComponent> components = facet instanceof Row
341                     ? Collections.singletonList(facet)
342                     : facet.getChildren();
343 
344         final ResponseWriter writer = fc.getResponseWriter();
345         for (final UIComponent child : components) {
346             if (!child.isRendered() || !(child instanceof Row)) {
347                 continue;
348             }
349 
350             writer.startElement("tr", null);
351             if (shouldWriteId(child)) {
352                 writer.writeAttribute("id", child.getClientId(fc), null);
353             }
354             writer.writeAttribute("role", "row", null);
355             writer.writeAttribute("class", styleClass, null);
356 
357             int i = 0;
358             for (final UIComponent column : child.getChildren()) {
359                 if (!column.isRendered()) {
360                     continue;
361                 }
362 
363                 String columnClass = CELL_CLASS;
364                 if (i % totalColspan == 0) {
365                     columnClass = columnClass + " " + CELL_FIRST_CLASS;
366                 }
367 
368                 if ((i + 1) % totalColspan == 0) {
369                     columnClass = columnClass + " " + CELL_LAST_CLASS;
370                 }
371 
372                 writer.startElement("td", null);
373                 writer.writeAttribute("role", GRID_CELL_ROLE, null);
374                 writer.writeAttribute("class", columnClass, null);
375                 column.encodeAll(fc);
376                 writer.endElement("td");
377                 i++;
378             }
379 
380             writer.endElement("tr");
381         }
382     }
383 
384     protected void preRenderLabel(final FacesContext fc, final DynaForm dynaForm, final DynaFormModel model) {
385         for (final DynaFormLabel label : model.getLabels()) {
386             // get corresponding control if any
387             final DynaFormControl control = label.getForControl();
388             if (control != null) {
389                 // find control's cell by type
390                 final UIDynaFormControl cell = dynaForm.getControlCell(control.getType());
391 
392                 if (cell.getFor() != null) {
393                     dynaForm.setData(control);
394 
395                     final UIComponent target = cell.findComponent(cell.getFor());
396                     if (target == null) {
397                         LOGGER.warning("Cannot find component with identifier " + cell.getFor() + " inside UIDynaFormControl");
398 
399                         continue;
400                     }
401 
402                     final String targetClientId = target instanceof InputHolder
403                                 ? ((InputHolder) target).getInputClientId()
404                                 : target.getClientId(fc);
405                     label.setTargetClientId(targetClientId);
406 
407                     final ContextCallback callback = new ContextCallback() {
408 
409                         @Override
410                         public void invokeContextCallback(final FacesContext context, final UIComponent target) {
411                             label.setTargetValid(((EditableValueHolder) target).isValid());
412                             label.setTargetRequired(((EditableValueHolder) target).isRequired());
413                         }
414                     };
415 
416                     if (CompositeUtils.isComposite(target)) {
417                         CompositeUtils.invokeOnDeepestEditableValueHolder(fc, target, callback);
418                     }
419                     else {
420                         callback.invokeContextCallback(fc, target);
421                     }
422 
423                     if (label.getValue() != null) {
424                         target.getAttributes().put("label", label.getValue());
425                     }
426 
427                     label.setStyleClass(String.format(LABEL_CONTROL_TYPE_CLASS_FORMAT, control.getType().toLowerCase()));
428                 }
429             }
430         }
431 
432         dynaForm.resetData();
433     }
434 
435     protected int getTotalColspan(final DynaFormModel dynaFormModel) {
436         // the main aim of this method is layout check
437         int totalColspan = -1;
438         for (final DynaFormRow dynaFormRow : dynaFormModel.getRegularRows()) {
439             if (dynaFormRow.getTotalColspan() > totalColspan) {
440                 totalColspan = dynaFormRow.getTotalColspan();
441             }
442         }
443 
444         if (dynaFormModel.getExtendedRows() != null) {
445             for (final DynaFormRow dynaFormRow : dynaFormModel.getExtendedRows()) {
446                 if (dynaFormRow.getTotalColspan() > totalColspan) {
447                     totalColspan = dynaFormRow.getTotalColspan();
448                 }
449             }
450         }
451 
452         if (totalColspan < 1) {
453             totalColspan = 1;
454         }
455 
456         return totalColspan;
457     }
458 
459     @Override
460     public void encodeChildren(final FacesContext context, final UIComponent component) throws IOException {
461         // Rendering happens on encodeEnd
462     }
463 
464     @Override
465     public boolean getRendersChildren() {
466         return true;
467     }
468 }