001    package org.apache.myfaces.tobago.renderkit.html;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    import org.apache.myfaces.tobago.component.ComponentUtil;
024    import org.apache.myfaces.tobago.component.SupportsMarkup;
025    import org.apache.myfaces.tobago.component.UICommand;
026    import org.apache.myfaces.tobago.component.UIData;
027    import org.apache.myfaces.tobago.component.UIPage;
028    import org.apache.myfaces.tobago.context.ResourceManagerUtil;
029    import org.apache.myfaces.tobago.renderkit.LabelWithAccessKey;
030    import org.apache.myfaces.tobago.renderkit.LayoutInformationProvider;
031    import org.apache.myfaces.tobago.renderkit.LayoutableRendererBase;
032    import org.apache.myfaces.tobago.renderkit.RenderUtil;
033    import org.apache.myfaces.tobago.renderkit.RendererBaseWrapper;
034    import org.apache.myfaces.tobago.util.LayoutUtil;
035    import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
036    import org.apache.myfaces.tobago.webapp.TobagoResponseWriterWrapper;
037    
038    import javax.faces.component.NamingContainer;
039    import javax.faces.component.UIComponent;
040    import javax.faces.component.UIInput;
041    import javax.faces.context.FacesContext;
042    import javax.faces.context.ResponseWriter;
043    import javax.faces.model.SelectItem;
044    import javax.faces.model.SelectItemGroup;
045    import java.io.IOException;
046    import java.util.Arrays;
047    import java.util.List;
048    import java.util.Locale;
049    import java.util.Map;
050    import java.util.StringTokenizer;
051    
052    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_FOCUS;
053    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INLINE;
054    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_HEIGHT;
055    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_INNER_WIDTH;
056    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_HEIGHT;
057    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_LAYOUT_WIDTH;
058    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE;
059    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_BODY;
060    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_HEADER;
061    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_TIP;
062    import static org.apache.myfaces.tobago.TobagoConstants.FACET_LAYOUT;
063    import static org.apache.myfaces.tobago.TobagoConstants.RENDERER_TYPE_OUT;
064    
065    /*
066     * Date: Jan 11, 2005
067     * Time: 4:59:36 PM
068     */
069    public final class HtmlRendererUtil {
070    
071      private static final Log LOG = LogFactory.getLog(HtmlRendererUtil.class);
072      private static final String ERROR_FOCUS_KEY = HtmlRendererUtil.class.getName() + ".ErrorFocusId";
073    
074      private HtmlRendererUtil() {
075        // to prevent instantiation
076      }
077    
078      private static boolean renderErrorFocusId(final FacesContext facesContext, final UIInput input) throws IOException {
079        if (ComponentUtil.isError(input)) {
080          if (!FacesContext.getCurrentInstance().getExternalContext().getRequestMap().containsKey(ERROR_FOCUS_KEY)) {
081            FacesContext.getCurrentInstance().getExternalContext().getRequestMap().put(ERROR_FOCUS_KEY, Boolean.TRUE);
082            TobagoResponseWriter writer = HtmlRendererUtil.getTobagoResponseWriter(facesContext);
083            String id = input.getClientId(facesContext);
084            writer.writeJavascript("Tobago.errorFocusId = '" + id + "';");
085            return true;
086          } else {
087            return true;
088          }
089        }
090        return FacesContext.getCurrentInstance().getExternalContext().getRequestMap().containsKey(ERROR_FOCUS_KEY);
091      }
092    
093      public static void renderFocusId(final FacesContext facesContext, final UIComponent component)
094          throws IOException {
095        if (component instanceof UIInput) {
096          renderFocusId(facesContext, (UIInput) component);
097        }
098      }
099    
100      public static void renderFocusId(final FacesContext facesContext, final UIInput component)
101          throws IOException {
102        if (renderErrorFocusId(facesContext, component)) {
103          return;
104        }
105        if (ComponentUtil.getBooleanAttribute(component, ATTR_FOCUS)) {
106          UIPage page = ComponentUtil.findPage(facesContext, component);
107          String id = component.getClientId(facesContext);
108          if (!StringUtils.isBlank(page.getFocusId()) && !page.getFocusId().equals(id)) {
109            LOG.warn("page focusId = \"" + page.getFocusId() + "\" ignoring new value \""
110                + id + "\"");
111          } else {
112            TobagoResponseWriter writer = HtmlRendererUtil.getTobagoResponseWriter(facesContext);
113            writer.writeJavascript("Tobago.focusId = '" + id + "';");
114          }
115        }
116      }
117    
118      public static void prepareRender(FacesContext facesContext, UIComponent component) {
119        // xxx find a better way for this question: isTobago or isLayoutable something like that.
120        LayoutableRendererBase layoutRendererBase = ComponentUtil.getRenderer(facesContext, component);
121        if (layoutRendererBase != null && !(layoutRendererBase instanceof RendererBaseWrapper)) {
122          createCssClass(facesContext, component);
123          layoutWidth(facesContext, component);
124          layoutHeight(facesContext, component);
125        }
126      }
127    
128      public static HtmlStyleMap prepareInnerStyle(UIComponent component) {
129        HtmlStyleMap htmlStyleMap = new HtmlStyleMap();
130        Integer innerSpaceInteger = (Integer)
131            component.getAttributes().get(ATTR_INNER_WIDTH);
132        if (innerSpaceInteger != null && innerSpaceInteger != -1) {
133          htmlStyleMap.put("width", innerSpaceInteger);
134        }
135        innerSpaceInteger = (Integer)
136            component.getAttributes().get(ATTR_INNER_HEIGHT);
137        if (innerSpaceInteger != null && innerSpaceInteger != -1) {
138          htmlStyleMap.put("height", innerSpaceInteger);
139        }
140        return htmlStyleMap;
141      }
142    
143    
144      public static void createCssClass(FacesContext facesContext, UIComponent component) {
145        String rendererName = getRendererName(facesContext, component);
146        if (rendererName != null) {
147          StyleClasses classes = StyleClasses.ensureStyleClasses(component);
148          classes.updateClassAttributeAndMarkup(component, rendererName);
149        }
150      }
151    
152      public static String getRendererName(FacesContext facesContext, UIComponent component) {
153        final String rendererType = component.getRendererType();
154        //final String family = component.getFamily();
155        if (rendererType != null//&& !"facelets".equals(family)
156            ) {
157          LayoutableRendererBase layoutableRendererBase = ComponentUtil.getRenderer(facesContext, component);
158          if (layoutableRendererBase != null) {
159            return layoutableRendererBase.getRendererName(rendererType);
160          }
161        }
162        return null;
163      }
164    
165      public static void writeLabelWithAccessKey(TobagoResponseWriter writer, LabelWithAccessKey label)
166          throws IOException {
167        int pos = label.getPos();
168        String text = label.getText();
169        if (pos == -1) {
170          writer.writeText(text);
171        } else {
172          writer.writeText(text.substring(0, pos));
173          writer.startElement(HtmlConstants.U, null);
174          writer.writeText(Character.toString(text.charAt(pos)));
175          writer.endElement(HtmlConstants.U);
176          writer.writeText(text.substring(pos + 1));
177        }
178      }
179    
180      public static void setDefaultTransition(FacesContext facesContext, boolean transition)
181          throws IOException {
182        writeScriptLoader(facesContext, null, new String[]{"Tobago.transition = " + transition + ";"});
183      }
184    
185      public static void addClickAcceleratorKey(
186          FacesContext facesContext, String clientId, char key)
187          throws IOException {
188        addClickAcceleratorKey(facesContext, clientId, key, null);
189      }
190    
191      public static void addClickAcceleratorKey(
192          FacesContext facesContext, String clientId, char key, String modifier)
193          throws IOException {
194        String str
195            = createOnclickAcceleratorKeyJsStatement(clientId, key, modifier);
196        writeScriptLoader(facesContext, null, new String[]{str});
197      }
198    
199      public static void addAcceleratorKey(
200          FacesContext facesContext, String func, char key) throws IOException {
201        addAcceleratorKey(facesContext, func, key, null);
202      }
203    
204      public static void addAcceleratorKey(
205          FacesContext facesContext, String func, char key, String modifier)
206          throws IOException {
207        String str = createAcceleratorKeyJsStatement(func, key, modifier);
208        writeScriptLoader(facesContext, null, new String[]{str});
209      }
210    
211      public static String createOnclickAcceleratorKeyJsStatement(
212          String clientId, char key, String modifier) {
213        String func = "Tobago.clickOnElement('" + clientId + "');";
214        return createAcceleratorKeyJsStatement(func, key, modifier);
215      }
216    
217      public static String createAcceleratorKeyJsStatement(
218          String func, char key, String modifier) {
219        StringBuilder buffer = new StringBuilder();
220        buffer.append("new Tobago.AcceleratorKey(function() {");
221        buffer.append(func);
222        if (!func.endsWith(";")) {
223          buffer.append(';');
224        }
225        buffer.append("}, \"");
226        buffer.append(key);
227        if (modifier != null) {
228          buffer.append("\", \"");
229          buffer.append(modifier);
230        }
231        buffer.append("\");");
232        return buffer.toString();
233      }
234    
235      public static String getLayoutSpaceStyle(UIComponent component) {
236        StringBuilder sb = new StringBuilder();
237        Integer space = LayoutUtil.getLayoutSpace(component, ATTR_LAYOUT_WIDTH, ATTR_LAYOUT_WIDTH);
238        if (space != null) {
239          sb.append(" width: ");
240          sb.append(space);
241          sb.append("px;");
242        }
243        space = LayoutUtil.getLayoutSpace(component, ATTR_LAYOUT_HEIGHT, ATTR_LAYOUT_HEIGHT);
244        if (space != null) {
245          sb.append(" height: ");
246          sb.append(space);
247          sb.append("px;");
248        }
249        return sb.toString();
250      }
251    
252      public static Integer getStyleAttributeIntValue(HtmlStyleMap style, String name) {
253        if (style == null) {
254          return null;
255        }
256        return style.getInt(name);
257      }
258    
259      public static String getStyleAttributeValue(String style, String name) {
260        if (style == null) {
261          return null;
262        }
263        String value = null;
264        StringTokenizer st = new StringTokenizer(style, ";");
265        while (st.hasMoreTokens()) {
266          String attribute = st.nextToken().trim();
267          if (attribute.startsWith(name)) {
268            value = attribute.substring(attribute.indexOf(':') + 1).trim();
269          }
270        }
271        return value;
272      }
273    
274    
275      public static void replaceStyleAttribute(UIComponent component, String styleAttribute, String value) {
276        HtmlStyleMap style = ensureStyleAttributeMap(component);
277        style.put(styleAttribute, value);
278      }
279    
280      public static void replaceStyleAttribute(UIComponent component, String attribute,
281          String styleAttribute, String value) {
282        HtmlStyleMap style = ensureStyleAttributeMap(component, attribute);
283        style.put(styleAttribute, value);
284      }
285    
286      public static void replaceStyleAttribute(UIComponent component, String styleAttribute, int value) {
287        HtmlStyleMap style = ensureStyleAttributeMap(component);
288        style.put(styleAttribute, value);
289      }
290    
291      public static void replaceStyleAttribute(UIComponent component, String attribute,
292          String styleAttribute, int value) {
293        HtmlStyleMap style = ensureStyleAttributeMap(component, attribute);
294        style.put(styleAttribute, value);
295    
296      }
297    
298      private static HtmlStyleMap ensureStyleAttributeMap(UIComponent component) {
299        return ensureStyleAttributeMap(component, ATTR_STYLE);
300      }
301    
302      private static HtmlStyleMap ensureStyleAttributeMap(UIComponent component, String attribute) {
303        final Map attributes = component.getAttributes();
304        HtmlStyleMap style = (HtmlStyleMap) attributes.get(attribute);
305        if (style == null) {
306          style = new HtmlStyleMap();
307          attributes.put(attribute, style);
308        }
309        return style;
310      }
311    
312      /**
313       * @deprecated
314       */
315      public static String replaceStyleAttribute(String style, String name,
316          String value) {
317        style = removeStyleAttribute(style != null ? style : "", name);
318        return style + " " + name + ": " + value + ";";
319      }
320    
321      /**
322       * @deprecated
323       */
324      public static String removeStyleAttribute(String style, String name) {
325        if (style == null) {
326          return null;
327        }
328        String pattern = name + "\\s*?:[^;]*?;";
329        return style.replaceAll(pattern, "").trim();
330      }
331    
332      public static void removeStyleAttribute(UIComponent component, String name) {
333        ensureStyleAttributeMap(component).remove(name);
334      }
335    
336      /**
337       * @deprecated Please use StyleClasses.ensureStyleClasses(component).add(clazz);
338       */
339      @Deprecated
340      public static void addCssClass(UIComponent component, String clazz) {
341        StyleClasses.ensureStyleClasses(component).addFullQualifiedClass(clazz);
342      }
343    
344      public static void layoutWidth(FacesContext facesContext, UIComponent component) {
345        layoutSpace(facesContext, component, true);
346      }
347    
348      public static void layoutHeight(FacesContext facesContext, UIComponent component) {
349        layoutSpace(facesContext, component, false);
350      }
351    
352      public static void layoutSpace(FacesContext facesContext, UIComponent component,
353          boolean width) {
354    
355        // prepare html 'style' attribute
356        Integer layoutSpace;
357        String layoutAttribute;
358        String styleAttribute;
359        if (width) {
360          layoutSpace = LayoutUtil.getLayoutWidth(component);
361          layoutAttribute = ATTR_LAYOUT_WIDTH;
362          styleAttribute = HtmlAttributes.WIDTH;
363        } else {
364          layoutSpace = LayoutUtil.getLayoutHeight(component);
365          layoutAttribute = ATTR_LAYOUT_HEIGHT;
366          styleAttribute = HtmlAttributes.HEIGHT;
367        }
368        int space = -1;
369        if (layoutSpace != null) {
370          space = layoutSpace.intValue();
371        }
372        if (space == -1 && (!RENDERER_TYPE_OUT.equals(component.getRendererType()))) {
373          UIComponent parent = component.getParent();
374          space = LayoutUtil.getInnerSpace(facesContext, parent, width);
375          if (space > 0 && !ComponentUtil.isFacetOf(component, parent)) {
376            component.getAttributes().put(layoutAttribute, Integer.valueOf(space));
377            if (width) {
378              component.getAttributes().remove(ATTR_INNER_WIDTH);
379            } else {
380              component.getAttributes().remove(ATTR_INNER_HEIGHT);
381            }
382          }
383        }
384        if (space > 0) {
385          LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
386          if (layoutSpace != null
387              || !ComponentUtil.getBooleanAttribute(component, ATTR_INLINE)) {
388            int styleSpace = space;
389            if (renderer != null) {
390              if (width) {
391                styleSpace -= renderer.getComponentExtraWidth(facesContext, component);
392              } else {
393                styleSpace -= renderer.getComponentExtraHeight(facesContext, component);
394              }
395            }
396    
397            replaceStyleAttribute(component, styleAttribute, styleSpace);
398    
399          }
400          UIComponent layout = component.getFacet(FACET_LAYOUT);
401          if (layout != null) {
402            int layoutSpace2 = LayoutUtil.getInnerSpace(facesContext, component,
403                width);
404            if (layoutSpace2 > 0) {
405              layout.getAttributes().put(layoutAttribute, Integer.valueOf(layoutSpace2));
406            }
407          }
408        }
409      }
410    
411      public static void createHeaderAndBodyStyles(FacesContext facesContext, UIComponent component) {
412        createHeaderAndBodyStyles(facesContext, component, true);
413        createHeaderAndBodyStyles(facesContext, component, false);
414      }
415    
416      public static void createHeaderAndBodyStyles(FacesContext facesContext, UIComponent component, boolean width) {
417        LayoutInformationProvider renderer = ComponentUtil.getRenderer(facesContext, component);
418        HtmlStyleMap style = (HtmlStyleMap) component.getAttributes().get(ATTR_STYLE);
419        Integer styleSpace = null;
420        try {
421          styleSpace = style.getInt(width ? "width" : "height");
422        } catch (Exception e) {
423          /* ignore */
424        }
425        if (styleSpace != null) {
426          int bodySpace = 0;
427          int headerSpace = 0;
428          if (!width) {
429            if (renderer != null) {
430              headerSpace = renderer.getHeaderHeight(facesContext, component);
431            }
432            bodySpace = styleSpace - headerSpace;
433          }
434          HtmlStyleMap headerStyle = ensureStyleAttributeMap(component, ATTR_STYLE_HEADER);
435          HtmlStyleMap bodyStyle = ensureStyleAttributeMap(component, ATTR_STYLE_BODY);
436          if (width) {
437            headerStyle.put("width", styleSpace);
438            bodyStyle.put("width", styleSpace);
439          } else {
440            headerStyle.put("height", headerSpace);
441            bodyStyle.put("height", bodySpace);
442          }
443        }
444      }
445    
446      /**
447       * @deprecated Please use StyleClasses.ensureStyleClasses(component).updateClassAttribute(renderer, component);
448       */
449      @Deprecated
450      public static void updateClassAttribute(String cssClass, String rendererName, UIComponent component) {
451        throw new UnsupportedOperationException(
452            "Please use StyleClasses.ensureStyleClasses(component).updateClassAttribute(renderer, component)");
453      }
454    
455      /**
456       * @deprecated Please use StyleClasses.addMarkupClass()
457       */
458      @Deprecated
459      public static void addMarkupClass(UIComponent component, String rendererName,
460          String subComponent, StringBuilder tobagoClass) {
461        throw new UnsupportedOperationException("Please use StyleClasses.addMarkupClass()");
462      }
463    
464      /**
465       * @deprecated Please use StyleClasses.addMarkupClass()
466       */
467      @Deprecated
468      public static void addMarkupClass(UIComponent component, String rendererName, StyleClasses classes) {
469        classes.addMarkupClass(component, rendererName);
470      }
471    
472      public static void addImageSources(FacesContext facesContext, TobagoResponseWriter writer, String src, String id)
473          throws IOException {
474        StringBuilder buffer = new StringBuilder();
475        buffer.append("new Tobago.Image('");
476        buffer.append(id);
477        buffer.append("','");
478        buffer.append(ResourceManagerUtil.getImageWithPath(facesContext, src, false));
479        buffer.append("','");
480        buffer.append(ResourceManagerUtil.getImageWithPath(facesContext, createSrc(src, "Disabled"), true));
481        buffer.append("','");
482        buffer.append(ResourceManagerUtil.getImageWithPath(facesContext, createSrc(src, "Hover"), true));
483        buffer.append("');");
484        writer.writeJavascript(buffer.toString());
485      }
486    
487      public static String createSrc(String src, String ext) {
488        int dot = src.lastIndexOf('.');
489        if (dot == -1) {
490          LOG.warn("Image src without extension: '" + src + "'");
491          return src;
492        } else {
493          return src.substring(0, dot) + ext + src.substring(dot);
494        }
495      }
496    
497      public static TobagoResponseWriter getTobagoResponseWriter(FacesContext facesContext) {
498    
499        ResponseWriter writer = facesContext.getResponseWriter();
500        if (writer instanceof TobagoResponseWriter) {
501          return (TobagoResponseWriter) writer;
502        } else {
503          return new TobagoResponseWriterWrapper(writer);
504        }
505      }
506    
507      /**
508       * @deprecated use TobagoResponseWriter.writeJavascript()
509       */
510      @Deprecated
511      public static void writeJavascript(ResponseWriter writer, String script) throws IOException {
512        startJavascript(writer);
513        writer.write(script);
514        endJavascript(writer);
515      }
516    
517      /**
518       * @deprecated use TobagoResponseWriter.writeJavascript()
519       */
520      @Deprecated
521      public static void startJavascript(ResponseWriter writer) throws IOException {
522        writer.startElement(HtmlConstants.SCRIPT, null);
523        writer.writeAttribute(HtmlAttributes.TYPE, "text/javascript", null);
524        writer.write("\n<!--\n");
525      }
526    
527      /**
528       * @deprecated use TobagoResponseWriter.writeJavascript()
529       */
530      @Deprecated
531      public static void endJavascript(ResponseWriter writer) throws IOException {
532        writer.write("\n// -->\n");
533        writer.endElement(HtmlConstants.SCRIPT);
534      }
535    
536      public static void writeScriptLoader(FacesContext facesContext, String script)
537          throws IOException {
538        writeScriptLoader(facesContext, new String[]{script}, null);
539      }
540    
541      public static void writeScriptLoader(FacesContext facesContext, String[] scripts, String[] afterLoadCmds)
542          throws IOException {
543        TobagoResponseWriter writer = HtmlRendererUtil.getTobagoResponseWriter(facesContext);
544    
545        String allScripts = "[]";
546        if (scripts != null) {
547          allScripts = ResourceManagerUtil.getScriptsAsJSArray(facesContext, scripts);
548        }
549    
550        StringBuilder script = new StringBuilder();
551        script.append("new Tobago.ScriptLoader(\n    ");
552        script.append(allScripts);
553    
554        if (afterLoadCmds != null && afterLoadCmds.length > 0) {
555          script.append(", \n");
556          boolean first = true;
557          for (String afterLoadCmd : afterLoadCmds) {
558            String[] splittedStrings = StringUtils.split(afterLoadCmd, '\n'); // split on <CR> to have nicer JS
559            for (String splitted : splittedStrings) {
560              String cmd = StringUtils.replace(splitted, "\\", "\\\\");
561              cmd = StringUtils.replace(cmd, "\"", "\\\"");
562              script.append(first ? "          " : "        + ");
563              script.append("\"");
564              script.append(cmd);
565              script.append("\"\n");
566              first = false;
567            }
568          }
569        }
570        script.append(");");
571    
572        writer.writeJavascript(script.toString());
573      }
574    
575      public static void writeStyleLoader(
576          FacesContext facesContext, String[] styles) throws IOException {
577        TobagoResponseWriter writer = HtmlRendererUtil.getTobagoResponseWriter(facesContext);
578    
579        StringBuilder builder = new StringBuilder();
580        builder.append("Tobago.ensureStyleFiles(\n    ");
581        builder.append(ResourceManagerUtil.getStylesAsJSArray(facesContext, styles));
582        builder.append(");");
583        writer.writeJavascript(builder.toString());
584      }
585    
586      public static String getTitleFromTipAndMessages(FacesContext facesContext, UIComponent component) {
587        String messages = ComponentUtil.getFacesMessageAsString(facesContext, component);
588        return HtmlRendererUtil.addTip(messages, component.getAttributes().get(ATTR_TIP));
589      }
590    
591      public static String addTip(String title, Object tip) {
592        if (tip != null) {
593          if (title != null && title.length() > 0) {
594            title += " :: ";
595          } else {
596            title = "";
597          }
598          title += tip;
599        }
600        return title;
601      }
602    
603      public static void renderSelectItems(UIInput component, List<SelectItem> items, Object[] values,
604          TobagoResponseWriter writer, FacesContext facesContext) throws IOException {
605    
606        if (LOG.isDebugEnabled()) {
607          LOG.debug("value = '" + Arrays.toString(values) + "'");
608        }
609        for (SelectItem item : items) {
610          if (item instanceof SelectItemGroup) {
611            writer.startElement(HtmlConstants.OPTGROUP, null);
612            writer.writeAttribute(HtmlAttributes.LABEL, item.getLabel(), true);
613            if (item.isDisabled()) {
614              writer.writeAttribute(HtmlAttributes.DISABLED, true);
615            }
616            SelectItem[] selectItems = ((SelectItemGroup) item).getSelectItems();
617            renderSelectItems(component, Arrays.asList(selectItems), values, writer, facesContext);
618            writer.endElement(HtmlConstants.OPTGROUP);
619          } else {
620            writer.startElement(HtmlConstants.OPTION, null);
621            Object itemValue = item.getValue();
622            // when using selectItem tag with a literal value: use the converted value
623            if (itemValue instanceof String && values != null && values.length > 0 && !(values[0] instanceof String)) {
624              itemValue = ComponentUtil.getConvertedValue(facesContext, component, (String) itemValue);
625            }
626            String formattedValue = RenderUtil.getFormattedValue(facesContext, component, itemValue);
627            writer.writeAttribute(HtmlAttributes.VALUE, formattedValue, true);
628            if (item instanceof org.apache.myfaces.tobago.model.SelectItem) {
629              String image = ((org.apache.myfaces.tobago.model.SelectItem) item).getImage();
630              if (image != null) {
631                String imagePath = ResourceManagerUtil.getImageWithPath(facesContext, image);
632                writer.writeStyleAttribute("background-image: url('" + imagePath + "')");
633              }
634            }
635            if (item instanceof SupportsMarkup) {
636              StyleClasses optionStyle = new StyleClasses();
637              optionStyle.addMarkupClass((SupportsMarkup) item, getRendererName(facesContext, component), "option");
638              writer.writeClassAttribute(optionStyle);
639            }
640            if (RenderUtil.contains(values, itemValue)) {
641              writer.writeAttribute(HtmlAttributes.SELECTED, true);
642            }
643            if (item.isDisabled()) {
644              writer.writeAttribute(HtmlAttributes.DISABLED, true);
645            }
646            writer.writeText(item.getLabel());
647            writer.endElement(HtmlConstants.OPTION);
648          }
649        }
650      }
651    
652      public static String getComponentId(FacesContext context, UIComponent component, String componentId) {
653        UIComponent partiallyComponent = ComponentUtil.findComponent(component, componentId);
654        if (partiallyComponent != null) {
655          String clientId = partiallyComponent.getClientId(context);
656          if (partiallyComponent instanceof UIData) {
657            int rowIndex = ((UIData) partiallyComponent).getRowIndex();
658            if (rowIndex >= 0 && clientId.endsWith(Integer.toString(rowIndex))) {
659              return clientId.substring(0, clientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR));
660            }
661          }
662          return clientId;
663        }
664        LOG.error("No Component found for id " + componentId + " search base component " + component.getClientId(context));
665        return null;
666      }
667    
668      public static String toStyleString(String key, Integer value) {
669        StringBuilder buf = new StringBuilder();
670        buf.append(key);
671        buf.append(":");
672        buf.append(value);
673        buf.append("px; ");
674        return buf.toString();
675      }
676    
677      public static String toStyleString(String key, String value) {
678        StringBuilder buf = new StringBuilder();
679        buf.append(key);
680        buf.append(":");
681        buf.append(value);
682        buf.append("; ");
683        return buf.toString();
684      }
685    
686      public static void renderTip(UIComponent component, TobagoResponseWriter writer) throws IOException {
687        Object objTip = component.getAttributes().get(ATTR_TIP);
688        if (objTip != null) {
689          writer.writeAttribute(HtmlAttributes.TITLE, String.valueOf(objTip), true);
690        }
691      }
692    
693      public static void renderImageTip(UIComponent component, TobagoResponseWriter writer) throws IOException {
694        Object objTip = component.getAttributes().get(ATTR_TIP);
695        if (objTip != null) {
696          writer.writeAttribute(HtmlAttributes.ALT, String.valueOf(objTip), true);
697        } else {
698          writer.writeAttribute(HtmlAttributes.ALT, "", false);
699        }
700      }
701    
702      public static String getJavascriptString(String str) {
703        if (str != null) {
704          return "\"" + str + "\"";
705        }
706        return null;
707      }
708    
709      public static String getRenderedPartiallyJavascriptArray(FacesContext facesContext, UICommand command) {
710        if (command == null) {
711          return null;
712        }
713        String[] list = command.getRenderedPartially();
714        StringBuilder strBuilder = new StringBuilder();
715        strBuilder.append("[");
716        for (int i = 0; i < list.length; i++) {
717          if (i != 0) {
718            strBuilder.append(",");
719          }
720          strBuilder.append("\"");
721          strBuilder.append(HtmlRendererUtil.getComponentId(facesContext, command, list[i]));
722          strBuilder.append("\"");
723        }
724        strBuilder.append("]");
725        return strBuilder.toString();
726      }
727    
728      public static String getJavascriptArray(String[] list) {
729        StringBuilder strBuilder = new StringBuilder();
730        strBuilder.append("[");
731        for (int i = 0; i < list.length; i++) {
732          if (i != 0) {
733            strBuilder.append(",");
734          }
735          strBuilder.append("\"");
736          strBuilder.append(list[i]);
737          strBuilder.append("\"");
738        }
739        strBuilder.append("]");
740        return strBuilder.toString();
741      }
742    
743      public static void removeStyleClasses(UIComponent cell) {
744        Object obj = cell.getAttributes().get(org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_CLASS);
745        if (obj != null && obj instanceof StyleClasses && cell.getRendererType() != null) {
746          StyleClasses styleClasses = (StyleClasses) obj;
747          if (!styleClasses.isEmpty()) {
748            String rendererName = cell.getRendererType().substring(0, 1).toLowerCase(Locale.ENGLISH)
749                + cell.getRendererType().substring(1);
750            styleClasses.removeTobagoClasses(rendererName);
751          }
752          if (styleClasses.isEmpty()) {
753            cell.getAttributes().remove(org.apache.myfaces.tobago.TobagoConstants.ATTR_STYLE_CLASS);
754          }
755        }
756      }
757    }