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 
182     switch (labelLayout) {
183       case none:
184         break;
185       case flexRight:
186       case flowRight:
187         break;
188       case segmentLeft:
189         if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentLeft) {
190           encodeLabel(facesContext, component, writer, labelLayout);
191         }
192         break;
193       case segmentRight:
194         if (LabelLayout.getSegment(facesContext) == LabelLayout.segmentRight) {
195           encodeLabel(facesContext, component, writer, labelLayout);
196         }
197         break;
198       default:
199         encodeLabel(facesContext, component, writer, labelLayout);
200     }
201   }
202 
203   protected void encodeEndSurroundingLabel(final FacesContext facesContext, final UIComponent component)
204       throws IOException {
205 
206     final TobagoResponseWriter writer = getResponseWriter(facesContext);
207     final LabelLayout labelLayout = getType(component);
208 
209     switch (labelLayout) {
210       case skip:
211         return;
212       case none:
213         break;
214       case flexRight:
215       case flowRight:
216         encodeLabel(facesContext, component, writer, labelLayout);
217         break;
218       default:
219         // nothing to do
220     }
221 
222     writer.endElement(HtmlElements.DIV);
223   }
224 
225   protected void encodeLabel(final FacesContext facesContext, final UIComponent component,
226       final TobagoResponseWriter writer, final LabelLayout labelLayout)
227       throws IOException {
228     // TBD: maybe use an interface for getLabel()
229     final String label = ComponentUtils.getStringAttribute(component, Attributes.label);
230     if (StringUtils.isNotBlank(label)) {
231       writer.startElement(HtmlElements.LABEL);
232       writer.writeAttribute(HtmlAttributes.FOR, getFieldId(facesContext, component), false);
233       writer.writeClassAttribute(TobagoClass.LABEL, BootstrapClass.COL_FORM_LABEL);
234       if (component instanceof SupportsAccessKey) {
235         final LabelWithAccessKey labelWithAccessKey = new LabelWithAccessKey((SupportsAccessKey) component);
236         HtmlRendererUtils.writeLabelWithAccessKey(writer, labelWithAccessKey);
237       } else {
238         writer.writeText(label);
239       }
240       writer.endElement(HtmlElements.LABEL);
241     }
242   }
243 
244   private LabelLayout getType(final UIComponent component) {
245     return component instanceof SupportsLabelLayout
246         ? ((SupportsLabelLayout) component).getLabelLayout()
247         : LabelLayout.skip;
248   }
249 
250   protected abstract String getFieldId(final FacesContext facesContext, final UIComponent component);
251 }