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.renderkit.html.util;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.Facets;
24  import org.apache.myfaces.tobago.component.UIForm;
25  import org.apache.myfaces.tobago.component.Visual;
26  import org.apache.myfaces.tobago.context.Markup;
27  import org.apache.myfaces.tobago.context.ResourceManagerUtils;
28  import org.apache.myfaces.tobago.internal.component.AbstractUICommand;
29  import org.apache.myfaces.tobago.internal.util.FacesContextUtils;
30  import org.apache.myfaces.tobago.internal.webapp.TobagoResponseWriterWrapper;
31  import org.apache.myfaces.tobago.renderkit.LabelWithAccessKey;
32  import org.apache.myfaces.tobago.renderkit.css.Classes;
33  import org.apache.myfaces.tobago.renderkit.css.FontAwesomeIconEncoder;
34  import org.apache.myfaces.tobago.renderkit.css.Style;
35  import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
36  import org.apache.myfaces.tobago.renderkit.html.Command;
37  import org.apache.myfaces.tobago.renderkit.html.CommandMap;
38  import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
39  import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
40  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
41  import org.apache.myfaces.tobago.renderkit.html.JsonUtils;
42  import org.apache.myfaces.tobago.renderkit.util.RenderUtils;
43  import org.apache.myfaces.tobago.util.ComponentUtils;
44  import org.apache.myfaces.tobago.util.FacetUtils;
45  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import javax.el.ELContext;
50  import javax.el.ValueExpression;
51  import javax.faces.component.UIComponent;
52  import javax.faces.component.UIInput;
53  import javax.faces.context.FacesContext;
54  import javax.faces.context.ResponseWriter;
55  import javax.faces.model.SelectItem;
56  import javax.faces.model.SelectItemGroup;
57  import java.io.IOException;
58  import java.util.Arrays;
59  import java.util.Locale;
60  import java.util.Map;
61  
62  public final class HtmlRendererUtils {
63  
64    private static final Logger LOG = LoggerFactory.getLogger(HtmlRendererUtils.class);
65    private static final String ERROR_FOCUS_KEY = HtmlRendererUtils.class.getName() + ".ErrorFocusId";
66    private static final String FOCUS_KEY = HtmlRendererUtils.class.getName() + ".FocusId";
67    public static final String CHAR_NON_BEAKING_SPACE = "\u00a0";
68  
69    private HtmlRendererUtils() {
70      // to prevent instantiation
71    }
72  
73    public static void renderFocus(
74        final String clientId, final boolean focus, final boolean error, final FacesContext facesContext,
75        final TobagoResponseWriter writer) throws IOException {
76      final Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
77      if (!requestMap.containsKey(FOCUS_KEY)
78          && (clientId.equals(FacesContextUtils.getFocusId(facesContext)) || focus || error)) {
79        requestMap.put(FOCUS_KEY, Boolean.TRUE);
80        writer.writeAttribute(HtmlAttributes.AUTOFOCUS, true);
81      }
82    }
83  
84    public static String getRendererName(final FacesContext facesContext, final UIComponent component) {
85      final String rendererType = component.getRendererType();
86      return rendererType.substring(0, 1).toLowerCase(Locale.ENGLISH) + rendererType.substring(1);
87    }
88  
89    public static void writeLabelWithAccessKey(final TobagoResponseWriter writer, final LabelWithAccessKey label)
90        throws IOException {
91      final int pos = label.getPos();
92      final String text = label.getLabel();
93      if (text != null) {
94        if (pos == -1) {
95          writer.writeText(text);
96        } else {
97          writer.writeText(text.substring(0, pos));
98          writer.startElement(HtmlElements.U);
99          writer.writeText(Character.toString(text.charAt(pos)));
100         writer.endElement(HtmlElements.U);
101         writer.writeText(text.substring(pos + 1));
102       }
103     }
104   }
105 
106   public static void encodeIconWithLabel(TobagoResponseWriter writer, String image, String label) throws IOException {
107     if (image != null && image.startsWith("fa-")) { // XXX hack: should be integrated in the resource manager
108       writer.writeIcon(null, FontAwesomeIconEncoder.generateClass(image)); // todo: should not be static
109     }
110     if (label != null) {
111       writer.startElement(HtmlElements.SPAN);
112       writer.writeText(label);
113       writer.endElement(HtmlElements.SPAN);
114     }
115   }
116 
117   public static void encodeIconWithLabel(
118        TobagoResponseWriter writer, FacesContext facesContext, String image, LabelWithAccessKey label, boolean disabled)
119       throws IOException {
120     if (image != null) {
121       if (image.startsWith("fa-")) {
122         writer.writeIcon(null, FontAwesomeIconEncoder.generateClass(image)); // todo: should not be static
123       } else {
124         if (ResourceManagerUtils.isAbsoluteResource(image)) {
125           // absolute Path to image: nothing to do
126         } else {
127           image = getImageWithPath(facesContext, image, disabled);
128         }
129         writer.startElement(HtmlElements.IMG);
130         writer.writeAttribute(HtmlAttributes.SRC, image, true);
131         writer.writeAttribute(HtmlAttributes.ALT, "", false);
132         writer.endElement(HtmlElements.IMG);
133       }
134     }
135 
136     if (label.getLabel() != null) {
137       writer.startElement(HtmlElements.SPAN);
138       HtmlRendererUtils.writeLabelWithAccessKey(writer, label);
139       writer.endElement(HtmlElements.SPAN);
140     }
141   }
142 
143   public static String getImageWithPath(final FacesContext facesContext, final String image, final boolean disabled) {
144     final int indexOfExtension = ResourceManagerUtils.indexOfExtension(image);
145     if (indexOfExtension == -1) {
146       return ResourceManagerUtils.getImageOrDisabledImage(facesContext, image, disabled);
147     } else {
148       return ResourceManagerUtils.getImageOrDisabledImageWithPath(facesContext, image, disabled);
149     }
150   }
151 
152   /**
153    * @deprecated since 3.0.0, use {@link org.apache.myfaces.tobago.renderkit.RendererBase#getResponseWriter}
154    */
155   public static TobagoResponseWriter getTobagoResponseWriter(final FacesContext facesContext) {
156 
157     final ResponseWriter writer = facesContext.getResponseWriter();
158     if (writer instanceof TobagoResponseWriter) {
159       return (TobagoResponseWriter) writer;
160     } else {
161       return new TobagoResponseWriterWrapper(writer);
162     }
163   }
164 
165   public static String getTitleFromTipAndMessages(final FacesContext facesContext, final UIComponent component) {
166     final String messages = ComponentUtils.getFacesMessageAsString(facesContext, component);
167     return HtmlRendererUtils.addTip(messages, ComponentUtils.getAttribute(component, Attributes.tip));
168   }
169 
170   public static String addTip(String title, final Object tip) {
171     if (tip != null) {
172       if (title != null && title.length() > 0) {
173         title += " :: ";
174       } else {
175         title = "";
176       }
177       title += tip;
178     }
179     return title;
180   }
181 
182   /**
183    * @deprecated Since Tobago 2.0.7
184    */
185   @Deprecated
186   public static void renderSelectItems(final UIInput component, final Iterable<SelectItem> items, final Object[] values,
187       final TobagoResponseWriter writer, final FacesContext facesContext) throws IOException {
188     renderSelectItems(component, items, values, null, null, writer, facesContext);
189   }
190 
191   public static void renderSelectItems(final UIInput component, final Iterable<SelectItem> items, final Object[] values,
192       final String[] submittedValues, final TobagoResponseWriter writer, final FacesContext facesContext)
193       throws IOException {
194     renderSelectItems(component, items, values, submittedValues, null, writer, facesContext);
195   }
196 
197   public static void renderSelectItems(final UIInput component, final Iterable<SelectItem> items, final Object value,
198       final String submittedValue, final TobagoResponseWriter writer, final FacesContext facesContext)
199       throws IOException {
200     renderSelectItems(component, items, value != null ? new Object[] {value}: null,
201         submittedValue != null ?  new String[] {submittedValue}: null, null, writer, facesContext);
202   }
203 
204   public static void renderSelectItems(final UIInput component, final Iterable<SelectItem> items, final Object[] values,
205       final String[] submittedValues, final Boolean onlySelected, final TobagoResponseWriter writer,
206       final FacesContext facesContext) throws IOException {
207 
208     if (LOG.isDebugEnabled()) {
209       LOG.debug("component id = '{}'", component.getId());
210       LOG.debug("values = '{}'", Arrays.toString(values));
211       LOG.debug("submittedValues = '{}'", Arrays.toString(submittedValues));
212     }
213     for (final SelectItem item : items) {
214       if (item instanceof SelectItemGroup) {
215         writer.startElement(HtmlElements.OPTGROUP);
216         writer.writeAttribute(HtmlAttributes.LABEL, item.getLabel(), true);
217         if (item.isDisabled()) {
218           writer.writeAttribute(HtmlAttributes.DISABLED, true);
219         }
220         final SelectItem[] selectItems = ((SelectItemGroup) item).getSelectItems();
221         renderSelectItems(component, Arrays.asList(selectItems), values, submittedValues,
222             onlySelected, writer, facesContext);
223         writer.endElement(HtmlElements.OPTGROUP);
224       } else {
225 
226         Object itemValue = item.getValue();
227         // when using selectItem tag with a literal value: use the converted value
228         if (itemValue instanceof String && values != null && values.length > 0 && !(values[0] instanceof String)) {
229           itemValue = ComponentUtils.getConvertedValue(facesContext, component, (String) itemValue);
230         }
231         final String formattedValue = ComponentUtils.getFormattedValue(facesContext, component, itemValue);
232         boolean contains;
233         if (submittedValues == null) {
234           contains = RenderUtils.contains(values, itemValue);
235         } else {
236           contains = RenderUtils.contains(submittedValues, formattedValue);
237         }
238         if (onlySelected != null) {
239           if (onlySelected) {
240             if (!contains) {
241               continue;
242             }
243           } else {
244             if (contains) {
245               continue;
246             }
247           }
248         }
249         writer.startElement(HtmlElements.OPTION);
250         writer.writeAttribute(HtmlAttributes.VALUE, formattedValue, true);
251         if (item instanceof org.apache.myfaces.tobago.model.SelectItem) {
252           final String image = ((org.apache.myfaces.tobago.model.SelectItem) item).getImage();
253           if (image != null) {
254             final Style style = new Style();
255             style.setBackgroundImage("url('"
256                 + ResourceManagerUtils.getImageOrDisabledImageWithPath(facesContext, image, item.isDisabled())
257                 + "')");
258             writer.writeStyleAttribute(style);
259           }
260         }
261         Markup markup = item instanceof Visual ? ((Visual) item).getMarkup() : Markup.NULL;
262         if (onlySelected == null && contains) {
263           writer.writeAttribute(HtmlAttributes.SELECTED, true);
264           markup = Markup.SELECTED.add(markup);
265         }
266         if (item.isDisabled()) {
267           writer.writeAttribute(HtmlAttributes.DISABLED, true);
268           markup = Markup.DISABLED.add(markup);
269         }
270         writer.writeClassAttribute(Classes.create(component, "option", markup));
271 
272         writer.writeText(item.getLabel());
273         writer.endElement(HtmlElements.OPTION);
274       }
275     }
276   }
277 
278   /**
279    * @deprecated Use client behaviour instead.
280    */
281   @Deprecated
282   public static void renderCommandFacet(
283       final UIComponent component, final FacesContext facesContext, final TobagoResponseWriter writer)
284       throws IOException {
285     renderCommandFacet(component, component.getClientId(facesContext), facesContext, writer);
286   }
287 
288   /**
289    * @deprecated Use client behaviour instead.
290    */
291   @Deprecated
292   public static void renderCommandFacet(
293       final UIComponent component, final String id, final FacesContext facesContext, final TobagoResponseWriter writer)
294       throws IOException {
295     if (ComponentUtils.getBooleanAttribute(component, Attributes.readonly)
296         || ComponentUtils.getBooleanAttribute(component, Attributes.disabled)) {
297       return;
298     }
299     CommandMap commandMap = null;
300     final Map<String, UIComponent> facets = component.getFacets();
301     for (final Map.Entry<String, UIComponent> entry : facets.entrySet()) {
302       final UIComponent facetComponent = entry.getValue();
303       final String key = entry.getKey();
304       if (facetComponent.isRendered()
305           && (facetComponent instanceof AbstractUICommand || facetComponent instanceof UIForm)
306           && Facets.isEvent(key)) {
307         if (commandMap == null) {
308           commandMap = new CommandMap();
309         }
310         commandMap.addCommand(key, new Command(facesContext, entry.getValue(), id));
311       }
312     }
313     if (commandMap != null) {
314       writer.writeAttribute(DataAttributes.COMMANDS, JsonUtils.encode(commandMap), true);
315     }
316   }
317 
318   public static void encodeContextMenu(
319       final FacesContext facesContext, final TobagoResponseWriter writer, final UIComponent parent)
320       throws IOException {
321     final UIComponent contextMenu = FacetUtils.getContextMenu(parent);
322     if (contextMenu != null) {
323       writer.startElement(HtmlElements.OL);
324       writer.writeClassAttribute(TobagoClass.MENU_BAR, TobagoClass.MENU__CONTEXT_MENU);
325       RenderUtils.encode(facesContext, contextMenu);
326       writer.endElement(HtmlElements.OL);
327     }
328   }
329 
330   public static void writeDataAttributes(
331       final FacesContext context, final TobagoResponseWriter writer, final UIComponent component)
332       throws IOException {
333 
334     final Map<Object, Object> dataAttributes = ComponentUtils.getDataAttributes(component);
335     if (dataAttributes == null) {
336       return;
337     }
338 
339     final ELContext elContext = context.getELContext();
340 
341     for (final Map.Entry<Object, Object> entry : dataAttributes.entrySet()) {
342       final Object mapKey = entry.getKey();
343       final String name = mapKey instanceof ValueExpression
344           ? ((ValueExpression) mapKey).getValue(elContext).toString() : mapKey.toString();
345       final Object mapValue = entry.getValue();
346       final String value = mapValue instanceof ValueExpression
347           ? ((ValueExpression) mapValue).getValue(elContext).toString() : mapValue.toString();
348       writer.writeAttribute(DataAttributes.dynamic(name), value, true);
349     }
350   }
351 }