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