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