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