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   public static TobagoResponseWriter getTobagoResponseWriter(final FacesContext facesContext) {
152 
153     final ResponseWriter writer = facesContext.getResponseWriter();
154     if (writer instanceof TobagoResponseWriter) {
155       return (TobagoResponseWriter) writer;
156     } else {
157       return new TobagoResponseWriterWrapper(writer);
158     }
159   }
160 
161   public static String getTitleFromTipAndMessages(final FacesContext facesContext, final UIComponent component) {
162     final String messages = ComponentUtils.getFacesMessageAsString(facesContext, component);
163     return HtmlRendererUtils.addTip(messages, ComponentUtils.getAttribute(component, Attributes.tip));
164   }
165 
166   public static String addTip(String title, final Object tip) {
167     if (tip != null) {
168       if (title != null && title.length() > 0) {
169         title += " :: ";
170       } else {
171         title = "";
172       }
173       title += tip;
174     }
175     return title;
176   }
177 
178   /**
179    * @deprecated Since Tobago 2.0.7
180    */
181   @Deprecated
182   public static void renderSelectItems(final UIInput component, final Iterable<SelectItem> items, final Object[] values,
183       final TobagoResponseWriter writer, final FacesContext facesContext) throws IOException {
184     renderSelectItems(component, items, values, null, null, writer, facesContext);
185   }
186 
187   public static void renderSelectItems(final UIInput component, final Iterable<SelectItem> items, final Object[] values,
188       final String[] submittedValues, final TobagoResponseWriter writer, final FacesContext facesContext)
189       throws IOException {
190     renderSelectItems(component, items, values, submittedValues, null, writer, facesContext);
191   }
192 
193   public static void renderSelectItems(final UIInput component, final Iterable<SelectItem> items, final Object value,
194       final String submittedValue, final TobagoResponseWriter writer, final FacesContext facesContext)
195       throws IOException {
196     renderSelectItems(component, items, value != null ? new Object[] {value}: null,
197         submittedValue != null ?  new String[] {submittedValue}: null, null, writer, facesContext);
198   }
199 
200   public static void renderSelectItems(final UIInput component, final Iterable<SelectItem> items, final Object[] values,
201       final String[] submittedValues, final Boolean onlySelected, final TobagoResponseWriter writer,
202       final FacesContext facesContext) throws IOException {
203 
204     if (LOG.isDebugEnabled()) {
205       LOG.debug("component id = '{}'", component.getId());
206       LOG.debug("values = '{}'", Arrays.toString(values));
207       LOG.debug("submittedValues = '{}'", Arrays.toString(submittedValues));
208     }
209     for (final SelectItem item : items) {
210       if (item instanceof SelectItemGroup) {
211         writer.startElement(HtmlElements.OPTGROUP);
212         writer.writeAttribute(HtmlAttributes.LABEL, item.getLabel(), true);
213         if (item.isDisabled()) {
214           writer.writeAttribute(HtmlAttributes.DISABLED, true);
215         }
216         final SelectItem[] selectItems = ((SelectItemGroup) item).getSelectItems();
217         renderSelectItems(component, Arrays.asList(selectItems), values, submittedValues,
218             onlySelected, writer, facesContext);
219         writer.endElement(HtmlElements.OPTGROUP);
220       } else {
221 
222         Object itemValue = item.getValue();
223         // when using selectItem tag with a literal value: use the converted value
224         if (itemValue instanceof String && values != null && values.length > 0 && !(values[0] instanceof String)) {
225           itemValue = ComponentUtils.getConvertedValue(facesContext, component, (String) itemValue);
226         }
227         final String formattedValue = RenderUtils.getFormattedValue(facesContext, component, itemValue);
228         boolean contains;
229         if (submittedValues == null) {
230           contains = RenderUtils.contains(values, itemValue);
231         } else {
232           contains = RenderUtils.contains(submittedValues, formattedValue);
233         }
234         if (onlySelected != null) {
235           if (onlySelected) {
236             if (!contains) {
237               continue;
238             }
239           } else {
240             if (contains) {
241               continue;
242             }
243           }
244         }
245         writer.startElement(HtmlElements.OPTION);
246         writer.writeAttribute(HtmlAttributes.VALUE, formattedValue, true);
247         if (item instanceof org.apache.myfaces.tobago.model.SelectItem) {
248           final String image = ((org.apache.myfaces.tobago.model.SelectItem) item).getImage();
249           if (image != null) {
250             final Style style = new Style();
251             style.setBackgroundImage("url('"
252                 + ResourceManagerUtils.getImageOrDisabledImageWithPath(facesContext, image, item.isDisabled())
253                 + "')");
254             writer.writeStyleAttribute(style);
255           }
256         }
257         Markup markup = item instanceof Visual ? ((Visual) item).getMarkup() : Markup.NULL;
258         if (onlySelected == null && contains) {
259           writer.writeAttribute(HtmlAttributes.SELECTED, true);
260           markup = Markup.SELECTED.add(markup);
261         }
262         if (item.isDisabled()) {
263           writer.writeAttribute(HtmlAttributes.DISABLED, true);
264           markup = Markup.DISABLED.add(markup);
265         }
266         writer.writeClassAttribute(Classes.create(component, "option", markup));
267 
268         writer.writeText(item.getLabel());
269         writer.endElement(HtmlElements.OPTION);
270       }
271     }
272   }
273 
274   /**
275    * @deprecated Use client behaviour instead.
276    */
277   @Deprecated
278   public static void renderCommandFacet(
279       final UIComponent component, final FacesContext facesContext, final TobagoResponseWriter writer)
280       throws IOException {
281     renderCommandFacet(component, component.getClientId(facesContext), facesContext, writer);
282   }
283 
284   /**
285    * @deprecated Use client behaviour instead.
286    */
287   @Deprecated
288   public static void renderCommandFacet(
289       final UIComponent component, final String id, final FacesContext facesContext, final TobagoResponseWriter writer)
290       throws IOException {
291     if (ComponentUtils.getBooleanAttribute(component, Attributes.readonly)
292         || ComponentUtils.getBooleanAttribute(component, Attributes.disabled)) {
293       return;
294     }
295     CommandMap commandMap = null;
296     final Map<String, UIComponent> facets = component.getFacets();
297     for (final Map.Entry<String, UIComponent> entry : facets.entrySet()) {
298       final UIComponent facetComponent = entry.getValue();
299       if (facetComponent.isRendered()
300           && (facetComponent instanceof AbstractUICommand || facetComponent instanceof UIForm)) {
301         if (commandMap == null) {
302           commandMap = new CommandMap();
303         }
304         final String key = entry.getKey();
305         commandMap.addCommand(key, new Command(facesContext, entry.getValue(), id));
306       }
307     }
308     if (commandMap != null) {
309       writer.writeAttribute(DataAttributes.COMMANDS, JsonUtils.encode(commandMap), true);
310     }
311   }
312 
313   public static void encodeContextMenu(
314       final FacesContext facesContext, final TobagoResponseWriter writer, final UIComponent parent)
315       throws IOException {
316     final UIComponent contextMenu = FacetUtils.getContextMenu(parent);
317     if (contextMenu != null) {
318       writer.startElement(HtmlElements.OL);
319       writer.writeClassAttribute(TobagoClass.MENU_BAR, TobagoClass.MENU__CONTEXT_MENU);
320       RenderUtils.encode(facesContext, contextMenu);
321       writer.endElement(HtmlElements.OL);
322     }
323   }
324 
325   public static void writeDataAttributes(
326       final FacesContext context, final TobagoResponseWriter writer, final UIComponent component)
327       throws IOException {
328 
329     final Map<Object, Object> dataAttributes = ComponentUtils.getDataAttributes(component);
330     if (dataAttributes == null) {
331       return;
332     }
333 
334     final ELContext elContext = context.getELContext();
335 
336     for (final Map.Entry<Object, Object> entry : dataAttributes.entrySet()) {
337       final Object mapKey = entry.getKey();
338       final String name = mapKey instanceof ValueExpression
339           ? ((ValueExpression) mapKey).getValue(elContext).toString() : mapKey.toString();
340       final Object mapValue = entry.getValue();
341       final String value = mapValue instanceof ValueExpression
342           ? ((ValueExpression) mapValue).getValue(elContext).toString() : mapValue.toString();
343       writer.writeAttribute(DataAttributes.dynamic(name), value, true);
344     }
345   }
346 }