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.renderkit.renderer;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.LabelLayout;
24  import org.apache.myfaces.tobago.component.SupportsAccessKey;
25  import org.apache.myfaces.tobago.component.SupportsLabelLayout;
26  import org.apache.myfaces.tobago.context.Markup;
27  import org.apache.myfaces.tobago.internal.component.AbstractUIStyle;
28  import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
29  import org.apache.myfaces.tobago.internal.util.JsonUtils;
30  import org.apache.myfaces.tobago.internal.util.StringUtils;
31  import org.apache.myfaces.tobago.renderkit.LabelWithAccessKey;
32  import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
33  import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
34  import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
35  import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
36  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
37  import org.apache.myfaces.tobago.util.ComponentUtils;
38  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
39  
40  import javax.faces.component.UIComponent;
41  import javax.faces.context.FacesContext;
42  import java.io.IOException;
43  import java.util.List;
44  
45  /**
46   * Manages the rendering of the <b>label</b> and the <b>field</b> together with different possibilities for
47   * the position of the label (defined by {@link org.apache.myfaces.tobago.component.Attributes#labelLayout}
48   */
49  public abstract class LabelLayoutRendererBase extends DecodingInputRendererBase {
50  
51    @Override
52    public void encodeBegin(final FacesContext facesContext, final UIComponent component) throws IOException {
53  
54      encodeBeginSurroundingLabel(facesContext, component);
55  
56      switch (getType(component)) {
57        case segmentLeft:
58          if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentRight) {
59            encodeBeginMessageField(facesContext, component);
60          }
61          break;
62        case segmentRight:
63          if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentLeft) {
64            encodeBeginMessageField(facesContext, component);
65          }
66          break;
67        default:
68          encodeBeginMessageField(facesContext, component);
69          break;
70      }
71    }
72  
73    @Override
74    public void encodeEnd(final FacesContext facesContext, final UIComponent component) throws IOException {
75  
76      switch (getType(component)) {
77        case segmentLeft:
78          if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentRight) {
79            encodeEndMessageField(facesContext, component);
80          }
81          break;
82        case segmentRight:
83          if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentLeft) {
84            encodeEndMessageField(facesContext, component);
85          }
86          break;
87        default:
88          encodeEndMessageField(facesContext, component);
89          break;
90      }
91  
92      // render the styles here, because inside of <select> its not possible.
93      if (component.getRendersChildren()) {
94        final List<AbstractUIStyle> children = ComponentUtils.findDescendantList(component, AbstractUIStyle.class);
95        for (final AbstractUIStyle child : children) {
96          child.encodeAll(facesContext);
97        }
98      }
99  
100     encodeEndSurroundingLabel(facesContext, component);
101   }
102 
103   @Override
104   public void encodeChildren(final FacesContext context, final UIComponent component) throws IOException {
105     if (component.getChildCount() > 0) {
106       for (int i = 0, childCount = component.getChildCount(); i < childCount; i++) {
107         final UIComponent child = component.getChildren().get(i);
108         if (!child.isRendered()) {
109           continue;
110         }
111         if (child instanceof AbstractUIStyle) {
112           // will be rendered in {@link encodeEnd}
113           continue;
114         }
115 
116         child.encodeAll(context);
117       }
118     }
119   }
120 
121   protected abstract void encodeBeginMessageField(FacesContext facesContext, UIComponent component) throws IOException;
122 
123   protected abstract void encodeEndMessageField(FacesContext facesContext, UIComponent component) throws IOException;
124 
125   protected void encodeBeginSurroundingLabel(final FacesContext facesContext, final UIComponent component)
126       throws IOException {
127 
128     final TobagoResponseWriter writer = getResponseWriter(facesContext);
129     String clientId = component.getClientId(facesContext);
130     final Markup markup = (Markup) ComponentUtils.getAttribute(component, Attributes.markup);
131 
132     // possible values:
133     // - none
134     // - flexLeft (default)
135     // - flexRight
136     // - top
137     // - segmentLeft (todo)
138     // - segmentRight (todo)
139     // - flowLeft (todo)
140     // - flowRight (todo)
141     // - skip
142     final LabelLayout labelLayout = getType(component);
143     final boolean flex;
144     switch (labelLayout) {
145       case skip:
146         return;
147       case flexLeft:
148       case flexRight:
149         flex = true;
150         break;
151       case segmentLeft:
152         if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentLeft) {
153           clientId += ComponentUtils.SUB_SEPARATOR + "label";
154         }
155         flex = false;
156         break;
157       case segmentRight:
158         if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentRight) {
159           clientId += ComponentUtils.SUB_SEPARATOR + "label";
160         }
161         flex = false;
162         break;
163       case none:
164       case top:
165       case flowLeft:
166       case flowRight:
167       default: // none, top, segmentLeft, segmentRight, flowLeft, flowRight
168         flex = false;
169     }
170 
171     writer.startElement(HtmlElements.DIV);
172     writer.writeIdAttribute(clientId);
173     writer.writeAttribute(DataAttributes.MARKUP, JsonUtils.encode(markup), false);
174 
175     writer.writeClassAttribute(
176         flex ? TobagoClass.FLEX_LAYOUT : null,
177         flex ? BootstrapClass.D_FLEX : null,
178         TobagoClass.LABEL__CONTAINER,
179         BootstrapClass.FORM_GROUP,
180         ComponentUtils.getBooleanAttribute(component, Attributes.required) ? TobagoClass.REQUIRED : null,
181         markup != null && markup.contains(Markup.SPREAD) ? TobagoClass.SPREAD : null);
182 
183     switch (labelLayout) {
184       case none:
185         break;
186       case flexRight:
187       case flowRight:
188         break;
189       case segmentLeft:
190         if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentLeft) {
191           encodeLabel(facesContext, component, writer, labelLayout);
192         }
193         break;
194       case segmentRight:
195         if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentRight) {
196           encodeLabel(facesContext, component, writer, labelLayout);
197         }
198         break;
199       default:
200         encodeLabel(facesContext, component, writer, labelLayout);
201     }
202   }
203 
204   protected void encodeEndSurroundingLabel(final FacesContext facesContext, final UIComponent component)
205       throws IOException {
206 
207     final TobagoResponseWriter writer = getResponseWriter(facesContext);
208     final LabelLayout labelLayout = getType(component);
209 
210     switch (labelLayout) {
211       case skip:
212         return;
213       case none:
214         break;
215       case flexRight:
216       case flowRight:
217         encodeLabel(facesContext, component, writer, labelLayout);
218         break;
219       default:
220         // nothing to do
221     }
222 
223     writer.endElement(HtmlElements.DIV);
224   }
225 
226   protected void encodeLabel(final FacesContext facesContext, final UIComponent component,
227       final TobagoResponseWriter writer, final LabelLayout labelLayout)
228       throws IOException {
229     // TBD: maybe use an interface for getLabel()
230     final String label = ComponentUtils.getStringAttribute(component, Attributes.label);
231     if (StringUtils.isNotBlank(label)) {
232       writer.startElement(HtmlElements.LABEL);
233       writer.writeAttribute(HtmlAttributes.FOR, getFieldId(facesContext, component), false);
234       writer.writeClassAttribute(TobagoClass.LABEL, BootstrapClass.COL_FORM_LABEL);
235       if (component instanceof SupportsAccessKey) {
236         final LabelWithAccessKey labelWithAccessKey = new LabelWithAccessKey((SupportsAccessKey) component);
237         HtmlRendererUtils.writeLabelWithAccessKey(writer, labelWithAccessKey);
238       } else {
239         writer.writeText(label);
240       }
241       writer.endElement(HtmlElements.LABEL);
242     }
243   }
244 
245   private LabelLayout getType(final UIComponent component) {
246     return component instanceof SupportsLabelLayout
247         ? ((SupportsLabelLayout) component).getLabelLayout()
248         : LabelLayout.skip;
249   }
250 
251   protected abstract String getFieldId(final FacesContext facesContext, final UIComponent component);
252 }