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