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