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 }