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.SupportsMarkup;
24  import org.apache.myfaces.tobago.component.SupportsRenderedPartially;
25  import org.apache.myfaces.tobago.component.UIColumnEvent;
26  import org.apache.myfaces.tobago.component.UICommand;
27  import org.apache.myfaces.tobago.component.UIForm;
28  import org.apache.myfaces.tobago.component.UIPage;
29  import org.apache.myfaces.tobago.component.UISheet;
30  import org.apache.myfaces.tobago.context.Markup;
31  import org.apache.myfaces.tobago.context.ResourceManagerUtils;
32  import org.apache.myfaces.tobago.internal.component.AbstractUICommand;
33  import org.apache.myfaces.tobago.internal.util.Deprecation;
34  import org.apache.myfaces.tobago.internal.util.FacesContextUtils;
35  import org.apache.myfaces.tobago.internal.util.StringUtils;
36  import org.apache.myfaces.tobago.internal.webapp.TobagoResponseWriterWrapper;
37  import org.apache.myfaces.tobago.renderkit.LabelWithAccessKey;
38  import org.apache.myfaces.tobago.renderkit.css.Classes;
39  import org.apache.myfaces.tobago.renderkit.css.Style;
40  import org.apache.myfaces.tobago.renderkit.html.Command;
41  import org.apache.myfaces.tobago.renderkit.html.CommandMap;
42  import org.apache.myfaces.tobago.renderkit.html.DataAttributes;
43  import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
44  import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
45  import org.apache.myfaces.tobago.renderkit.html.JsonUtils;
46  import org.apache.myfaces.tobago.renderkit.util.RenderUtils;
47  import org.apache.myfaces.tobago.util.ComponentUtils;
48  import org.apache.myfaces.tobago.util.FacetUtils;
49  import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  import javax.el.ELContext;
54  import javax.el.ValueExpression;
55  import javax.faces.component.UIComponent;
56  import javax.faces.component.UIInput;
57  import javax.faces.component.UINamingContainer;
58  import javax.faces.context.FacesContext;
59  import javax.faces.context.ResponseWriter;
60  import javax.faces.model.SelectItem;
61  import javax.faces.model.SelectItemGroup;
62  import java.io.IOException;
63  import java.util.ArrayList;
64  import java.util.Arrays;
65  import java.util.List;
66  import java.util.Locale;
67  import java.util.Map;
68  
69  public final class HtmlRendererUtils {
70  
71    private static final Logger LOG = LoggerFactory.getLogger(HtmlRendererUtils.class);
72    private static final String ERROR_FOCUS_KEY = HtmlRendererUtils.class.getName() + ".ErrorFocusId";
73    private static final String FOCUS_KEY = HtmlRendererUtils.class.getName() + ".FocusId";
74  
75    private HtmlRendererUtils() {
76      // to prevent instantiation
77    }
78  
79    private static boolean renderErrorFocusId(final FacesContext facesContext, final UIInput input) throws IOException {
80      if (ComponentUtils.isError(input)) {
81        if (!FacesContext.getCurrentInstance().getExternalContext().getRequestMap().containsKey(ERROR_FOCUS_KEY)) {
82          FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put(ERROR_FOCUS_KEY, Boolean.TRUE);
83          TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
84          String id = input.getClientId(facesContext);        
85          writer.writeJavascript("Tobago.errorFocusId = '" + id + "';");
86          return true;
87        } else {
88          return true;
89        }
90      }
91      return FacesContext.getCurrentInstance().getExternalContext().getRequestMap().containsKey(ERROR_FOCUS_KEY);
92    }
93  
94    public static void renderFocus(String clientId, boolean focus, boolean error, FacesContext facesContext,
95        TobagoResponseWriter writer) throws IOException {
96      final Map<String, Object> requestMap = facesContext.getExternalContext().getRequestMap();
97      if (!requestMap.containsKey(FOCUS_KEY)
98          && (clientId.equals(FacesContextUtils.getFocusId(facesContext)) || focus || error)) {
99        requestMap.put(FOCUS_KEY, Boolean.TRUE);
100       writer.writeAttribute(HtmlAttributes.AUTOFOCUS, true);
101     }
102   }
103 
104   public static void renderFocusId(final FacesContext facesContext, final UIComponent component)
105       throws IOException {
106     if (component instanceof UIInput) {
107       renderFocusId(facesContext, (UIInput) component);
108     }
109   }
110 
111   public static void renderFocusId(final FacesContext facesContext, final UIInput component)
112       throws IOException {
113     if (renderErrorFocusId(facesContext, component)) {
114       return;
115     }
116     if (ComponentUtils.getBooleanAttribute(component, Attributes.FOCUS)) {
117       UIPage page = (UIPage) ComponentUtils.findPage(facesContext, component);
118       String id = component.getClientId(facesContext);
119       if (!StringUtils.isBlank(page.getFocusId()) && !page.getFocusId().equals(id)) {
120         LOG.warn("page focusId = \"" + page.getFocusId() + "\" ignoring new value \""
121             + id + "\"");
122       } else {
123         TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
124         writer.writeJavascript("Tobago.focusId = '" + id + "';");
125       }
126     }
127   }
128 
129   /**
130    * @deprecated Since Tobago 2.0.0
131    */
132   @Deprecated
133   public static void createCssClass(FacesContext facesContext, UIComponent component) {
134     String rendererName = getRendererName(facesContext, component);
135     Deprecation.LOG.error("Can't render style class for renderer " + rendererName);
136   }
137 
138   public static String getRendererName(FacesContext facesContext, UIComponent component) {
139     String rendererType = component.getRendererType();
140     return rendererType.substring(0, 1).toLowerCase(Locale.ENGLISH) + rendererType.substring(1);
141   }
142 
143   public static void writeLabelWithAccessKey(TobagoResponseWriter writer, LabelWithAccessKey label)
144       throws IOException {
145     int pos = label.getPos();
146     String text = label.getText();
147     if (pos == -1) {
148       writer.writeText(text);
149     } else {
150       writer.writeText(text.substring(0, pos));
151       writer.startElement(HtmlElements.U, null);
152       writer.writeText(Character.toString(text.charAt(pos)));
153       writer.endElement(HtmlElements.U);
154       writer.writeText(text.substring(pos + 1));
155     }
156   }
157 
158   /** @deprecated since 1.5.7 and 2.0.0 */
159   @Deprecated
160   public static void setDefaultTransition(FacesContext facesContext, boolean transition)
161       throws IOException {
162     writeScriptLoader(facesContext, null, new String[]{"Tobago.transition = " + transition + ";"});
163   }
164 
165   public static void addClickAcceleratorKey(
166       FacesContext facesContext, String clientId, char key)
167       throws IOException {
168     //addClickAcceleratorKey(facesContext, clientId, key, null);
169   }
170 
171   public static void addClickAcceleratorKey(
172       FacesContext facesContext, String clientId, char key, String modifier)
173       throws IOException {
174     //String str
175     //    = createOnclickAcceleratorKeyJsStatement(clientId, key, modifier);
176     //writeScriptLoader(facesContext, null, new String[]{str});
177   }
178 
179   public static void addAcceleratorKey(
180       FacesContext facesContext, String func, char key) throws IOException {
181     //addAcceleratorKey(facesContext, func, key, null);
182   }
183 
184   public static void addAcceleratorKey(
185       FacesContext facesContext, String func, char key, String modifier)
186       throws IOException {
187     String str = createAcceleratorKeyJsStatement(func, key, modifier);
188     writeScriptLoader(facesContext, null, new String[]{str});
189   }
190 
191   public static String createOnclickAcceleratorKeyJsStatement(
192       String clientId, char key, String modifier) {
193     String func = "Tobago.clickOnElement('" + clientId + "');";
194     return createAcceleratorKeyJsStatement(func, key, modifier);
195   }
196 
197   public static String createAcceleratorKeyJsStatement(
198       String func, char key, String modifier) {
199     StringBuilder buffer = new StringBuilder("new Tobago.AcceleratorKey(function() {");
200     buffer.append(func);
201     if (!func.endsWith(";")) {
202       buffer.append(';');
203     }
204     buffer.append("}, \"");
205     buffer.append(key);
206     if (modifier != null) {
207       buffer.append("\", \"");
208       buffer.append(modifier);
209     }
210     buffer.append("\");");
211     return buffer.toString();
212   }
213 
214   /**
215    * @deprecated Please use setter;
216    */
217   @Deprecated
218   public static void removeStyleAttribute(UIComponent component, String name) {
219     Deprecation.LOG.error("HtmlRendererUtils.removeStyleAttribute() no longer supported. Use setter.");
220   }
221 
222   @Deprecated
223   public static void createHeaderAndBodyStyles(FacesContext facesContext, UIComponent component) {
224     Deprecation.LOG.error("HtmlRendererUtils.createHeaderAndBodyStyles() no longer supported");
225   }
226 
227   @Deprecated
228   public static void createHeaderAndBodyStyles(FacesContext facesContext, UIComponent component, boolean width) {
229     Deprecation.LOG.error("HtmlRendererUtils.createHeaderAndBodyStyles() no longer supported");
230   }
231 
232   public static String createSrc(String src, String ext) {
233     int dot = src.lastIndexOf('.');
234     if (dot == -1) {
235       LOG.warn("Image src without extension: '" + src + "'");
236       return src;
237     } else {
238       return src.substring(0, dot) + ext + src.substring(dot);
239     }
240   }
241 
242   public static TobagoResponseWriter getTobagoResponseWriter(FacesContext facesContext) {
243 
244     ResponseWriter writer = facesContext.getResponseWriter();
245     if (writer instanceof TobagoResponseWriter) {
246       return (TobagoResponseWriter) writer;
247     } else {
248       return new TobagoResponseWriterWrapper(writer);
249     }
250   }
251 
252   /**
253    * @deprecated Since Tobago 2.0.0. Because of CSP.
254    */
255   @Deprecated
256   public static void writeScriptLoader(FacesContext facesContext, String script)
257       throws IOException {
258     writeScriptLoader(facesContext, new String[]{script}, null);
259   }
260 
261   /**
262    * @deprecated Since Tobago 2.0.0. Because of CSP.
263    */
264   @Deprecated
265   public static void writeScriptLoader(FacesContext facesContext, String[] scripts, String[] afterLoadCmds)
266       throws IOException {
267     TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
268     if (scripts != null) {
269       LOG.error("Scripts argument for writeScriptLoader not supported anymore!");
270     }
271     String allScripts = "[]";
272     if (scripts != null) {
273       allScripts = ResourceManagerUtils.getScriptsAsJSArray(facesContext, scripts);
274     }
275     boolean ajax = FacesContextUtils.isAjax(facesContext);
276     writer.startJavascript();
277     // XXX fix me if scripts != null
278     if (ajax || scripts != null) {
279       writer.write("new Tobago.ScriptLoader(");
280       if (!ajax) {
281         writer.write("\n    ");
282       }
283       writer.write(allScripts);
284 
285       if (afterLoadCmds != null && afterLoadCmds.length > 0) {
286         writer.write(", ");
287         if (!ajax) {
288           writer.write("\n");
289         }
290         boolean first = true;
291         for (String afterLoadCmd : afterLoadCmds) {
292           String[] splittedStrings = StringUtils.split(afterLoadCmd, '\n'); // split on <CR> to have nicer JS
293           for (String splitted : splittedStrings) {
294             writer.write(first ? "          " : "        + ");
295             writer.write("\"");
296             String cmd = StringUtils.replace(splitted, "\\", "\\\\");
297             cmd = StringUtils.replace(cmd, "\"", "\\\"");
298             writer.write(cmd);
299             writer.write("\"");
300             if (!ajax) {
301               writer.write("\n");
302             }
303             first = false;
304           }
305         }
306       }
307       writer.write(");");
308     } else {
309     for (String afterLoadCmd : afterLoadCmds) {
310       writer.write(afterLoadCmd);
311     }
312     }
313     writer.endJavascript();
314   }
315 
316   /**
317    * @deprecated Since Tobago 2.0.0. Because of CSP.
318    */
319   @Deprecated
320   public static void writeStyleLoader(
321       FacesContext facesContext, String[] styles) throws IOException {
322     TobagoResponseWriter writer = HtmlRendererUtils.getTobagoResponseWriter(facesContext);
323 
324     writer.startJavascript();
325     writer.write("Tobago.ensureStyleFiles(");
326     writer.write(ResourceManagerUtils.getStylesAsJSArray(facesContext, styles));
327     writer.write(");");
328     writer.endJavascript();
329   }
330 
331   public static String getTitleFromTipAndMessages(FacesContext facesContext, UIComponent component) {
332     String messages = ComponentUtils.getFacesMessageAsString(facesContext, component);
333     return HtmlRendererUtils.addTip(messages, component.getAttributes().get(Attributes.TIP));
334   }
335 
336   public static String addTip(String title, Object tip) {
337     if (tip != null) {
338       if (title != null && title.length() > 0) {
339         title += " :: ";
340       } else {
341         title = "";
342       }
343       title += tip;
344     }
345     return title;
346   }
347 
348   public static void renderSelectItems(UIInput component, List<SelectItem> items, Object[] values,
349       TobagoResponseWriter writer, FacesContext facesContext) throws IOException {
350     renderSelectItems(component, items, values, null, writer, facesContext);
351 
352   }
353 
354   public static void renderSelectItems(UIInput component, List<SelectItem> items, Object[] values, Boolean onlySelected,
355       TobagoResponseWriter writer, FacesContext facesContext) throws IOException {
356 
357     if (LOG.isDebugEnabled()) {
358       LOG.debug("value = '" + Arrays.toString(values) + "'");
359     }
360     for (SelectItem item : items) {
361       if (item instanceof SelectItemGroup) {
362         writer.startElement(HtmlElements.OPTGROUP, null);
363         writer.writeAttribute(HtmlAttributes.LABEL, item.getLabel(), true);
364         if (item.isDisabled()) {
365           writer.writeAttribute(HtmlAttributes.DISABLED, true);
366         }
367         SelectItem[] selectItems = ((SelectItemGroup) item).getSelectItems();
368         renderSelectItems(component, Arrays.asList(selectItems), values, onlySelected, writer, facesContext);
369         writer.endElement(HtmlElements.OPTGROUP);
370       } else {
371 
372         Object itemValue = item.getValue();
373         // when using selectItem tag with a literal value: use the converted value
374         if (itemValue instanceof String && values != null && values.length > 0 && !(values[0] instanceof String)) {
375           itemValue = ComponentUtils.getConvertedValue(facesContext, component, (String) itemValue);
376         }
377         boolean contains = RenderUtils.contains(values, itemValue);
378         if (onlySelected != null) {
379           if (onlySelected) {
380             if (!contains) {
381               continue;
382             }
383           } else {
384             if (contains) {
385               continue;
386             }
387           }
388         }
389         writer.startElement(HtmlElements.OPTION, null);
390         String formattedValue = RenderUtils.getFormattedValue(facesContext, component, itemValue);
391         writer.writeAttribute(HtmlAttributes.VALUE, formattedValue, true);
392         if (item instanceof org.apache.myfaces.tobago.model.SelectItem) {
393           String image = ((org.apache.myfaces.tobago.model.SelectItem) item).getImage();
394           if (image != null) {
395             final Style style = new Style();
396             style.setBackgroundImage("url('"
397                 + ResourceManagerUtils.getImageOrDisabledImageWithPath(facesContext, image, item.isDisabled())
398                 + "')");
399             writer.writeStyleAttribute(style);
400           }
401         }
402         Markup markup = item instanceof SupportsMarkup ? ((SupportsMarkup) item).getMarkup() : Markup.NULL;
403         if (onlySelected == null && contains) {
404           writer.writeAttribute(HtmlAttributes.SELECTED, true);
405           markup = Markup.SELECTED.add(markup);
406         }
407         if (item.isDisabled()) {
408           writer.writeAttribute(HtmlAttributes.DISABLED, true);
409           markup = Markup.DISABLED.add(markup);
410         }
411         writer.writeClassAttribute(Classes.create(component, "option", markup));
412 
413         writer.writeText(item.getLabel());
414         writer.endElement(HtmlElements.OPTION);
415       }
416     }
417   }
418 
419   public static String getComponentIds(FacesContext context, UIComponent component, String[] componentId) {
420     StringBuilder sb = new StringBuilder();
421     for (String id : componentId) {
422       if (!StringUtils.isBlank(id)) {
423         if (sb.length() > 0) {
424           sb.append(",");
425         }
426         String clientId = getComponentId(context, component, id);
427         if (clientId != null) {
428           sb.append(clientId);
429         }
430       }
431     }
432     return sb.toString();
433   }
434 
435   public static String[] getComponentIdsAsList(
436       FacesContext context, UIComponent component, String[] componentId) {
437     List<String> result = new ArrayList<String>(componentId.length);
438     for (String id : componentId) {
439       if (!StringUtils.isBlank(id)) {
440         String clientId = getComponentId(context, component, id);
441         if (clientId != null) {
442           result.add(clientId);
443         }
444       }
445     }
446     return (String[]) result.toArray(new String[result.size()]);
447   }
448 
449   public static String getComponentId(FacesContext context, UIComponent component, String componentId) {
450     UIComponent partiallyComponent = ComponentUtils.findComponent(component, componentId);
451     if (partiallyComponent != null) {
452       String clientId = partiallyComponent.getClientId(context);
453       if (partiallyComponent instanceof UISheet) {
454         int rowIndex = ((UISheet) partiallyComponent).getRowIndex();
455         if (rowIndex >= 0 && clientId.endsWith(Integer.toString(rowIndex))) {
456           return clientId.substring(0, clientId.lastIndexOf(UINamingContainer.getSeparatorChar(context)));
457         }
458       }
459       return clientId;
460     }
461     LOG.error("No Component found for id " + componentId + " search base component " + component.getClientId(context));
462     return null;
463   }
464 
465   /**
466    * @deprecated since Tobago 1.5.0.
467    */
468   @Deprecated
469   public static String toStyleString(String key, Integer value) {
470     StringBuilder buf = new StringBuilder();
471     buf.append(key);
472     buf.append(":");
473     buf.append(value);
474     buf.append("px; ");
475     return buf.toString();
476   }
477 
478   /**
479    * @deprecated since Tobago 1.5.0.
480    */
481   @Deprecated
482   public static String toStyleString(String key, String value) {
483     StringBuilder buf = new StringBuilder();
484     buf.append(key);
485     buf.append(":");
486     buf.append(value);
487     buf.append("; ");
488     return buf.toString();
489   }
490 
491   /**
492    * @deprecated since Tobago 1.5.0. Please use getTitleFromTipAndMessages and write it out.
493    */
494   @Deprecated
495   public static void renderTip(UIComponent component, TobagoResponseWriter writer) throws IOException {
496     Object objTip = component.getAttributes().get(Attributes.TIP);
497     if (objTip != null) {
498       String tip = String.valueOf(objTip);
499       writer.writeAttribute(HtmlAttributes.TITLE, tip, true);
500     }
501   }
502 
503   /**
504    * @deprecated since Tobago 1.5.0. Please use getTitleFromTipAndMessages and write it out.
505    */
506   public static void renderImageTip(UIComponent component, TobagoResponseWriter writer) throws IOException {
507     Object objTip = component.getAttributes().get(Attributes.TIP);
508     if (objTip != null) {
509       String tip = String.valueOf(objTip);
510       writer.writeAttribute(HtmlAttributes.ALT, tip, true);
511     } else {
512       writer.writeAttribute(HtmlAttributes.ALT, "", false);
513     }
514   }
515 
516   public static String getJavascriptString(String str) {
517     if (str != null) {
518       return "\"" + str + "\"";
519     }
520     return null;
521   }
522 
523   public static String getRenderedPartiallyJavascriptArray(FacesContext facesContext, UICommand command) {
524     if (command == null) {
525       return null;
526     }
527     return getRenderedPartiallyJavascriptArray(facesContext, command, command);
528   }
529 
530   public static String getRenderedPartiallyJavascriptArray(FacesContext facesContext, UIComponent searchBase,
531       SupportsRenderedPartially supportsRenderedPartially) {
532     String[] list = supportsRenderedPartially.getRenderedPartially();
533     if (list == null || list.length == 0) {
534       return null;
535     }
536     StringBuilder strBuilder = new StringBuilder();
537     strBuilder.append("[");
538     for (int i = 0; i < list.length; i++) {
539       if (i != 0) {
540         strBuilder.append(",");
541       }
542       strBuilder.append("\"");
543       strBuilder.append(HtmlRendererUtils.getComponentId(facesContext, searchBase, list[i]));
544       strBuilder.append("\"");
545     }
546     strBuilder.append("]");
547     return strBuilder.toString();
548   }
549 
550   public static String getJavascriptArray(String[] list) {
551     StringBuilder strBuilder = new StringBuilder();
552     strBuilder.append("[");
553     for (int i = 0; i < list.length; i++) {
554       if (i != 0) {
555         strBuilder.append(",");
556       }
557       strBuilder.append("\"");
558       strBuilder.append(list[i]);
559       strBuilder.append("\"");
560     }
561     strBuilder.append("]");
562     return strBuilder.toString();
563   }
564 
565   public static void renderDojoDndSource(FacesContext context, UIComponent component)
566       throws IOException {
567     Object objDojoType = component.getAttributes().get("dojoType");
568     if (null != objDojoType && (objDojoType.equals("dojo.dnd.Source") || objDojoType.equals("dojo.dnd.Target"))) {
569       FacesContextUtils.addOnloadScript(context, createDojoDndType(component,
570           component.getClientId(context), String.valueOf(objDojoType)));
571     }
572   }
573 
574   public static void renderDojoDndItem(UIComponent component, TobagoResponseWriter writer, boolean addStyle)
575       throws IOException {
576     Object objDndType = component.getAttributes().get("dndType");
577     if (objDndType != null) {
578       writer.writeAttribute("dndType", String.valueOf(objDndType), false);
579     }
580     Object objDndData = component.getAttributes().get("dndData");
581     if (objDndData != null) {
582       writer.writeAttribute("dndData", String.valueOf(objDndData), false);
583     }
584   }
585 
586   private static String createDojoDndType(UIComponent component, String clientId, String dojoType) {
587     StringBuilder strBuilder = new StringBuilder();
588     strBuilder.append("new ").append(dojoType).append("('").append(clientId).append("'");
589     StringBuilder parameter = new StringBuilder();
590 
591     Object objHorizontal = component.getAttributes().get("horizontal");
592     if (objHorizontal != null) {
593       parameter.append("horizontal: ").append(String.valueOf(objHorizontal)).append(",");
594     }
595     Object objCopyOnly = component.getAttributes().get("copyOnly");
596     if (objCopyOnly != null) {
597       parameter.append("copyOnly: ").append(String.valueOf(objCopyOnly)).append(",");
598     }
599     Object objSkipForm = component.getAttributes().get("skipForm");
600     if (objSkipForm != null) {
601       parameter.append("skipForm: ").append(String.valueOf(objSkipForm)).append(",");
602     }
603     Object objWithHandles = component.getAttributes().get("withHandles");
604     if (objWithHandles != null) {
605       parameter.append("withHandles: ").append(String.valueOf(objWithHandles)).append(",");
606     }
607     Object objAccept = component.getAttributes().get("accept");
608     if (objAccept != null) {
609       String accept = null;
610       if (objAccept instanceof String[]) {
611         String[] allowed = (String[]) objAccept;
612         if (allowed.length > 1) {
613           // TODO replace this
614           accept = "'" + allowed[0] + "'";
615           for (int i = 1; i < allowed.length; i++) {
616             accept += ",'" + allowed[i] + "'";
617           }
618         }
619       } else {
620         accept = (String) objAccept;
621       }
622       parameter.append("accept: [").append(accept).append("],");
623     }
624     Object objSingular = component.getAttributes().get("singular");
625     if (objSingular != null) {
626       parameter.append("singular: ").append(String.valueOf(objSingular)).append(",");
627     }
628     Object objCreator = component.getAttributes().get("creator");
629     if (objCreator != null) {
630       parameter.append("creator: ").append(String.valueOf(objCreator)).append(",");
631     }
632     if (parameter.length() > 0) {
633       parameter.deleteCharAt(parameter.lastIndexOf(","));
634       strBuilder.append(",{").append(parameter).append("}");
635     }
636     strBuilder.append(");");
637     return strBuilder.toString();
638   }
639 
640   public static void renderCommandFacet(UIComponent component, FacesContext facesContext,
641       TobagoResponseWriter writer) throws IOException {
642     renderCommandFacet(component, component.getClientId(facesContext), facesContext, writer);
643   }
644 
645   public static void renderCommandFacet(
646       UIComponent component, String id, FacesContext facesContext, TobagoResponseWriter writer) throws IOException {
647     if (ComponentUtils.getBooleanAttribute(component, Attributes.READONLY)
648         || ComponentUtils.getBooleanAttribute(component, Attributes.DISABLED)) {
649       return;
650     }
651     CommandMap commandMap = null;
652     Map<String, UIComponent> facets = component.getFacets();
653     for (Map.Entry<String, UIComponent> entry : facets.entrySet()) {
654       UIComponent facetComponent = entry.getValue();
655       if (facetComponent.isRendered()
656           && (facetComponent instanceof AbstractUICommand || facetComponent instanceof UIForm)) {
657         if (commandMap == null) {
658           commandMap = new CommandMap();
659         }
660         String key = entry.getKey();
661         commandMap.addCommand(key, new Command(facesContext, entry.getValue(), id));
662       }
663     }
664     if (commandMap != null) {
665       writer.writeAttribute(DataAttributes.COMMANDS, JsonUtils.encode(commandMap), true);
666     }
667   }
668 
669   public static boolean renderSheetCommands(UISheet sheet, FacesContext facesContext,
670                                          TobagoResponseWriter writer) throws IOException {
671     CommandMap commandMap = null;
672     for (UIComponent child : sheet.getChildren()) {
673       if (child instanceof UIColumnEvent) {
674         UIColumnEvent columnEvent = (UIColumnEvent) child;
675         if (columnEvent.isRendered()) {
676           UIComponent selectionChild = child.getChildren().get(0);
677           if (selectionChild != null && selectionChild instanceof AbstractUICommand && selectionChild.isRendered()) {
678             UICommand action = (UICommand) selectionChild;
679             if (commandMap == null) {
680               commandMap = new CommandMap();
681             }
682             commandMap.addCommand(columnEvent.getEvent(), new Command(facesContext, action, null));
683           }
684         }
685       }
686     }
687     if (commandMap != null) {
688       writer.writeAttribute(DataAttributes.ROW_ACTION, JsonUtils.encode(commandMap), true);
689       return true;
690     }
691     return false;
692   }
693 
694 
695   public static void checkForCommandFacet(UIComponent component, FacesContext facesContext, TobagoResponseWriter writer)
696       throws IOException {
697     checkForCommandFacet(component, Arrays.asList(component.getClientId(facesContext)), facesContext, writer);
698   }
699 
700   public static void checkForCommandFacet(UIComponent component, List<String> clientIds, FacesContext facesContext,
701       TobagoResponseWriter writer) throws IOException {
702     if (ComponentUtils.getBooleanAttribute(component, Attributes.READONLY)
703         || ComponentUtils.getBooleanAttribute(component, Attributes.DISABLED)) {
704       return;
705     }
706     Map<String, UIComponent> facets = component.getFacets();
707     for (Map.Entry<String, UIComponent> entry : facets.entrySet()) {
708       if (entry.getValue() instanceof UICommand) {
709         addCommandFacet(clientIds, entry, facesContext, writer);
710       }
711     }
712   }
713 
714   private static void addCommandFacet(
715       List<String> clientIds, Map.Entry<String, UIComponent> facetEntry,
716       FacesContext facesContext, TobagoResponseWriter writer)
717       throws IOException {
718     for (String clientId : clientIds) {
719       writeScriptForClientId(clientId, facetEntry, facesContext, writer);
720     }
721   }
722 
723   /**
724    * @deprecated Since Tobago 2.0.0. Because of CSP.
725    */
726   @Deprecated
727   private static void writeScriptForClientId(
728       String clientId, Map.Entry<String, UIComponent> facetEntry,
729       FacesContext facesContext, TobagoResponseWriter writer) throws IOException {
730     if (facetEntry.getValue() instanceof UICommand
731         && ((UICommand) facetEntry.getValue()).getRenderedPartially().length > 0) {
732       writer.startJavascript();
733       writer.write("var element = Tobago.element(\"");
734       writer.write(clientId);
735       writer.write("\");\n");
736       writer.write("if (element) {\n");
737       writer.write("   Tobago.addEventListener(element, \"");
738       writer.write(facetEntry.getKey());
739       writer.write("\", function(){Tobago.reloadComponent(this, '");
740       writer.write(HtmlRendererUtils.getComponentIds(facesContext, facetEntry.getValue(),
741               ((UICommand) facetEntry.getValue()).getRenderedPartially()));
742       writer.write("','");
743       writer.write(facetEntry.getValue().getClientId(facesContext)); 
744       writer.write("', {})});\n");
745       writer.write("};");
746       writer.endJavascript();
747     } else {
748       UIComponent facetComponent = facetEntry.getValue();
749 
750       writer.startJavascript();
751       writer.write("var element = Tobago.element(\"");
752       writer.write(clientId + "\");\n");
753       writer.write("if (element) {\n");
754       writer.write("   Tobago.addEventListener(element, \"");
755       writer.write(facetEntry.getKey());
756       writer.write("\", function(){");
757       String facetAction = (String) facetComponent.getAttributes().get(Attributes.ONCLICK);
758       if (facetAction != null) {
759          // Replace @autoId
760         facetAction = StringUtils.replace(facetAction, "@autoId", facetComponent.getClientId(facesContext));
761         writer.write(facetAction);
762       } else {
763         writer.write(createSubmitAction(
764             facetComponent.getClientId(facesContext),
765             ComponentUtils.getBooleanAttribute(facetComponent, Attributes.TRANSITION),
766             null,
767             clientId));
768       }
769       writer.write("});\n};");
770       writer.endJavascript();
771     }
772   }
773 
774   /**
775    * @deprecated since 2.0.0. JavaScript should not be rendered in the page. See CSP.
776    */
777   @Deprecated
778   public static String createSubmitAction(String clientId, boolean transition, String target, String focus) {
779     StringBuilder builder = new StringBuilder();
780     builder.append("Tobago.submitAction(this,'");
781     builder.append(clientId);
782     builder.append("',{");
783     if (!transition) { // transition == true is the default
784       builder.append("transition:false");
785       if (target != null || focus != null) {
786         builder.append(',');
787       }
788     }
789     if (target != null) {
790       builder.append("target:'");
791       builder.append(target);
792       builder.append('\'');
793       if (focus != null) {
794         builder.append(',');
795       }
796     }
797     if (focus != null) {
798       builder.append("focus:'");
799       builder.append(focus);
800       builder.append('\'');
801     }
802     builder.append("});");
803     return builder.toString();
804   }
805 
806   /**
807    * @deprecated since Tobago 1.5.0. Please use {@link org.apache.myfaces.tobago.renderkit.css.Classes}.
808    */
809   @Deprecated
810   public static void removeStyleClasses(UIComponent cell) {
811     Deprecation.LOG.warn("cell = '" + cell + "'");
812   }
813 
814   public static void encodeContextMenu(FacesContext facesContext, TobagoResponseWriter writer, UIComponent parent)
815       throws IOException {
816     final UIComponent contextMenu = FacetUtils.getContextMenu(parent);
817     if (contextMenu != null) {
818       writer.startElement(HtmlElements.OL, contextMenu);
819       writer.writeClassAttribute("tobago-menuBar tobago-menu-contextMenu");
820       RenderUtils.encode(facesContext, contextMenu);
821       writer.endElement(HtmlElements.OL);
822     }
823   }
824 
825     public static void addAcceleratorKey(FacesContext facesContext, UIComponent component, Character accessKey) {
826       String clientId = component.getClientId(facesContext);
827       String jsStatement = createOnclickAcceleratorKeyJsStatement(clientId, accessKey, null);
828       FacesContextUtils.addMenuAcceleratorScript(facesContext, jsStatement);
829     }
830 
831   public static void writeDataAttributes(
832       FacesContext context, TobagoResponseWriter writer, UIComponent component)
833       throws IOException {
834 
835     final Map<Object, Object> dataAttributes = ComponentUtils.getDataAttributes(component);
836     if (dataAttributes == null) {
837       return;
838     }
839 
840     final ELContext elContext = context.getELContext();
841 
842     for (Map.Entry<Object, Object> entry : dataAttributes.entrySet()) {
843       final Object mapKey = entry.getKey();
844       final String name = mapKey instanceof ValueExpression
845           ? ((ValueExpression) mapKey).getValue(elContext).toString() : mapKey.toString();
846       final Object mapValue = entry.getValue();
847       final String value = mapValue instanceof ValueExpression
848           ? ((ValueExpression) mapValue).getValue(elContext).toString() : mapValue.toString();
849       writer.writeAttribute("data-" + name, value, true);
850     }
851   }
852 }