001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.gwt.client.util; 029 030import org.opencms.gwt.client.CmsEditableDataJSO; 031import org.opencms.gwt.client.I_CmsDescendantResizeHandler; 032import org.opencms.gwt.client.Messages; 033import org.opencms.gwt.client.ui.CmsAlertDialog; 034import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; 035import org.opencms.gwt.client.util.impl.DOMImpl; 036import org.opencms.gwt.client.util.impl.DocumentStyleImpl; 037import org.opencms.gwt.shared.CmsGwtConstants; 038import org.opencms.util.CmsStringUtil; 039 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.HashMap; 043import java.util.List; 044import java.util.Map; 045import java.util.Map.Entry; 046 047import com.google.common.collect.Lists; 048import com.google.gwt.animation.client.Animation; 049import com.google.gwt.core.client.GWT; 050import com.google.gwt.core.client.JavaScriptObject; 051import com.google.gwt.dom.client.Document; 052import com.google.gwt.dom.client.Element; 053import com.google.gwt.dom.client.FormElement; 054import com.google.gwt.dom.client.InputElement; 055import com.google.gwt.dom.client.NativeEvent; 056import com.google.gwt.dom.client.Node; 057import com.google.gwt.dom.client.NodeList; 058import com.google.gwt.dom.client.Style.Overflow; 059import com.google.gwt.dom.client.Style.Position; 060import com.google.gwt.dom.client.Style.Unit; 061import com.google.gwt.event.dom.client.DomEvent; 062import com.google.gwt.event.shared.HasHandlers; 063import com.google.gwt.user.client.DOM; 064import com.google.gwt.user.client.ui.FlowPanel; 065import com.google.gwt.user.client.ui.HasHorizontalAlignment; 066import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant; 067import com.google.gwt.user.client.ui.RootPanel; 068import com.google.gwt.user.client.ui.Widget; 069 070/** 071 * Utility class to access the HTML DOM.<p> 072 * 073 * @since 8.0.0 074 */ 075public final class CmsDomUtil { 076 077 /** 078 * HTML tag attributes.<p> 079 */ 080 public static enum Attribute { 081 082 /** class. */ 083 clazz { 084 085 /** 086 * @see java.lang.Enum#toString() 087 */ 088 @Override 089 public String toString() { 090 091 return "class"; 092 } 093 }, 094 095 /** title. */ 096 title; 097 } 098 099 /** 100 * Helper class to encapsulate an attribute/value pair.<p> 101 */ 102 public static class AttributeValue { 103 104 /** The attribute. */ 105 private Attribute m_attr; 106 107 /** The attribute value. */ 108 private String m_value; 109 110 /** 111 * Constructor.<p> 112 * 113 * @param attr the attribute 114 */ 115 public AttributeValue(Attribute attr) { 116 117 this(attr, null); 118 } 119 120 /** 121 * Constructor.<p> 122 * 123 * @param attr the attribute 124 * @param value the value 125 */ 126 public AttributeValue(Attribute attr, String value) { 127 128 m_attr = attr; 129 m_value = value; 130 } 131 132 /** 133 * Returns the attribute.<p> 134 * 135 * @return the attribute 136 */ 137 public Attribute getAttr() { 138 139 return m_attr; 140 } 141 142 /** 143 * Returns the value.<p> 144 * 145 * @return the value 146 */ 147 public String getValue() { 148 149 return m_value; 150 } 151 152 /** 153 * Sets the value.<p> 154 * 155 * @param value the value to set 156 */ 157 public void setValue(String value) { 158 159 m_value = value; 160 } 161 162 /** 163 * @see java.lang.Object#toString() 164 */ 165 @Override 166 public String toString() { 167 168 StringBuffer sb = new StringBuffer(); 169 sb.append(m_attr.toString()); 170 if (m_value != null) { 171 sb.append("=\"").append(m_value).append("\""); 172 } 173 return sb.toString(); 174 } 175 } 176 177 /** 178 * CSS Colors.<p> 179 */ 180 public static enum Color { 181 182 /** CSS Color. */ 183 red; 184 } 185 186 /** 187 * HTML entities.<p> 188 */ 189 public static enum Entity { 190 191 /** non-breaking space. */ 192 hellip, 193 194 /** non-breaking space. */ 195 nbsp; 196 197 /** 198 * Returns the HTML code for this entity.<p> 199 * 200 * @return the HTML code for this entity 201 */ 202 public String html() { 203 204 return "&" + super.name() + ";"; 205 } 206 } 207 208 /** Form methods. */ 209 public static enum Method { 210 211 /** The get method. */ 212 get, 213 /** The post method. */ 214 post; 215 } 216 217 /** 218 * CSS Properties.<p> 219 */ 220 public static enum Style { 221 222 /** CSS Property. */ 223 backgroundColor, 224 225 /** CSS Property. */ 226 backgroundImage, 227 228 /** CSS property. */ 229 borderLeftWidth, 230 231 /** CSS property. */ 232 borderRightWidth, 233 234 /** CSS Property. */ 235 borderStyle, 236 237 /** CSS property. */ 238 boxSizing, 239 240 /** CSS Property. */ 241 display, 242 243 /** CSS Property. */ 244 floatCss { 245 246 /** 247 * @see java.lang.Enum#toString() 248 */ 249 @Override 250 public String toString() { 251 252 return "float"; 253 } 254 }, 255 256 /** CSS Property. */ 257 fontFamily, 258 259 /** CSS Property. */ 260 fontSize, 261 262 /** CSS Property. */ 263 fontSizeAdjust, 264 265 /** CSS Property. */ 266 fontStretch, 267 268 /** CSS Property. */ 269 fontStyle, 270 271 /** CSS Property. */ 272 fontVariant, 273 274 /** CSS Property. */ 275 fontWeight, 276 277 /** CSS Property. */ 278 height, 279 280 /** CSS Property. */ 281 left, 282 283 /** CSS Property. */ 284 letterSpacing, 285 286 /** CSS Property. */ 287 lineHeight, 288 289 /** CSS Property. */ 290 marginBottom, 291 292 /** CSS Property. */ 293 marginTop, 294 295 /** CSS Property. */ 296 maxHeight, 297 298 /** CSS Property. */ 299 minHeight, 300 301 /** CSS Property. */ 302 opacity, 303 304 /** CSS Property. */ 305 overflow, 306 307 /** CSS Property. */ 308 padding, 309 310 /** CSS property. */ 311 paddingLeft, 312 313 /** CSS property. */ 314 paddingRight, 315 316 /** CSS Property. */ 317 position, 318 319 /** CSS Property. */ 320 right, 321 322 /** CSS Property. */ 323 textAlign, 324 325 /** CSS Property. */ 326 textDecoration, 327 328 /** CSS Property. */ 329 textIndent, 330 331 /** CSS Property. */ 332 textShadow, 333 334 /** CSS Property. */ 335 textTransform, 336 337 /** CSS Property. */ 338 top, 339 340 /** CSS Property. */ 341 visibility, 342 343 /** CSS Property. */ 344 whiteSpace, 345 346 /** CSS Property. */ 347 width, 348 349 /** CSS Property. */ 350 wordSpacing, 351 352 /** CSS Property. */ 353 wordWrap, 354 355 /** CSS Property. */ 356 zIndex; 357 358 } 359 360 /** 361 * CSS Property values.<p> 362 */ 363 public static enum StyleValue { 364 365 /** CSS Property value. */ 366 absolute, 367 368 /** CSS Property value. */ 369 auto, 370 371 /** CSS Property value. */ 372 hidden, 373 374 /** CSS Property value. */ 375 inherit, 376 377 /** CSS Property value. */ 378 none, 379 380 /** CSS Property value. */ 381 normal, 382 383 /** CSS Property value. */ 384 nowrap, 385 386 /** CSS Property value. */ 387 transparent; 388 } 389 390 /** 391 * HTML Tags.<p> 392 */ 393 public static enum Tag { 394 395 /** HTML Tag. */ 396 a, 397 398 /** HTML Tag. */ 399 ALL { 400 401 /** 402 * @see java.lang.Enum#toString() 403 */ 404 @Override 405 public String toString() { 406 407 return "*"; 408 } 409 }, 410 411 /** HTML Tag. */ 412 b, 413 414 /** HTML Tag. */ 415 body, 416 417 /** HTML Tag. */ 418 div, 419 420 /** HTML Tag. */ 421 h1, 422 423 /** HTML Tag. */ 424 h2, 425 426 /** HTML Tag. */ 427 h3, 428 429 /** HTML Tag. */ 430 h4, 431 432 /** HTML-Tag. */ 433 iframe, 434 435 /** HTML Tag. */ 436 li, 437 438 /** HTML Tag. */ 439 p, 440 441 /** HTML Tag. */ 442 script, 443 444 /** HTML Tag. */ 445 span, 446 447 /** HTML Tag. */ 448 table, 449 450 /** HTML Tag. */ 451 ul; 452 } 453 454 /** Enumeration of link/form targets. */ 455 public static enum Target { 456 457 /** Target blank. */ 458 BLANK("_blank"), 459 460 /** Unspecified target. */ 461 NONE(""), 462 463 /** Target parent. */ 464 PARENT("_parent"), 465 466 /** Target self. */ 467 SELF("_self"), 468 469 /** Target top. */ 470 TOP("_top"); 471 472 /** The target representation. */ 473 private String m_representation; 474 475 /** 476 * Constructor.<p> 477 * 478 * @param representation the target representation 479 */ 480 Target(String representation) { 481 482 m_representation = representation; 483 } 484 485 /** 486 * Returns the target representation.<p> 487 * @return the target representation 488 */ 489 public String getRepresentation() { 490 491 return m_representation; 492 } 493 } 494 495 /** Browser dependent DOM implementation. */ 496 private static DOMImpl domImpl; 497 498 /** The dynamic style sheet object. */ 499 private static JavaScriptObject m_dynamicStyleSheet; 500 501 /** Stores the scroll bar width measurement. */ 502 private static int m_scrollbarWidth = -1; 503 504 /** Browser dependent style implementation. */ 505 private static DocumentStyleImpl styleImpl; 506 507 /** 508 * Hidden constructor.<p> 509 */ 510 private CmsDomUtil() { 511 512 // doing nothing 513 } 514 515 /** 516 * Adds an overlay div to the element.<p> 517 * 518 * @param element the element 519 */ 520 public static void addDisablingOverlay(Element element) { 521 522 Element overlay = DOM.createDiv(); 523 overlay.addClassName(I_CmsLayoutBundle.INSTANCE.generalCss().disablingOverlay()); 524 element.getStyle().setPosition(Position.RELATIVE); 525 element.appendChild(overlay); 526 } 527 528 /** 529 * Adds a CSS style rule to a dynamically inserted style sheet.<p> 530 * 531 * @param rule the style rule 532 */ 533 public static native void addDynamicStyleRule(String rule) /*-{ 534 var style = @org.opencms.gwt.client.util.CmsDomUtil::m_dynamicStyleSheet; 535 if (style == null) { 536 var style = $wnd.document.createElement("style"); 537 style.appendChild($wnd.document.createTextNode("")); 538 $wnd.document.head.appendChild(style); 539 @org.opencms.gwt.client.util.CmsDomUtil::m_dynamicStyleSheet = style; 540 } 541 style.sheet.insertRule(rule, 0); 542 }-*/; 543 544 /** 545 * Returns if the given client position is over the given element.<p> 546 * Use <code>-1</code> for x or y to ignore one ordering orientation.<p> 547 * 548 * @param element the element 549 * @param x the client x position, use <code>-1</code> to ignore x position 550 * @param y the client y position, use <code>-1</code> to ignore y position 551 * 552 * @return <code>true</code> if the given position is over the given element 553 */ 554 public static boolean checkPositionInside(Element element, int x, int y) { 555 556 // ignore x / left-right values for x == -1 557 if (x != -1) { 558 // check if the mouse pointer is within the width of the target 559 int left = CmsDomUtil.getRelativeX(x, element); 560 int offsetWidth = element.getOffsetWidth(); 561 if ((left <= 0) || (left >= offsetWidth)) { 562 return false; 563 } 564 } 565 // ignore y / top-bottom values for y == -1 566 if (y != -1) { 567 // check if the mouse pointer is within the height of the target 568 int top = CmsDomUtil.getRelativeY(y, element); 569 int offsetHeight = element.getOffsetHeight(); 570 if ((top <= 0) || (top >= offsetHeight)) { 571 return false; 572 } 573 } 574 return true; 575 } 576 577 /** 578 * Clears the elements hover state by removing it from the DOM and re-attaching it.<p> 579 * 580 * @param element the element 581 */ 582 public static void clearHover(Element element) { 583 584 Element parent = element.getParentElement(); 585 Element sibling = element.getNextSiblingElement(); 586 element.removeFromParent(); 587 parent.insertBefore(element, sibling); 588 } 589 590 /** 591 * Removes the opacity attribute from the element's inline-style.<p> 592 * 593 * @param element the DOM element to manipulate 594 */ 595 public static void clearOpacity(Element element) { 596 597 getStyleImpl().clearOpacity(element); 598 } 599 600 /** 601 * Clones the given element.<p> 602 * 603 * It creates a new element with the same tag, and sets the class attribute, 604 * and sets the innerHTML.<p> 605 * 606 * @param element the element to clone 607 * 608 * @return the cloned element 609 */ 610 public static Element clone(Element element) { 611 612 Element elementClone = DOM.createElement(element.getTagName()); 613 elementClone.setClassName(element.getClassName()); 614 elementClone.setInnerHTML(element.getInnerHTML()); 615 return elementClone; 616 } 617 618 /** 619 * Generates a closing tag.<p> 620 * 621 * @param tag the tag to use 622 * 623 * @return HTML code 624 */ 625 public static String close(Tag tag) { 626 627 return "</" + tag.name() + ">"; 628 } 629 630 /** 631 * Copy the text content of the matching element to the clip-board.<p> 632 * 633 * @param selector the query selector matching the target element 634 * 635 * @return in case the command was executed successfully 636 */ 637 public static native boolean copyToClipboard(String selector)/*-{ 638 639 var doc = $wnd.document; 640 var targetElement = doc.querySelector(selector); 641 if (targetElement != null) { 642 var textAreaAdded = false; 643 var textArea; 644 if ("TEXTAREA" == targetElement.tagName) { 645 textArea = targetElement; 646 } else { 647 textAreaAdded = true; 648 var text = targetElement.textContent; 649 textArea = document.createElement("textarea"); 650 651 // add some styles to hide the text area 652 textArea.style.position = 'fixed'; 653 textArea.style.top = 0; 654 textArea.style.left = 0; 655 textArea.style.width = '2em'; 656 textArea.style.height = '2em'; 657 textArea.style.padding = 0; 658 textArea.style.border = 'none'; 659 textArea.style.outline = 'none'; 660 textArea.style.boxShadow = 'none'; 661 textArea.style.background = 'transparent'; 662 textArea.style.color = 'transparent'; 663 textArea.value = text; 664 665 document.body.appendChild(textArea); 666 } 667 textArea.select(); 668 var result = false; 669 try { 670 result = document.execCommand('copy'); 671 } catch (err) { 672 } 673 if (textAreaAdded) { 674 document.body.removeChild(textArea); 675 } else { 676 // remove selection 677 textArea.selectionStart = textArea.selectionEnd; 678 } 679 return result; 680 } 681 }-*/; 682 683 /** 684 * This method will create an {@link com.google.gwt.dom.client.Element} for the given HTML. 685 * The HTML should have a single root tag, if not, the first tag will be used and all others discarded.<p> 686 * Script-tags will be removed.<p> 687 * 688 * @param html the HTML to use for the element 689 * 690 * @return the created element 691 * 692 * @throws Exception if something goes wrong 693 */ 694 public static Element createElement(String html) throws Exception { 695 696 Element wrapperDiv = DOM.createDiv(); 697 wrapperDiv.setInnerHTML(html); 698 Element elementRoot = wrapperDiv.getFirstChildElement(); 699 wrapperDiv.removeChild(elementRoot); 700 // just in case we have a script tag outside the root HTML-tag 701 while ((elementRoot != null) && (elementRoot.getTagName().toLowerCase().equals(Tag.script.name()))) { 702 elementRoot = wrapperDiv.getFirstChildElement(); 703 wrapperDiv.removeChild(elementRoot); 704 } 705 if (elementRoot == null) { 706 CmsDebugLog.getInstance().printLine( 707 "Could not create element as the given HTML has no appropriate root element"); 708 throw new IllegalArgumentException( 709 "Could not create element as the given HTML has no appropriate root element"); 710 } 711 return elementRoot; 712 } 713 714 /** 715 * Convenience method to assemble the HTML to use for a button face.<p> 716 * 717 * @param text text the up face text to set, set to <code>null</code> to not show any 718 * @param imageClass the up face image class to use, set to <code>null</code> to not show any 719 * @param align the alignment of the text in reference to the image 720 * 721 * @return the HTML 722 */ 723 public static String createFaceHtml(String text, String imageClass, HorizontalAlignmentConstant align) { 724 725 StringBuffer sb = new StringBuffer(); 726 if (align == HasHorizontalAlignment.ALIGN_LEFT) { 727 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(text)) { 728 sb.append(text.trim()); 729 } 730 } 731 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(imageClass)) { 732 String clazz = imageClass; 733 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(text)) { 734 if (align == HasHorizontalAlignment.ALIGN_LEFT) { 735 clazz += " " + I_CmsLayoutBundle.INSTANCE.buttonCss().spacerLeft(); 736 } else { 737 clazz += " " + I_CmsLayoutBundle.INSTANCE.buttonCss().spacerRight(); 738 } 739 } 740 AttributeValue attr = new AttributeValue(Attribute.clazz, clazz); 741 sb.append(enclose(Tag.span, "", attr)); 742 } 743 if (align == HasHorizontalAlignment.ALIGN_RIGHT) { 744 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(text)) { 745 sb.append(text.trim()); 746 } 747 } 748 return sb.toString(); 749 } 750 751 /** 752 * Creates an iFrame element with the given name attribute.<p> 753 * 754 * @param name the name attribute value 755 * 756 * @return the iFrame element 757 */ 758 public static com.google.gwt.dom.client.Element createIFrameElement(String name) { 759 760 return getDOMImpl().createIFrameElement(Document.get(), name); 761 } 762 763 /** 764 * Encloses the given text with the given tag.<p> 765 * 766 * @param tag the tag to use 767 * @param text the text to enclose 768 * @param attrs the optional tag attributes 769 * 770 * @return HTML code 771 */ 772 public static String enclose(Tag tag, String text, AttributeValue... attrs) { 773 774 return open(tag, attrs) + text + close(tag); 775 } 776 777 /** 778 * Ensures a script tag is present within the window document context.<p> 779 * 780 * @param javascriptLink the link to the java script resource 781 */ 782 public static void ensureJavaScriptIncluded(String javascriptLink) { 783 784 ensureJavaScriptIncluded(javascriptLink, true); 785 } 786 787 /** 788 * Ensures a script tag is present within the window document context.<p> 789 * 790 * @param javascriptLink the link to the java script resource 791 */ 792 public static void ensureJavaScriptIncluded(String javascriptLink, boolean async) { 793 794 if (!isJavaScriptPresent(javascriptLink)) { 795 injectScript(javascriptLink, async, null); 796 } 797 } 798 799 /** 800 * Ensures a script tag is present within the window document context.<p> 801 * 802 * @param javascriptLink the link to the java script resource 803 */ 804 public static void ensureJavaScriptIncluded(String javascriptLink, boolean async, JavaScriptObject callback) { 805 806 if (!isJavaScriptPresent(javascriptLink)) { 807 injectScript(javascriptLink, async, callback); 808 } 809 } 810 811 /** 812 * Triggers a mouse-out event for the given element.<p> 813 * 814 * Useful in case something is capturing all events.<p> 815 * 816 * @param element the element to use 817 */ 818 public static void ensureMouseOut(Element element) { 819 820 NativeEvent nativeEvent = Document.get().createMouseOutEvent( 821 0, 822 0, 823 0, 824 0, 825 0, 826 false, 827 false, 828 false, 829 false, 830 0, 831 null); 832 element.dispatchEvent(nativeEvent); 833 } 834 835 /** 836 * Triggers a mouse-out event for the given target.<p> 837 * 838 * Useful in case something is capturing all events.<p> 839 * 840 * @param target the target to use 841 */ 842 public static void ensureMouseOut(HasHandlers target) { 843 844 NativeEvent nativeEvent = Document.get().createMouseOutEvent( 845 0, 846 0, 847 0, 848 0, 849 0, 850 false, 851 false, 852 false, 853 false, 854 0, 855 null); 856 DomEvent.fireNativeEvent(nativeEvent, target); 857 } 858 859 /** 860 * Triggers a mouse-over event for the given element.<p> 861 * 862 * Useful in case something is capturing all events.<p> 863 * 864 * @param element the element to use 865 */ 866 public static void ensureMouseOver(Element element) { 867 868 NativeEvent nativeEvent = Document.get().createMouseOverEvent( 869 0, 870 0, 871 0, 872 0, 873 0, 874 false, 875 false, 876 false, 877 false, 878 0, 879 null); 880 element.dispatchEvent(nativeEvent); 881 } 882 883 /** 884 * Checks the window.document for given style-sheet and includes it if required.<p> 885 * 886 * @param styleSheetLink the style-sheet link 887 */ 888 public static native void ensureStyleSheetIncluded(String styleSheetLink)/*-{ 889 var styles = $wnd.document.styleSheets; 890 for (var i = 0; i < styles.length; i++) { 891 if (styles[i].href != null 892 && styles[i].href.indexOf(styleSheetLink) >= 0) { 893 // style-sheet is present 894 return; 895 } 896 } 897 // include style-sheet into head 898 var headID = $wnd.document.getElementsByTagName("head")[0]; 899 var cssNode = $wnd.document.createElement('link'); 900 cssNode.type = 'text/css'; 901 cssNode.rel = 'stylesheet'; 902 cssNode.href = styleSheetLink; 903 headID.appendChild(cssNode); 904 }-*/; 905 906 /** 907 * Ensures that the given element is visible.<p> 908 * 909 * Assuming the scrollbars are on the container element, and that the element is a child of the container element.<p> 910 * 911 * @param containerElement the container element, has to be parent of the element 912 * @param element the element to be seen 913 * @param animationTime the animation time for scrolling, use zero for no animation 914 */ 915 public static void ensureVisible(final Element containerElement, Element element, int animationTime) { 916 917 Element item = element; 918 int realOffset = 0; 919 while ((item != null) && (item != containerElement)) { 920 realOffset += element.getOffsetTop(); 921 item = item.getOffsetParent(); 922 } 923 final int endScrollTop = realOffset - (containerElement.getOffsetHeight() / 2); 924 925 if (animationTime <= 0) { 926 // no animation 927 containerElement.setScrollTop(endScrollTop); 928 return; 929 } 930 final int startScrollTop = containerElement.getScrollTop(); 931 (new Animation() { 932 933 /** 934 * @see com.google.gwt.animation.client.Animation#onUpdate(double) 935 */ 936 @Override 937 protected void onUpdate(double progress) { 938 939 containerElement.setScrollTop(startScrollTop + (int)((endScrollTop - startScrollTop) * progress)); 940 } 941 }).run(animationTime); 942 } 943 944 /** 945 * Escapes a String so it may be printed as text content or attribute 946 * value in a HTML page or an XML file.<p> 947 * 948 * This method replaces the following characters in a String: 949 * <ul> 950 * <li><b><</b> with &lt; 951 * <li><b>></b> with &gt; 952 * <li><b>&</b> with &amp; 953 * <li><b>"</b> with &quot; 954 * </ul><p> 955 * 956 * @param source the string to escape 957 * 958 * @return the escaped string 959 */ 960 public static String escapeXml(String source) { 961 962 if (source == null) { 963 return null; 964 } 965 StringBuffer result = new StringBuffer(source.length() * 2); 966 967 for (int i = 0; i < source.length(); ++i) { 968 char ch = source.charAt(i); 969 switch (ch) { 970 case '<': 971 result.append("<"); 972 break; 973 case '>': 974 result.append(">"); 975 break; 976 case '&': 977 // don't escape already escaped international and special characters 978 int terminatorIndex = source.indexOf(";", i); 979 if (terminatorIndex > 0) { 980 if (source.substring(i + 1, terminatorIndex).matches("#[0-9]+")) { 981 result.append(ch); 982 break; 983 } 984 } 985 986 // note that to other "break" in the above "if" block 987 result.append("&"); 988 break; 989 case '"': 990 result.append("""); 991 break; 992 default: 993 result.append(ch); 994 } 995 } 996 return new String(result); 997 } 998 999 /** 1000 * Fires a focus event for the given widget.<p> 1001 * 1002 * @param widget the widget 1003 */ 1004 public static void fireFocusEvent(Widget widget) { 1005 1006 NativeEvent nativeEvent = Document.get().createFocusEvent(); 1007 DomEvent.fireNativeEvent(nativeEvent, widget, widget.getElement()); 1008 } 1009 1010 /** 1011 * Ensures any embedded flash players are set opaque so UI elements may be placed above them.<p> 1012 * 1013 * @param element the element to work on 1014 */ 1015 public static native void fixFlashZindex(Element element)/*-{ 1016 1017 var embeds = element.getElementsByTagName('embed'); 1018 for (i = 0; i < embeds.length; i++) { 1019 embed = embeds[i]; 1020 var new_embed; 1021 // everything but Firefox & Konqueror 1022 if (embed.outerHTML) { 1023 var html = embed.outerHTML; 1024 // replace an existing wmode parameter 1025 if (html.match(/wmode\s*=\s*('|")[a-zA-Z]+('|")/i)) 1026 new_embed = html.replace(/wmode\s*=\s*('|")window('|")/i, 1027 "wmode='transparent'"); 1028 // add a new wmode parameter 1029 else 1030 new_embed = html.replace(/<embed\s/i, 1031 "<embed wmode='transparent' "); 1032 // replace the old embed object with the fixed version 1033 embed.insertAdjacentHTML('beforeBegin', new_embed); 1034 embed.parentNode.removeChild(embed); 1035 } else { 1036 // cloneNode is buggy in some versions of Safari & Opera, but works fine in FF 1037 new_embed = embed.cloneNode(true); 1038 if (!new_embed.getAttribute('wmode') 1039 || new_embed.getAttribute('wmode').toLowerCase() == 'window') 1040 new_embed.setAttribute('wmode', 'transparent'); 1041 embed.parentNode.replaceChild(new_embed, embed); 1042 } 1043 } 1044 // loop through every object tag on the site 1045 var objects = element.getElementsByTagName('object'); 1046 for (i = 0; i < objects.length; i++) { 1047 object = objects[i]; 1048 var new_object; 1049 // object is an IE specific tag so we can use outerHTML here 1050 if (object.outerHTML) { 1051 var html = object.outerHTML; 1052 // replace an existing wmode parameter 1053 if (html 1054 .match(/<param\s+name\s*=\s*('|")wmode('|")\s+value\s*=\s*('|")[a-zA-Z]+('|")\s*\/?\>/i)) 1055 new_object = html 1056 .replace( 1057 /<param\s+name\s*=\s*('|")wmode('|")\s+value\s*=\s*('|")window('|")\s*\/?\>/i, 1058 "<param name='wmode' value='transparent' />"); 1059 // add a new wmode parameter 1060 else 1061 new_object = html 1062 .replace(/<\/object\>/i, 1063 "<param name='wmode' value='transparent' />\n</object>"); 1064 // loop through each of the param tags 1065 var children = object.childNodes; 1066 for (j = 0; j < children.length; j++) { 1067 try { 1068 if (children[j] != null) { 1069 var theName = children[j].getAttribute('name'); 1070 if (theName != null && theName.match(/flashvars/i)) { 1071 new_object = new_object 1072 .replace( 1073 /<param\s+name\s*=\s*('|")flashvars('|")\s+value\s*=\s*('|")[^'"]*('|")\s*\/?\>/i, 1074 "<param name='flashvars' value='" 1075 + children[j] 1076 .getAttribute('value') 1077 + "' />"); 1078 } 1079 } 1080 } catch (err) { 1081 } 1082 } 1083 // replace the old embed object with the fixed versiony 1084 object.insertAdjacentHTML('beforeBegin', new_object); 1085 object.parentNode.removeChild(object); 1086 } 1087 } 1088 1089 }-*/; 1090 1091 /** 1092 * Generates a form element with hidden input fields.<p> 1093 * 1094 * @param action the form action 1095 * @param method the form method 1096 * @param target the form target 1097 * @param values the input values 1098 * 1099 * @return the generated form element 1100 */ 1101 public static FormElement generateHiddenForm( 1102 String action, 1103 Method method, 1104 String target, 1105 Map<String, String> values) { 1106 1107 FormElement formElement = Document.get().createFormElement(); 1108 formElement.setMethod(method.name()); 1109 if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(target)) { 1110 formElement.setTarget(target); 1111 } 1112 formElement.setAction(action); 1113 for (Entry<String, String> input : values.entrySet()) { 1114 formElement.appendChild(createHiddenInput(input.getKey(), input.getValue())); 1115 } 1116 return formElement; 1117 } 1118 1119 /** 1120 * Generates a form element with hidden input fields.<p> 1121 * 1122 * @param action the form action 1123 * @param method the form method 1124 * @param target the form target 1125 * @param values the input values 1126 * 1127 * @return the generated form element 1128 */ 1129 public static FormElement generateHiddenForm( 1130 String action, 1131 Method method, 1132 Target target, 1133 Map<String, String> values) { 1134 1135 return generateHiddenForm(action, method, target.getRepresentation(), values); 1136 } 1137 1138 /** 1139 * Returns the currently focused element.<p> 1140 * 1141 * @return the currently focused element 1142 */ 1143 public static native Element getActiveElement() /*-{ 1144 return $wnd.document.activeElement; 1145 }-*/; 1146 1147 /** 1148 * Gets the edit data for all oc-editable elements in the page.<p> 1149 * 1150 * @return the list of edit data 1151 */ 1152 public static List<CmsEditableDataJSO> getAllEditableDataForPage() { 1153 1154 List<Element> elems = CmsDomUtil.getElementsByClass(CmsGwtConstants.CLASS_EDITABLE, Tag.div); 1155 List<CmsEditableDataJSO> result = Lists.newArrayList(); 1156 for (Element elem : elems) { 1157 String jsonData = elem.getAttribute(CmsGwtConstants.ATTR_DATA_EDITABLE); 1158 CmsEditableDataJSO data = CmsEditableDataJSO.parseEditableData(jsonData); 1159 result.add(data); 1160 } 1161 return result; 1162 } 1163 1164 /** 1165 * Returns the given element or it's closest ancestor with the given class.<p> 1166 * 1167 * Returns <code>null</code> if no appropriate element was found.<p> 1168 * 1169 * @param element the element 1170 * @param className the class name 1171 * 1172 * @return the matching element 1173 */ 1174 public static Element getAncestor(Element element, String className) { 1175 1176 if (hasClass(className, element)) { 1177 return element; 1178 } 1179 if (element.getTagName().equalsIgnoreCase(Tag.body.name())) { 1180 return null; 1181 } 1182 return getAncestor(element.getParentElement(), className); 1183 } 1184 1185 /** 1186 * Returns the given element or it's closest ancestor with the given tag name.<p> 1187 * 1188 * Returns <code>null</code> if no appropriate element was found.<p> 1189 * 1190 * @param element the element 1191 * @param tag the tag name 1192 * 1193 * @return the matching element 1194 */ 1195 public static Element getAncestor(Element element, Tag tag) { 1196 1197 if ((element == null) || (tag == null)) { 1198 return null; 1199 } 1200 if (element.getTagName().equalsIgnoreCase(tag.name())) { 1201 return element; 1202 } 1203 if (element.getTagName().equalsIgnoreCase(Tag.body.name())) { 1204 return null; 1205 } 1206 return getAncestor(element.getParentElement(), tag); 1207 } 1208 1209 /** 1210 * Returns the given element or it's closest ancestor with the given tag and class.<p> 1211 * 1212 * Returns <code>null</code> if no appropriate element was found.<p> 1213 * 1214 * @param element the element 1215 * @param tag the tag name 1216 * @param className the class name 1217 * 1218 * @return the matching element 1219 */ 1220 public static Element getAncestor(Element element, Tag tag, String className) { 1221 1222 if (element.getTagName().equalsIgnoreCase(tag.name()) && hasClass(className, element)) { 1223 return element; 1224 } 1225 if (element.getTagName().equalsIgnoreCase(Tag.body.name())) { 1226 return null; 1227 } 1228 return getAncestor(element.getParentElement(), tag, className); 1229 } 1230 1231 /** 1232 * Returns the computed style of the given element.<p> 1233 * 1234 * @param element the element 1235 * @param style the CSS property 1236 * 1237 * @return the currently computed style 1238 */ 1239 public static String getCurrentStyle(Element element, Style style) { 1240 1241 return getStyleImpl().getCurrentStyle(element, style.toString()); 1242 } 1243 1244 /** 1245 * Returns the computed style of the given element as floating point number.<p> 1246 * 1247 * @param element the element 1248 * @param style the CSS property 1249 * 1250 * @return the currently computed style 1251 */ 1252 public static double getCurrentStyleFloat(Element element, Style style) { 1253 1254 String currentStyle = getCurrentStyle(element, style); 1255 return CmsClientStringUtil.parseFloat(currentStyle); 1256 } 1257 1258 /** 1259 * Returns the computed style of the given element as number.<p> 1260 * 1261 * @param element the element 1262 * @param style the CSS property 1263 * 1264 * @return the currently computed style 1265 */ 1266 public static int getCurrentStyleInt(Element element, Style style) { 1267 1268 String currentStyle = getCurrentStyle(element, style); 1269 return CmsClientStringUtil.parseInt(currentStyle); 1270 } 1271 1272 /** 1273 * Determines the position of the list collector editable content.<p> 1274 * 1275 * @param editable the editable marker tag 1276 * 1277 * @return the position 1278 */ 1279 public static CmsPositionBean getEditablePosition(Element editable) { 1280 1281 CmsPositionBean result = new CmsPositionBean(); 1282 int dummy = -999; 1283 // setting minimum height 1284 result.setHeight(20); 1285 result.setWidth(60); 1286 result.setLeft(dummy); 1287 result.setTop(dummy); 1288 Element sibling = editable.getNextSiblingElement(); 1289 while ((sibling != null) 1290 && !CmsDomUtil.hasClass(CmsGwtConstants.CLASS_EDITABLE, sibling) 1291 && !CmsDomUtil.hasClass(CmsGwtConstants.CLASS_EDITABLE_END, sibling)) { 1292 // only consider element nodes 1293 1294 if ((sibling.getNodeType() == Node.ELEMENT_NODE) 1295 && !sibling.getTagName().equalsIgnoreCase(Tag.script.name())) { 1296 if (!CmsDomUtil.hasClass(CmsGwtConstants.CLASS_EDITABLE_SKIP, sibling)) { 1297 CmsPositionBean siblingPos = CmsPositionBean.generatePositionInfo(sibling); 1298 result.setLeft( 1299 ((result.getLeft() == dummy) || (siblingPos.getLeft() < result.getLeft())) 1300 ? siblingPos.getLeft() 1301 : result.getLeft()); 1302 result.setTop( 1303 ((result.getTop() == dummy) || (siblingPos.getTop() < result.getTop())) 1304 ? siblingPos.getTop() 1305 : result.getTop()); 1306 result.setHeight( 1307 ((result.getTop() + result.getHeight()) > (siblingPos.getTop() + siblingPos.getHeight())) 1308 ? result.getHeight() 1309 : (siblingPos.getTop() + siblingPos.getHeight()) - result.getTop()); 1310 result.setWidth( 1311 ((result.getLeft() + result.getWidth()) > (siblingPos.getLeft() + siblingPos.getWidth())) 1312 ? result.getWidth() 1313 : (siblingPos.getLeft() + siblingPos.getWidth()) - result.getLeft()); 1314 } 1315 } 1316 1317 sibling = sibling.getNextSiblingElement(); 1318 } 1319 if ((result.getTop() == dummy) && (result.getLeft() == dummy)) { 1320 result = CmsPositionBean.generatePositionInfo(editable); 1321 } 1322 if (result.getHeight() == -1) { 1323 // in case no height was set 1324 result = CmsPositionBean.generatePositionInfo(editable); 1325 result.setHeight(20); 1326 result.setWidth((result.getWidth() < 60) ? 60 : result.getWidth()); 1327 } 1328 1329 return result; 1330 } 1331 1332 /** 1333 * Utility method to determine the effective background color.<p> 1334 * 1335 * @param element the element 1336 * 1337 * @return the background color 1338 */ 1339 public static String getEffectiveBackgroundColor(Element element) { 1340 1341 String backgroundColor = CmsDomUtil.getCurrentStyle(element, Style.backgroundColor); 1342 if ((CmsStringUtil.isEmptyOrWhitespaceOnly(backgroundColor) 1343 || isTransparent(backgroundColor) 1344 || backgroundColor.equals(StyleValue.inherit.toString()))) { 1345 1346 if ((Document.get().getBody() != element) && (element.getParentElement() != null)) { 1347 1348 backgroundColor = getEffectiveBackgroundColor(element.getParentElement()); 1349 } else { 1350 // if body element has still no background color set default to white 1351 backgroundColor = "#FFFFFF"; 1352 } 1353 } 1354 1355 return backgroundColor; 1356 } 1357 1358 /** 1359 * Returns all elements from the DOM with the given CSS class.<p> 1360 * 1361 * @param className the class name to look for 1362 * 1363 * @return the matching elements 1364 */ 1365 public static List<Element> getElementsByClass(String className) { 1366 1367 return getElementsByClass(className, Tag.ALL, Document.get().getBody()); 1368 } 1369 1370 /** 1371 * Returns all elements with the given CSS class including the root element.<p> 1372 * 1373 * @param className the class name to look for 1374 * @param rootElement the root element of the search 1375 * 1376 * @return the matching elements 1377 */ 1378 public static List<Element> getElementsByClass(String className, Element rootElement) { 1379 1380 return getElementsByClass(className, Tag.ALL, rootElement); 1381 1382 } 1383 1384 /** 1385 * Returns all elements from the DOM with the given CSS class and tag name.<p> 1386 * 1387 * @param className the class name to look for 1388 * @param tag the tag 1389 * 1390 * @return the matching elements 1391 */ 1392 public static List<Element> getElementsByClass(String className, Tag tag) { 1393 1394 return getElementsByClass(className, tag, Document.get().getBody()); 1395 } 1396 1397 /** 1398 * Returns all elements with the given CSS class and tag name including the root element.<p> 1399 * 1400 * @param className the class name to look for 1401 * @param tag the tag 1402 * @param rootElement the root element of the search 1403 * 1404 * @return the matching elements 1405 */ 1406 public static List<Element> getElementsByClass(String className, Tag tag, Element rootElement) { 1407 1408 if ((rootElement == null) || (className == null) || (className.trim().length() == 0) || (tag == null)) { 1409 return null; 1410 } 1411 className = className.trim(); 1412 List<Element> result = new ArrayList<Element>(); 1413 if (internalHasClass(className, rootElement)) { 1414 result.add(rootElement); 1415 } 1416 NodeList<Element> elements = querySelectorAll(tag + "." + className, rootElement); 1417 for (int i = 0; i < elements.getLength(); i++) { 1418 result.add(elements.getItem(i)); 1419 } 1420 return result; 1421 } 1422 1423 /** 1424 * Returns the first direct child matching the given class name.<p> 1425 * 1426 * @param element the parent element 1427 * @param className the class name to match 1428 * 1429 * @return the child element 1430 */ 1431 public static Element getFirstChildWithClass(Element element, String className) { 1432 1433 NodeList<Node> children = element.getChildNodes(); 1434 1435 for (int i = 0; i < children.getLength(); i++) { 1436 if (children.getItem(i).getNodeType() == Node.ELEMENT_NODE) { 1437 Element child = (Element)children.getItem(i); 1438 if (child.hasClassName(className)) { 1439 return child; 1440 } 1441 } 1442 } 1443 return null; 1444 } 1445 1446 /** 1447 * Gets the root <html> element. 1448 * 1449 * @return the html element 1450 */ 1451 public static native Element getHtmlElement() /*-{ 1452 return $wnd.document.querySelector("html"); 1453 }-*/; 1454 1455 /** 1456 * Returns the content height of the given iFrame element.<p> 1457 * 1458 * @param iframe the iFrame element 1459 * 1460 * @return the content height 1461 */ 1462 public static native int getIFrameContentHeight(Element iframe)/*-{ 1463 var doc = iframe.contentDocument ? iframe.contentDocument 1464 : iframe.contentWindow.document; 1465 var body = doc.body; 1466 var html = doc.documentElement; 1467 var height = Math.max(body.scrollHeight, body.offsetHeight, 1468 html.clientHeight, html.scrollHeight, html.offsetHeight); 1469 return height; 1470 }-*/; 1471 1472 /** 1473 * Returns the element position relative to its siblings.<p> 1474 * 1475 * @param e the element to get the position for 1476 * 1477 * @return the position, or <code>-1</code> if not found 1478 */ 1479 public static int getPosition(Element e) { 1480 1481 NodeList<Node> childNodes = e.getParentElement().getChildNodes(); 1482 for (int i = childNodes.getLength(); i >= 0; i--) { 1483 if (childNodes.getItem(i) == e) { 1484 return i; 1485 } 1486 } 1487 return -1; 1488 } 1489 1490 /** 1491 * Returns the next ancestor to the element with an absolute, fixed or relative position.<p> 1492 * 1493 * @param child the element 1494 * 1495 * @return the positioning parent element (may be <code>null</code>) 1496 */ 1497 public static Element getPositioningParent(Element child) { 1498 1499 Element parent = child.getParentElement(); 1500 while (parent != null) { 1501 String parentPositioning = CmsDomUtil.getCurrentStyle(parent, Style.position); 1502 if (Position.RELATIVE.getCssName().equals(parentPositioning) 1503 || Position.ABSOLUTE.getCssName().equals(parentPositioning) 1504 || Position.FIXED.getCssName().equals(parentPositioning)) { 1505 return parent; 1506 } 1507 parent = parent.getParentElement(); 1508 } 1509 return RootPanel.getBodyElement(); 1510 } 1511 1512 /** 1513 * Gets the horizontal position of the given x-coordinate relative to a given element.<p> 1514 * 1515 * @param x the coordinate to use 1516 * @param target the element whose coordinate system is to be used 1517 * 1518 * @return the relative horizontal position 1519 * 1520 * @see com.google.gwt.event.dom.client.MouseEvent#getRelativeX(com.google.gwt.dom.client.Element) 1521 */ 1522 public static int getRelativeX(int x, Element target) { 1523 1524 return (x - target.getAbsoluteLeft()) + /* target.getScrollLeft() + */target.getOwnerDocument().getScrollLeft(); 1525 } 1526 1527 /** 1528 * Gets the vertical position of the given y-coordinate relative to a given element.<p> 1529 * 1530 * @param y the coordinate to use 1531 * @param target the element whose coordinate system is to be used 1532 * 1533 * @return the relative vertical position 1534 * 1535 * @see com.google.gwt.event.dom.client.MouseEvent#getRelativeY(com.google.gwt.dom.client.Element) 1536 */ 1537 public static int getRelativeY(int y, Element target) { 1538 1539 return (y - target.getAbsoluteTop()) + /* target.getScrollTop() +*/target.getOwnerDocument().getScrollTop(); 1540 } 1541 1542 /** 1543 * Measures the scroll bar width.<p> 1544 * 1545 * @return the scroll bar width 1546 */ 1547 public static int getScrollbarWidth() { 1548 1549 if (m_scrollbarWidth == -1) { 1550 Element div = DOM.createDiv(); 1551 div.setAttribute("style", "width:100px; height:100px; overflow: scroll; position:absolute; top:-9999px;"); 1552 RootPanel.getBodyElement().appendChild(div); 1553 m_scrollbarWidth = div.getOffsetWidth() - div.getClientWidth(); 1554 div.removeFromParent(); 1555 } 1556 return m_scrollbarWidth; 1557 } 1558 1559 /** 1560 * Returns the DOM window object.<p> 1561 * 1562 * @return the DOM window object 1563 */ 1564 public static native JavaScriptObject getWindow() /*-{ 1565 return $wnd; 1566 }-*/; 1567 1568 /** 1569 * Returns the Z index from the given style.<p> 1570 * 1571 * This is a workaround for a bug with {@link com.google.gwt.dom.client.Style#getZIndex()} which occurs with IE in 1572 * hosted mode.<p> 1573 * 1574 * @param style the style object from which the Z index property should be fetched 1575 * 1576 * @return the z index 1577 */ 1578 public static native String getZIndex(com.google.gwt.dom.client.Style style) 1579 /*-{ 1580 return "" + style.zIndex; 1581 }-*/; 1582 1583 /** 1584 * Utility method to determine if the given element has a set background.<p> 1585 * 1586 * @param element the element 1587 * 1588 * @return <code>true</code> if the element has a background set 1589 */ 1590 public static boolean hasBackground(Element element) { 1591 1592 String backgroundColor = CmsDomUtil.getCurrentStyle(element, Style.backgroundColor); 1593 String backgroundImage = CmsDomUtil.getCurrentStyle(element, Style.backgroundImage); 1594 if ((isTransparent(backgroundColor)) 1595 && ((backgroundImage == null) 1596 || (backgroundImage.trim().length() == 0) 1597 || backgroundImage.equals(StyleValue.none.toString()))) { 1598 return false; 1599 } 1600 return true; 1601 } 1602 1603 /** 1604 * Utility method to determine if the given element has a set background image.<p> 1605 * 1606 * @param element the element 1607 * 1608 * @return <code>true</code> if the element has a background image set 1609 */ 1610 public static boolean hasBackgroundImage(Element element) { 1611 1612 String backgroundImage = CmsDomUtil.getCurrentStyle(element, Style.backgroundImage); 1613 if ((backgroundImage == null) 1614 || (backgroundImage.trim().length() == 0) 1615 || backgroundImage.equals(StyleValue.none.toString())) { 1616 return false; 1617 } 1618 return true; 1619 } 1620 1621 /** 1622 * Utility method to determine if the given element has a set border.<p> 1623 * 1624 * @param element the element 1625 * 1626 * @return <code>true</code> if the element has a border 1627 */ 1628 public static boolean hasBorder(Element element) { 1629 1630 String borderStyle = CmsDomUtil.getCurrentStyle(element, Style.borderStyle); 1631 if ((borderStyle == null) || borderStyle.equals(StyleValue.none.toString()) || (borderStyle.length() == 0)) { 1632 return false; 1633 } 1634 return true; 1635 1636 } 1637 1638 /** 1639 * Indicates if the given element has a CSS class.<p> 1640 * 1641 * @param className the class name to look for 1642 * @param element the element 1643 * 1644 * @return <code>true</code> if the element has the given CSS class 1645 */ 1646 public static boolean hasClass(String className, Element element) { 1647 1648 return internalHasClass(className.trim(), element); 1649 } 1650 1651 /** 1652 * Returns if the given element has any dimension.<p> 1653 * All visible elements should have a dimension.<p> 1654 * 1655 * @param element the element to test 1656 * 1657 * @return <code>true</code> if the given element has any dimension 1658 */ 1659 public static boolean hasDimension(Element element) { 1660 1661 return (element.getOffsetHeight() > 0) || (element.getOffsetWidth() > 0); 1662 } 1663 1664 /** 1665 * Checks whether the copy command is supported by the client browser.<p> 1666 * 1667 * @return <code>true</code> if the copy command is supported 1668 */ 1669 public static native boolean isCopyToClipboardSupported()/*-{ 1670 var result = document.queryCommandSupported('copy'); 1671 if (result) { 1672 var uMatch = navigator.userAgent.match(/Firefox\/(.*)$/); 1673 if (uMatch && uMatch.length > 1) { 1674 result = uMatch[1] >= 41; 1675 } 1676 } 1677 return result; 1678 }-*/; 1679 1680 /** 1681 * Checks whether a given script resource is present within the window context.<p> 1682 * 1683 * @param javascriptLink the resource URL 1684 * 1685 * @return <code>true</code> if the script resource is present within the window context 1686 */ 1687 public static native boolean isJavaScriptPresent(String javascriptLink)/*-{ 1688 var scripts = $wnd.document.scripts; 1689 for (var i = 0; i < scripts.length; i++) { 1690 if (scripts[i].src != null 1691 && scripts[i].src.indexOf(javascriptLink) >= 0) { 1692 // script resource is present 1693 return true; 1694 } 1695 } 1696 return false; 1697 }-*/; 1698 1699 /** 1700 * Gives an element the overflow:auto property.<p> 1701 * 1702 * @param elem a DOM element 1703 */ 1704 public static void makeScrollable(Element elem) { 1705 1706 elem.getStyle().setOverflow(Overflow.AUTO); 1707 } 1708 1709 /** 1710 * Gives the element of a widget the overflow:auto property.<p> 1711 * 1712 * @param widget the widget to make scrollable 1713 */ 1714 public static void makeScrollable(Widget widget) { 1715 1716 makeScrollable(widget.getElement()); 1717 } 1718 1719 /** 1720 * Message accessor.<p> 1721 * 1722 * @return the message string 1723 */ 1724 public static String messagePopupBlocked() { 1725 1726 return Messages.get().key(Messages.GUI_POPUP_BLOCKED_0); 1727 } 1728 1729 /** 1730 * Message accessor.<p> 1731 * 1732 * @return the message string 1733 */ 1734 public static String messagePopupBlockedTitle() { 1735 1736 return Messages.get().key(Messages.GUI_POPUP_BLOCKED_TITLE_0); 1737 } 1738 1739 /** 1740 * Converts a NodeList to a List of elements.<p> 1741 * 1742 * @param nodelist the node list 1743 * @return the list of elements 1744 */ 1745 public static List<Element> nodeListToList(NodeList<Element> nodelist) { 1746 1747 List<Element> result = Lists.newArrayList(); 1748 for (int i = 0; i < nodelist.getLength(); i++) { 1749 result.add(nodelist.getItem(i)); 1750 } 1751 return result; 1752 } 1753 1754 /** 1755 * Generates an opening tag.<p> 1756 * 1757 * @param tag the tag to use 1758 * @param attrs the optional tag attributes 1759 * 1760 * @return HTML code 1761 */ 1762 public static String open(Tag tag, AttributeValue... attrs) { 1763 1764 StringBuffer sb = new StringBuffer(); 1765 sb.append("<").append(tag.name()); 1766 for (AttributeValue attr : attrs) { 1767 sb.append(" ").append(attr.toString()); 1768 } 1769 sb.append(">"); 1770 return sb.toString(); 1771 } 1772 1773 /** 1774 * Opens a new browser window. The "name" and "features" arguments are 1775 * specified <a href= 1776 * 'http://developer.mozilla.org/en/docs/DOM:window.open'>here</a>. 1777 * 1778 * @param url the URL that the new window will display 1779 * @param name the name of the window (e.g. "_blank") 1780 * @param features the features to be enabled/disabled on this window 1781 */ 1782 public static native void openWindow(String url, String name, String features) /*-{ 1783 var w = $wnd.open(url, name, features); 1784 if (!w) { 1785 @org.opencms.gwt.client.util.CmsDomUtil::showPopupBlockerMessage()(); 1786 } 1787 }-*/; 1788 1789 /** 1790 * Parses the given string into a JSON object.<p> 1791 * 1792 * @param jsonString the string to parse 1793 * 1794 * @return the JSON object 1795 */ 1796 public static native JavaScriptObject parseJSON(String jsonString)/*-{ 1797 return (typeof $wnd.JSON != 'undefined') && $wnd.JSON.parse(jsonString) 1798 || eval('(' + jsonString + ')'); 1799 }-*/; 1800 1801 /** 1802 * Positions an element in the DOM relative to another element.<p> 1803 * 1804 * @param elem the element to position 1805 * @param referenceElement the element relative to which the first element should be positioned 1806 * @param dx the x offset relative to the reference element 1807 * @param dy the y offset relative to the reference element 1808 */ 1809 public static void positionElement(Element elem, Element referenceElement, int dx, int dy) { 1810 1811 com.google.gwt.dom.client.Style style = elem.getStyle(); 1812 style.setLeft(0, Unit.PX); 1813 style.setTop(0, Unit.PX); 1814 int myX = elem.getAbsoluteLeft(); 1815 int myY = elem.getAbsoluteTop(); 1816 int refX = referenceElement.getAbsoluteLeft(); 1817 int refY = referenceElement.getAbsoluteTop(); 1818 int newX = (refX - myX) + dx; 1819 int newY = (refY - myY) + dy; 1820 style.setLeft(newX, Unit.PX); 1821 style.setTop(newY, Unit.PX); 1822 } 1823 1824 /** 1825 * Positions an element inside the given parent, reordering the content of the parent and returns the new position index.<p> 1826 * This is none absolute positioning. Use for drag and drop reordering of drop targets.<p> 1827 * Use <code>-1</code> for x or y to ignore one ordering orientation.<p> 1828 * 1829 * @param element the child element 1830 * @param parent the parent element 1831 * @param currentIndex the current index position of the element, use -1 if element is not attached to the parent yet 1832 * @param x the client x position, use <code>-1</code> to ignore x position 1833 * @param y the client y position, use <code>-1</code> to ignore y position 1834 * 1835 * @return the new index position 1836 */ 1837 public static int positionElementInside(Element element, Element parent, int currentIndex, int x, int y) { 1838 1839 if ((x == -1) && (y == -1)) { 1840 // this is wrong usage, do nothing 1841 CmsDebugLog.getInstance().printLine("this is wrong usage, doing nothing"); 1842 return currentIndex; 1843 } 1844 int indexCorrection = 0; 1845 int previousTop = 0; 1846 for (int index = 0; index < parent.getChildCount(); index++) { 1847 Node node = parent.getChild(index); 1848 if (node.getNodeType() != Node.ELEMENT_NODE) { 1849 continue; 1850 } 1851 Element child = (Element)node; 1852 if (child == element) { 1853 indexCorrection = 1; 1854 } 1855 String positioning = CmsDomUtil.getCurrentStyle(child, Style.position); 1856 if (Position.ABSOLUTE.getCssName().equals(positioning) || Position.FIXED.getCssName().equals(positioning)) { 1857 // only not 'position:absolute' elements into account, 1858 // not visible children will be excluded in the next condition 1859 continue; 1860 } 1861 int left = 0; 1862 int width = 0; 1863 int top = 0; 1864 int height = 0; 1865 if (y != -1) { 1866 // check if the mouse pointer is within the height of the element 1867 top = CmsDomUtil.getRelativeY(y, child); 1868 height = child.getOffsetHeight(); 1869 if ((top <= 0) || (top >= height)) { 1870 previousTop = top; 1871 continue; 1872 } 1873 } 1874 if (x != -1) { 1875 // check if the mouse pointer is within the width of the element 1876 left = CmsDomUtil.getRelativeX(x, child); 1877 width = child.getOffsetWidth(); 1878 if ((left <= 0) || (left >= width)) { 1879 previousTop = top; 1880 continue; 1881 } 1882 } 1883 1884 boolean floatSort = false; 1885 String floating = ""; 1886 if ((top != 0) && (top == previousTop)) { 1887 floating = getCurrentStyle(child, Style.floatCss); 1888 if ("left".equals(floating) || "right".equals(floating)) { 1889 floatSort = true; 1890 } 1891 } 1892 previousTop = top; 1893 if (child == element) { 1894 return currentIndex; 1895 } 1896 if ((y == -1) || floatSort) { 1897 boolean insertBefore = false; 1898 if (left < (width / 2)) { 1899 if (!(floatSort && "right".equals(floating))) { 1900 insertBefore = true; 1901 } 1902 } else if (floatSort && "right".equals(floating)) { 1903 insertBefore = true; 1904 } 1905 if (insertBefore) { 1906 parent.insertBefore(element, child); 1907 currentIndex = index - indexCorrection; 1908 return currentIndex; 1909 } else { 1910 parent.insertAfter(element, child); 1911 currentIndex = (index + 1) - indexCorrection; 1912 return currentIndex; 1913 } 1914 } 1915 if (top < (height / 2)) { 1916 parent.insertBefore(element, child); 1917 currentIndex = index - indexCorrection; 1918 return currentIndex; 1919 } else { 1920 parent.insertAfter(element, child); 1921 currentIndex = (index + 1) - indexCorrection; 1922 return currentIndex; 1923 } 1924 1925 } 1926 // not over any child position 1927 if ((currentIndex >= 0) && (element.getParentElement() == parent)) { 1928 // element is already attached to this parent and no new position available 1929 // don't do anything 1930 return currentIndex; 1931 } 1932 int top = CmsDomUtil.getRelativeY(y, parent); 1933 int offsetHeight = parent.getOffsetHeight(); 1934 if ((top >= (offsetHeight / 2))) { 1935 // over top half, insert as first child 1936 parent.insertFirst(element); 1937 currentIndex = 0; 1938 return currentIndex; 1939 } 1940 // over bottom half, insert as last child 1941 parent.appendChild(element); 1942 currentIndex = parent.getChildCount() - 1; 1943 return currentIndex; 1944 } 1945 1946 /** 1947 * Returns the first element matching the given CSS selector.<p> 1948 * 1949 * @param selector the CSS selector 1950 * @param context the context element, may be <code>null</code> 1951 * 1952 * @return the matching element 1953 */ 1954 public static native Element querySelector(String selector, Element context)/*-{ 1955 if (context != null) { 1956 return context.querySelector(selector); 1957 } else { 1958 return $doc.querySelector(selector); 1959 } 1960 }-*/; 1961 1962 /** 1963 * Returns a list of elements matching the given CSS selector.<p> 1964 * 1965 * @param selector the CSS selector 1966 * @param context the context element, may be <code>null</code> 1967 * 1968 * @return the list of matching elements 1969 */ 1970 public static native NodeList<Element> querySelectorAll(String selector, Element context)/*-{ 1971 if (context != null) { 1972 return context.querySelectorAll(selector); 1973 } else { 1974 $doc.querySelectorAll(selector); 1975 } 1976 }-*/; 1977 1978 /** 1979 * Removes any present overlay from the element and it's children.<p> 1980 * 1981 * @param element the element 1982 */ 1983 public static void removeDisablingOverlay(Element element) { 1984 1985 List<Element> overlays = CmsDomUtil.getElementsByClass( 1986 I_CmsLayoutBundle.INSTANCE.generalCss().disablingOverlay(), 1987 Tag.div, 1988 element); 1989 if (overlays == null) { 1990 return; 1991 } 1992 for (Element overlay : overlays) { 1993 overlay.getParentElement().getStyle().clearPosition(); 1994 overlay.removeFromParent(); 1995 } 1996 element.removeClassName(I_CmsLayoutBundle.INSTANCE.generalCss().hideOverlay()); 1997 } 1998 1999 /** 2000 * Removes all script tags from the given element.<p> 2001 * 2002 * @param element the element to remove the script tags from 2003 * 2004 * @return the resulting element 2005 */ 2006 public static Element removeScriptTags(Element element) { 2007 2008 NodeList<Element> scriptTags = element.getElementsByTagName(Tag.script.name()); 2009 // iterate backwards over list to ensure all tags get removed 2010 for (int i = scriptTags.getLength() - 1; i >= 0; i--) { 2011 scriptTags.getItem(i).removeFromParent(); 2012 } 2013 return element; 2014 } 2015 2016 /** 2017 * Removes all script tags from the given string.<p> 2018 * 2019 * @param source the source string 2020 * 2021 * @return the resulting string 2022 */ 2023 public static native String removeScriptTags(String source)/*-{ 2024 2025 var matchTag = /<script[^>]*?>[\s\S]*?<\/script>/g; 2026 return source.replace(matchTag, ""); 2027 }-*/; 2028 2029 /** 2030 * Calls {@link org.opencms.gwt.client.I_CmsDescendantResizeHandler#onResizeDescendant()} on the closest resizable ancestor.<p> 2031 * 2032 * @param parent the parent widget 2033 */ 2034 public static void resizeAncestor(Widget parent) { 2035 2036 while (parent != null) { 2037 if (parent instanceof I_CmsDescendantResizeHandler) { 2038 ((I_CmsDescendantResizeHandler)parent).onResizeDescendant(); 2039 return; 2040 } else { 2041 parent = parent.getParent(); 2042 } 2043 } 2044 } 2045 2046 /** 2047 * Loads a list of stylesheets and invokes a Javascript callback after everything has been loaded.<p> 2048 * 2049 * @param stylesheets the array of stylesheet uris 2050 * @param callback the callback to call after everything is loaded 2051 */ 2052 public static void safeLoadStylesheets(String[] stylesheets, JavaScriptObject callback) { 2053 2054 CmsStylesheetLoader loader = new CmsStylesheetLoader(Arrays.asList(stylesheets), new Runnable() { 2055 2056 public native void call(JavaScriptObject jsCallback) /*-{ 2057 jsCallback(); 2058 }-*/; 2059 2060 public void run() { 2061 2062 if (callback != null) { 2063 call(callback); 2064 } 2065 } 2066 2067 }); 2068 loader.loadWithTimeout(5000); 2069 } 2070 2071 /** 2072 * Sets an attribute on a Javascript object.<p> 2073 * 2074 * @param jso the Javascript object 2075 * @param key the attribute name 2076 * @param value the new attribute value 2077 */ 2078 public static native void setAttribute(JavaScriptObject jso, String key, JavaScriptObject value) /*-{ 2079 jso[key] = value; 2080 }-*/; 2081 2082 /** 2083 * Sets an attribute on a Javascript object.<p> 2084 * 2085 * @param jso the Javascript object 2086 * @param key the attribute name 2087 * @param value the new attribute value 2088 */ 2089 public static native void setAttribute(JavaScriptObject jso, String key, String value) /*-{ 2090 jso[key] = value; 2091 }-*/; 2092 2093 /** 2094 * Sets the stylesheet text for the stylesheet with the given ID.<p> 2095 * 2096 * If the stylesheet with the id does not already exist, it is created. 2097 * 2098 * @param id the stylesheet id 2099 * @param styleText the stylesheet text 2100 */ 2101 public static void setStylesheetText(String id, String styleText) { 2102 2103 Document document = Document.get(); 2104 Element elem = document.getElementById(id); 2105 if (elem == null) { 2106 elem = document.createStyleElement(); 2107 elem.setId(id); 2108 document.getHead().appendChild(elem); 2109 } 2110 elem.setInnerHTML(styleText); 2111 } 2112 2113 /** 2114 * Sets a CSS class to show or hide a given overlay. Will not add an overlay to the element.<p> 2115 * 2116 * @param element the parent element of the overlay 2117 * @param show <code>true</code> to show the overlay 2118 */ 2119 public static void showOverlay(Element element, boolean show) { 2120 2121 if (show) { 2122 element.removeClassName(I_CmsLayoutBundle.INSTANCE.generalCss().hideOverlay()); 2123 } else { 2124 element.addClassName(I_CmsLayoutBundle.INSTANCE.generalCss().hideOverlay()); 2125 } 2126 } 2127 2128 /** 2129 * Shows a message that a popup was blocked.<p> 2130 */ 2131 public static void showPopupBlockerMessage() { 2132 2133 CmsAlertDialog alertDialog = new CmsAlertDialog(messagePopupBlockedTitle(), messagePopupBlocked()); 2134 alertDialog.center(); 2135 } 2136 2137 /** 2138 * Returns the text content to any HTML. 2139 * 2140 * @param html the HTML 2141 * 2142 * @return the text content 2143 */ 2144 public static String stripHtml(String html) { 2145 2146 if (html == null) { 2147 return null; 2148 } 2149 Element el = DOM.createDiv(); 2150 el.setInnerHTML(html); 2151 return el.getInnerText(); 2152 } 2153 2154 /** 2155 * Updates a set of style properties on the given style object, and returns a map with the previous values. 2156 * 2157 * If a value in the map is null, it is interpreted as clearing the style property with that name. 2158 * 2159 * @param style the style object to update 2160 * @param properties the map of properties to change 2161 * 2162 * @return the map of previous values of the given properties 2163 */ 2164 public static Map<String, String> updateStyle( 2165 com.google.gwt.dom.client.Style style, 2166 Map<String, String> properties) { 2167 2168 Map<String, String> oldProps = new HashMap<>(); 2169 for (Map.Entry<String, String> entry : properties.entrySet()) { 2170 String prop = entry.getKey(); 2171 String value = entry.getValue(); 2172 String oldValue = style.getProperty(prop); 2173 oldProps.put(prop, oldValue); 2174 if (value != null) { 2175 style.setProperty(prop, value); 2176 } else { 2177 style.clearProperty(prop); 2178 } 2179 2180 } 2181 return oldProps; 2182 } 2183 2184 /** 2185 * Wraps a widget in a scrollable FlowPanel.<p> 2186 * 2187 * @param widget the original widget 2188 * @return the wrapped widget 2189 */ 2190 public static FlowPanel wrapScrollable(Widget widget) { 2191 2192 FlowPanel wrapper = new FlowPanel(); 2193 wrapper.add(widget); 2194 makeScrollable(wrapper); 2195 return wrapper; 2196 } 2197 2198 /** 2199 * Creates a hidden input field with the given name and value.<p> 2200 * 2201 * @param name the field name 2202 * @param value the field value 2203 * @return the input element 2204 */ 2205 private static InputElement createHiddenInput(String name, String value) { 2206 2207 InputElement input = Document.get().createHiddenInputElement(); 2208 input.setName(name); 2209 input.setValue(value); 2210 return input; 2211 } 2212 2213 /** 2214 * Returns the DOM implementation.<p> 2215 * 2216 * @return the DOM implementation 2217 */ 2218 private static DOMImpl getDOMImpl() { 2219 2220 if (domImpl == null) { 2221 domImpl = GWT.create(DOMImpl.class); 2222 } 2223 return domImpl; 2224 } 2225 2226 /** 2227 * Returns the document style implementation.<p> 2228 * 2229 * @return the document style implementation 2230 */ 2231 private static DocumentStyleImpl getStyleImpl() { 2232 2233 if (styleImpl == null) { 2234 styleImpl = GWT.create(DocumentStyleImpl.class); 2235 } 2236 return styleImpl; 2237 } 2238 2239 /** 2240 * Injects a script tag into the page head.<p> 2241 * 2242 * @param scriptLink the link to the javascript resource 2243 * @param async the value for the async attribute of the new script node 2244 * @param onload load handler for the script 2245 */ 2246 private static native void injectScript(String scriptLink, boolean async, JavaScriptObject onload)/*-{ 2247 var headID = $wnd.document.getElementsByTagName("head")[0]; 2248 var scriptNode = $wnd.document.createElement('script'); 2249 scriptNode.async = async; 2250 if (onload) { 2251 scriptNode.onload = onload; 2252 } 2253 scriptNode.src = scriptLink; 2254 headID.appendChild(scriptNode); 2255 }-*/; 2256 2257 /** 2258 * Internal method to indicate if the given element has a CSS class.<p> 2259 * 2260 * @param className the class name to look for 2261 * @param element the element 2262 * 2263 * @return <code>true</code> if the element has the given CSS class 2264 */ 2265 private static boolean internalHasClass(String className, Element element) { 2266 2267 boolean hasClass = false; 2268 try { 2269 String elementClass = element.getClassName().trim(); 2270 hasClass = elementClass.equals(className); 2271 hasClass |= elementClass.contains(" " + className + " "); 2272 hasClass |= elementClass.startsWith(className + " "); 2273 hasClass |= elementClass.endsWith(" " + className); 2274 } catch (Throwable t) { 2275 // ignore 2276 } 2277 return hasClass; 2278 } 2279 2280 /** 2281 * Checks if the given color value is transparent.<p> 2282 * 2283 * @param backgroundColor the color value 2284 * 2285 * @return <code>true</code> if transparent 2286 */ 2287 private static boolean isTransparent(String backgroundColor) { 2288 2289 // not only check 'transparent' but also 'rgba(0, 0, 0, 0)' as returned by chrome 2290 return StyleValue.transparent.toString().equalsIgnoreCase(backgroundColor) 2291 || "rgba(0, 0, 0, 0)".equalsIgnoreCase(backgroundColor); 2292 } 2293 2294}