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