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.ALL); 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 (element == null) { 1177 return null; 1178 } 1179 1180 if (hasClass(className, element)) { 1181 return element; 1182 } 1183 if (element.getTagName().equalsIgnoreCase(Tag.body.name())) { 1184 return null; 1185 } 1186 return getAncestor(element.getParentElement(), className); 1187 } 1188 1189 /** 1190 * Returns the given element or it's closest ancestor with the given tag name.<p> 1191 * 1192 * Returns <code>null</code> if no appropriate element was found.<p> 1193 * 1194 * @param element the element 1195 * @param tag the tag name 1196 * 1197 * @return the matching element 1198 */ 1199 public static Element getAncestor(Element element, Tag tag) { 1200 1201 if ((element == null) || (tag == null)) { 1202 return null; 1203 } 1204 if (element.getTagName().equalsIgnoreCase(tag.name())) { 1205 return element; 1206 } 1207 if (element.getTagName().equalsIgnoreCase(Tag.body.name())) { 1208 return null; 1209 } 1210 return getAncestor(element.getParentElement(), tag); 1211 } 1212 1213 /** 1214 * Returns the given element or it's closest ancestor with the given tag and class.<p> 1215 * 1216 * Returns <code>null</code> if no appropriate element was found.<p> 1217 * 1218 * @param element the element 1219 * @param tag the tag name 1220 * @param className the class name 1221 * 1222 * @return the matching element 1223 */ 1224 public static Element getAncestor(Element element, Tag tag, String className) { 1225 1226 if (element.getTagName().equalsIgnoreCase(tag.name()) && hasClass(className, element)) { 1227 return element; 1228 } 1229 if (element.getTagName().equalsIgnoreCase(Tag.body.name())) { 1230 return null; 1231 } 1232 return getAncestor(element.getParentElement(), tag, className); 1233 } 1234 1235 /** 1236 * Returns the computed style of the given element.<p> 1237 * 1238 * @param element the element 1239 * @param style the CSS property 1240 * 1241 * @return the currently computed style 1242 */ 1243 public static String getCurrentStyle(Element element, Style style) { 1244 1245 return getStyleImpl().getCurrentStyle(element, style.toString()); 1246 } 1247 1248 /** 1249 * Returns the computed style of the given element as floating point number.<p> 1250 * 1251 * @param element the element 1252 * @param style the CSS property 1253 * 1254 * @return the currently computed style 1255 */ 1256 public static double getCurrentStyleFloat(Element element, Style style) { 1257 1258 String currentStyle = getCurrentStyle(element, style); 1259 return CmsClientStringUtil.parseFloat(currentStyle); 1260 } 1261 1262 /** 1263 * Returns the computed style of the given element as number.<p> 1264 * 1265 * @param element the element 1266 * @param style the CSS property 1267 * 1268 * @return the currently computed style 1269 */ 1270 public static int getCurrentStyleInt(Element element, Style style) { 1271 1272 String currentStyle = getCurrentStyle(element, style); 1273 return CmsClientStringUtil.parseInt(currentStyle); 1274 } 1275 1276 /** 1277 * Determines the position of the list collector editable content.<p> 1278 * 1279 * @param editable the editable marker tag 1280 * 1281 * @return the position 1282 */ 1283 public static CmsPositionBean getEditablePosition(Element editable) { 1284 1285 CmsPositionBean result = new CmsPositionBean(); 1286 int dummy = -999; 1287 // setting minimum height 1288 result.setHeight(20); 1289 result.setWidth(60); 1290 result.setLeft(dummy); 1291 result.setTop(dummy); 1292 Element sibling = editable.getNextSiblingElement(); 1293 while ((sibling != null) 1294 && !CmsDomUtil.hasClass(CmsGwtConstants.CLASS_EDITABLE, sibling) 1295 && !CmsDomUtil.hasClass(CmsGwtConstants.CLASS_EDITABLE_END, sibling)) { 1296 // only consider element nodes 1297 1298 if ((sibling.getNodeType() == Node.ELEMENT_NODE) 1299 && !sibling.getTagName().equalsIgnoreCase(Tag.script.name())) { 1300 if (!CmsDomUtil.hasClass(CmsGwtConstants.CLASS_EDITABLE_SKIP, sibling)) { 1301 CmsPositionBean siblingPos = CmsPositionBean.generatePositionInfo(sibling); 1302 result.setLeft( 1303 ((result.getLeft() == dummy) || (siblingPos.getLeft() < result.getLeft())) 1304 ? siblingPos.getLeft() 1305 : result.getLeft()); 1306 result.setTop( 1307 ((result.getTop() == dummy) || (siblingPos.getTop() < result.getTop())) 1308 ? siblingPos.getTop() 1309 : result.getTop()); 1310 result.setHeight( 1311 ((result.getTop() + result.getHeight()) > (siblingPos.getTop() + siblingPos.getHeight())) 1312 ? result.getHeight() 1313 : (siblingPos.getTop() + siblingPos.getHeight()) - result.getTop()); 1314 result.setWidth( 1315 ((result.getLeft() + result.getWidth()) > (siblingPos.getLeft() + siblingPos.getWidth())) 1316 ? result.getWidth() 1317 : (siblingPos.getLeft() + siblingPos.getWidth()) - result.getLeft()); 1318 } 1319 } 1320 1321 sibling = sibling.getNextSiblingElement(); 1322 } 1323 if ((result.getTop() == dummy) && (result.getLeft() == dummy)) { 1324 result = CmsPositionBean.generatePositionInfo(editable); 1325 } 1326 if (result.getHeight() == -1) { 1327 // in case no height was set 1328 result = CmsPositionBean.generatePositionInfo(editable); 1329 result.setHeight(20); 1330 result.setWidth((result.getWidth() < 60) ? 60 : result.getWidth()); 1331 } 1332 1333 return result; 1334 } 1335 1336 /** 1337 * Utility method to determine the effective background color.<p> 1338 * 1339 * @param element the element 1340 * 1341 * @return the background color 1342 */ 1343 public static String getEffectiveBackgroundColor(Element element) { 1344 1345 String backgroundColor = CmsDomUtil.getCurrentStyle(element, Style.backgroundColor); 1346 if ((CmsStringUtil.isEmptyOrWhitespaceOnly(backgroundColor) 1347 || isTransparent(backgroundColor) 1348 || backgroundColor.equals(StyleValue.inherit.toString()))) { 1349 1350 if ((Document.get().getBody() != element) && (element.getParentElement() != null)) { 1351 1352 backgroundColor = getEffectiveBackgroundColor(element.getParentElement()); 1353 } else { 1354 // if body element has still no background color set default to white 1355 backgroundColor = "#FFFFFF"; 1356 } 1357 } 1358 1359 return backgroundColor; 1360 } 1361 1362 /** 1363 * Returns all elements from the DOM with the given CSS class.<p> 1364 * 1365 * @param className the class name to look for 1366 * 1367 * @return the matching elements 1368 */ 1369 public static List<Element> getElementsByClass(String className) { 1370 1371 return getElementsByClass(className, Tag.ALL, Document.get().getBody()); 1372 } 1373 1374 /** 1375 * Returns all elements with the given CSS class including the root element.<p> 1376 * 1377 * @param className the class name to look for 1378 * @param rootElement the root element of the search 1379 * 1380 * @return the matching elements 1381 */ 1382 public static List<Element> getElementsByClass(String className, Element rootElement) { 1383 1384 return getElementsByClass(className, Tag.ALL, rootElement); 1385 1386 } 1387 1388 /** 1389 * Returns all elements from the DOM with the given CSS class and tag name.<p> 1390 * 1391 * @param className the class name to look for 1392 * @param tag the tag 1393 * 1394 * @return the matching elements 1395 */ 1396 public static List<Element> getElementsByClass(String className, Tag tag) { 1397 1398 return getElementsByClass(className, tag, Document.get().getBody()); 1399 } 1400 1401 /** 1402 * Returns all elements with the given CSS class and tag name including the root element.<p> 1403 * 1404 * @param className the class name to look for 1405 * @param tag the tag 1406 * @param rootElement the root element of the search 1407 * 1408 * @return the matching elements 1409 */ 1410 public static List<Element> getElementsByClass(String className, Tag tag, Element rootElement) { 1411 1412 if ((rootElement == null) || (className == null) || (className.trim().length() == 0) || (tag == null)) { 1413 return null; 1414 } 1415 className = className.trim(); 1416 List<Element> result = new ArrayList<Element>(); 1417 if (internalHasClass(className, rootElement)) { 1418 result.add(rootElement); 1419 } 1420 NodeList<Element> elements = querySelectorAll(tag + "." + className, rootElement); 1421 for (int i = 0; i < elements.getLength(); i++) { 1422 result.add(elements.getItem(i)); 1423 } 1424 return result; 1425 } 1426 1427 /** 1428 * Returns the first direct child matching the given class name.<p> 1429 * 1430 * @param element the parent element 1431 * @param className the class name to match 1432 * 1433 * @return the child element 1434 */ 1435 public static Element getFirstChildWithClass(Element element, String className) { 1436 1437 NodeList<Node> children = element.getChildNodes(); 1438 1439 for (int i = 0; i < children.getLength(); i++) { 1440 if (children.getItem(i).getNodeType() == Node.ELEMENT_NODE) { 1441 Element child = (Element)children.getItem(i); 1442 if (child.hasClassName(className)) { 1443 return child; 1444 } 1445 } 1446 } 1447 return null; 1448 } 1449 1450 /** 1451 * Gets the root <html> element. 1452 * 1453 * @return the html element 1454 */ 1455 public static native Element getHtmlElement() /*-{ 1456 return $wnd.document.querySelector("html"); 1457 }-*/; 1458 1459 /** 1460 * Returns the content height of the given iFrame element.<p> 1461 * 1462 * @param iframe the iFrame element 1463 * 1464 * @return the content height 1465 */ 1466 public static native int getIFrameContentHeight(Element iframe)/*-{ 1467 var doc = iframe.contentDocument ? iframe.contentDocument 1468 : iframe.contentWindow.document; 1469 var body = doc.body; 1470 var html = doc.documentElement; 1471 var height = Math.max(body.scrollHeight, body.offsetHeight, 1472 html.clientHeight, html.scrollHeight, html.offsetHeight); 1473 return height; 1474 }-*/; 1475 1476 /** 1477 * Returns the element position relative to its siblings.<p> 1478 * 1479 * @param e the element to get the position for 1480 * 1481 * @return the position, or <code>-1</code> if not found 1482 */ 1483 public static int getPosition(Element e) { 1484 1485 NodeList<Node> childNodes = e.getParentElement().getChildNodes(); 1486 for (int i = childNodes.getLength(); i >= 0; i--) { 1487 if (childNodes.getItem(i) == e) { 1488 return i; 1489 } 1490 } 1491 return -1; 1492 } 1493 1494 /** 1495 * Returns the next ancestor to the element with an absolute, fixed or relative position.<p> 1496 * 1497 * @param child the element 1498 * 1499 * @return the positioning parent element (may be <code>null</code>) 1500 */ 1501 public static Element getPositioningParent(Element child) { 1502 1503 Element parent = child.getParentElement(); 1504 while (parent != null) { 1505 String parentPositioning = CmsDomUtil.getCurrentStyle(parent, Style.position); 1506 if (Position.RELATIVE.getCssName().equals(parentPositioning) 1507 || Position.ABSOLUTE.getCssName().equals(parentPositioning) 1508 || Position.FIXED.getCssName().equals(parentPositioning)) { 1509 return parent; 1510 } 1511 parent = parent.getParentElement(); 1512 } 1513 return RootPanel.getBodyElement(); 1514 } 1515 1516 /** 1517 * Gets the horizontal position of the given x-coordinate relative to a given element.<p> 1518 * 1519 * @param x the coordinate to use 1520 * @param target the element whose coordinate system is to be used 1521 * 1522 * @return the relative horizontal position 1523 * 1524 * @see com.google.gwt.event.dom.client.MouseEvent#getRelativeX(com.google.gwt.dom.client.Element) 1525 */ 1526 public static int getRelativeX(int x, Element target) { 1527 1528 return (x - target.getAbsoluteLeft()) + /* target.getScrollLeft() + */target.getOwnerDocument().getScrollLeft(); 1529 } 1530 1531 /** 1532 * Gets the vertical position of the given y-coordinate relative to a given element.<p> 1533 * 1534 * @param y the coordinate to use 1535 * @param target the element whose coordinate system is to be used 1536 * 1537 * @return the relative vertical position 1538 * 1539 * @see com.google.gwt.event.dom.client.MouseEvent#getRelativeY(com.google.gwt.dom.client.Element) 1540 */ 1541 public static int getRelativeY(int y, Element target) { 1542 1543 return (y - target.getAbsoluteTop()) + /* target.getScrollTop() +*/target.getOwnerDocument().getScrollTop(); 1544 } 1545 1546 /** 1547 * Measures the scroll bar width.<p> 1548 * 1549 * @return the scroll bar width 1550 */ 1551 public static int getScrollbarWidth() { 1552 1553 if (m_scrollbarWidth == -1) { 1554 Element div = DOM.createDiv(); 1555 div.setAttribute("style", "width:100px; height:100px; overflow: scroll; position:absolute; top:-9999px;"); 1556 RootPanel.getBodyElement().appendChild(div); 1557 m_scrollbarWidth = div.getOffsetWidth() - div.getClientWidth(); 1558 div.removeFromParent(); 1559 } 1560 return m_scrollbarWidth; 1561 } 1562 1563 /** 1564 * Returns the DOM window object.<p> 1565 * 1566 * @return the DOM window object 1567 */ 1568 public static native JavaScriptObject getWindow() /*-{ 1569 return $wnd; 1570 }-*/; 1571 1572 /** 1573 * Returns the Z index from the given style.<p> 1574 * 1575 * This is a workaround for a bug with {@link com.google.gwt.dom.client.Style#getZIndex()} which occurs with IE in 1576 * hosted mode.<p> 1577 * 1578 * @param style the style object from which the Z index property should be fetched 1579 * 1580 * @return the z index 1581 */ 1582 public static native String getZIndex(com.google.gwt.dom.client.Style style) 1583 /*-{ 1584 return "" + style.zIndex; 1585 }-*/; 1586 1587 /** 1588 * Utility method to determine if the given element has a set background.<p> 1589 * 1590 * @param element the element 1591 * 1592 * @return <code>true</code> if the element has a background set 1593 */ 1594 public static boolean hasBackground(Element element) { 1595 1596 String backgroundColor = CmsDomUtil.getCurrentStyle(element, Style.backgroundColor); 1597 String backgroundImage = CmsDomUtil.getCurrentStyle(element, Style.backgroundImage); 1598 if ((isTransparent(backgroundColor)) 1599 && ((backgroundImage == null) 1600 || (backgroundImage.trim().length() == 0) 1601 || backgroundImage.equals(StyleValue.none.toString()))) { 1602 return false; 1603 } 1604 return true; 1605 } 1606 1607 /** 1608 * Utility method to determine if the given element has a set background image.<p> 1609 * 1610 * @param element the element 1611 * 1612 * @return <code>true</code> if the element has a background image set 1613 */ 1614 public static boolean hasBackgroundImage(Element element) { 1615 1616 String backgroundImage = CmsDomUtil.getCurrentStyle(element, Style.backgroundImage); 1617 if ((backgroundImage == null) 1618 || (backgroundImage.trim().length() == 0) 1619 || backgroundImage.equals(StyleValue.none.toString())) { 1620 return false; 1621 } 1622 return true; 1623 } 1624 1625 /** 1626 * Utility method to determine if the given element has a set border.<p> 1627 * 1628 * @param element the element 1629 * 1630 * @return <code>true</code> if the element has a border 1631 */ 1632 public static boolean hasBorder(Element element) { 1633 1634 String borderStyle = CmsDomUtil.getCurrentStyle(element, Style.borderStyle); 1635 if ((borderStyle == null) || borderStyle.equals(StyleValue.none.toString()) || (borderStyle.length() == 0)) { 1636 return false; 1637 } 1638 return true; 1639 1640 } 1641 1642 /** 1643 * Indicates if the given element has a CSS class.<p> 1644 * 1645 * @param className the class name to look for 1646 * @param element the element 1647 * 1648 * @return <code>true</code> if the element has the given CSS class 1649 */ 1650 public static boolean hasClass(String className, Element element) { 1651 1652 return internalHasClass(className.trim(), element); 1653 } 1654 1655 /** 1656 * Returns if the given element has any dimension.<p> 1657 * All visible elements should have a dimension.<p> 1658 * 1659 * @param element the element to test 1660 * 1661 * @return <code>true</code> if the given element has any dimension 1662 */ 1663 public static boolean hasDimension(Element element) { 1664 1665 return (element.getOffsetHeight() > 0) || (element.getOffsetWidth() > 0); 1666 } 1667 1668 /** 1669 * Checks whether the copy command is supported by the client browser.<p> 1670 * 1671 * @return <code>true</code> if the copy command is supported 1672 */ 1673 public static native boolean isCopyToClipboardSupported()/*-{ 1674 var result = document.queryCommandSupported('copy'); 1675 if (result) { 1676 var uMatch = navigator.userAgent.match(/Firefox\/(.*)$/); 1677 if (uMatch && uMatch.length > 1) { 1678 result = uMatch[1] >= 41; 1679 } 1680 } 1681 return result; 1682 }-*/; 1683 1684 /** 1685 * Checks whether a given script resource is present within the window context.<p> 1686 * 1687 * @param javascriptLink the resource URL 1688 * 1689 * @return <code>true</code> if the script resource is present within the window context 1690 */ 1691 public static native boolean isJavaScriptPresent(String javascriptLink)/*-{ 1692 var scripts = $wnd.document.scripts; 1693 for (var i = 0; i < scripts.length; i++) { 1694 if (scripts[i].src != null 1695 && scripts[i].src.indexOf(javascriptLink) >= 0) { 1696 // script resource is present 1697 return true; 1698 } 1699 } 1700 return false; 1701 }-*/; 1702 1703 /** 1704 * Gives an element the overflow:auto property.<p> 1705 * 1706 * @param elem a DOM element 1707 */ 1708 public static void makeScrollable(Element elem) { 1709 1710 elem.getStyle().setOverflow(Overflow.AUTO); 1711 } 1712 1713 /** 1714 * Gives the element of a widget the overflow:auto property.<p> 1715 * 1716 * @param widget the widget to make scrollable 1717 */ 1718 public static void makeScrollable(Widget widget) { 1719 1720 makeScrollable(widget.getElement()); 1721 } 1722 1723 /** 1724 * Message accessor.<p> 1725 * 1726 * @return the message string 1727 */ 1728 public static String messagePopupBlocked() { 1729 1730 return Messages.get().key(Messages.GUI_POPUP_BLOCKED_0); 1731 } 1732 1733 /** 1734 * Message accessor.<p> 1735 * 1736 * @return the message string 1737 */ 1738 public static String messagePopupBlockedTitle() { 1739 1740 return Messages.get().key(Messages.GUI_POPUP_BLOCKED_TITLE_0); 1741 } 1742 1743 /** 1744 * Converts a NodeList to a List of elements.<p> 1745 * 1746 * @param nodelist the node list 1747 * @return the list of elements 1748 */ 1749 public static List<Element> nodeListToList(NodeList<Element> nodelist) { 1750 1751 List<Element> result = Lists.newArrayList(); 1752 for (int i = 0; i < nodelist.getLength(); i++) { 1753 result.add(nodelist.getItem(i)); 1754 } 1755 return result; 1756 } 1757 1758 /** 1759 * Generates an opening tag.<p> 1760 * 1761 * @param tag the tag to use 1762 * @param attrs the optional tag attributes 1763 * 1764 * @return HTML code 1765 */ 1766 public static String open(Tag tag, AttributeValue... attrs) { 1767 1768 StringBuffer sb = new StringBuffer(); 1769 sb.append("<").append(tag.name()); 1770 for (AttributeValue attr : attrs) { 1771 sb.append(" ").append(attr.toString()); 1772 } 1773 sb.append(">"); 1774 return sb.toString(); 1775 } 1776 1777 /** 1778 * Opens a new browser window. The "name" and "features" arguments are 1779 * specified <a href= 1780 * 'http://developer.mozilla.org/en/docs/DOM:window.open'>here</a>. 1781 * 1782 * @param url the URL that the new window will display 1783 * @param name the name of the window (e.g. "_blank") 1784 * @param features the features to be enabled/disabled on this window 1785 */ 1786 public static native void openWindow(String url, String name, String features) /*-{ 1787 var w = $wnd.open(url, name, features); 1788 if (!w) { 1789 @org.opencms.gwt.client.util.CmsDomUtil::showPopupBlockerMessage()(); 1790 } 1791 }-*/; 1792 1793 /** 1794 * Parses the given string into a JSON object.<p> 1795 * 1796 * @param jsonString the string to parse 1797 * 1798 * @return the JSON object 1799 */ 1800 public static native JavaScriptObject parseJSON(String jsonString)/*-{ 1801 return (typeof $wnd.JSON != 'undefined') && $wnd.JSON.parse(jsonString) 1802 || eval('(' + jsonString + ')'); 1803 }-*/; 1804 1805 /** 1806 * Positions an element in the DOM relative to another element.<p> 1807 * 1808 * @param elem the element to position 1809 * @param referenceElement the element relative to which the first element should be positioned 1810 * @param dx the x offset relative to the reference element 1811 * @param dy the y offset relative to the reference element 1812 */ 1813 public static void positionElement(Element elem, Element referenceElement, int dx, int dy) { 1814 1815 com.google.gwt.dom.client.Style style = elem.getStyle(); 1816 style.setLeft(0, Unit.PX); 1817 style.setTop(0, Unit.PX); 1818 int myX = elem.getAbsoluteLeft(); 1819 int myY = elem.getAbsoluteTop(); 1820 int refX = referenceElement.getAbsoluteLeft(); 1821 int refY = referenceElement.getAbsoluteTop(); 1822 int newX = (refX - myX) + dx; 1823 int newY = (refY - myY) + dy; 1824 style.setLeft(newX, Unit.PX); 1825 style.setTop(newY, Unit.PX); 1826 } 1827 1828 /** 1829 * Positions an element inside the given parent, reordering the content of the parent and returns the new position index.<p> 1830 * This is none absolute positioning. Use for drag and drop reordering of drop targets.<p> 1831 * Use <code>-1</code> for x or y to ignore one ordering orientation.<p> 1832 * 1833 * @param element the child element 1834 * @param parent the parent element 1835 * @param currentIndex the current index position of the element, use -1 if element is not attached to the parent yet 1836 * @param x the client x position, use <code>-1</code> to ignore x position 1837 * @param y the client y position, use <code>-1</code> to ignore y position 1838 * 1839 * @return the new index position 1840 */ 1841 public static int positionElementInside(Element element, Element parent, int currentIndex, int x, int y) { 1842 1843 if ((x == -1) && (y == -1)) { 1844 // this is wrong usage, do nothing 1845 CmsDebugLog.getInstance().printLine("this is wrong usage, doing nothing"); 1846 return currentIndex; 1847 } 1848 int indexCorrection = 0; 1849 int previousTop = 0; 1850 for (int index = 0; index < parent.getChildCount(); index++) { 1851 Node node = parent.getChild(index); 1852 if (node.getNodeType() != Node.ELEMENT_NODE) { 1853 continue; 1854 } 1855 Element child = (Element)node; 1856 if (child == element) { 1857 indexCorrection = 1; 1858 } 1859 String positioning = CmsDomUtil.getCurrentStyle(child, Style.position); 1860 if (Position.ABSOLUTE.getCssName().equals(positioning) || Position.FIXED.getCssName().equals(positioning)) { 1861 // only not 'position:absolute' elements into account, 1862 // not visible children will be excluded in the next condition 1863 continue; 1864 } 1865 int left = 0; 1866 int width = 0; 1867 int top = 0; 1868 int height = 0; 1869 if (y != -1) { 1870 // check if the mouse pointer is within the height of the element 1871 top = CmsDomUtil.getRelativeY(y, child); 1872 height = child.getOffsetHeight(); 1873 if ((top <= 0) || (top >= height)) { 1874 previousTop = top; 1875 continue; 1876 } 1877 } 1878 if (x != -1) { 1879 // check if the mouse pointer is within the width of the element 1880 left = CmsDomUtil.getRelativeX(x, child); 1881 width = child.getOffsetWidth(); 1882 if ((left <= 0) || (left >= width)) { 1883 previousTop = top; 1884 continue; 1885 } 1886 } 1887 1888 boolean floatSort = false; 1889 String floating = ""; 1890 if ((top != 0) && (top == previousTop)) { 1891 floating = getCurrentStyle(child, Style.floatCss); 1892 if ("left".equals(floating) || "right".equals(floating)) { 1893 floatSort = true; 1894 } 1895 } 1896 previousTop = top; 1897 if (child == element) { 1898 return currentIndex; 1899 } 1900 if ((y == -1) || floatSort) { 1901 boolean insertBefore = false; 1902 if (left < (width / 2)) { 1903 if (!(floatSort && "right".equals(floating))) { 1904 insertBefore = true; 1905 } 1906 } else if (floatSort && "right".equals(floating)) { 1907 insertBefore = true; 1908 } 1909 if (insertBefore) { 1910 parent.insertBefore(element, child); 1911 currentIndex = index - indexCorrection; 1912 return currentIndex; 1913 } else { 1914 parent.insertAfter(element, child); 1915 currentIndex = (index + 1) - indexCorrection; 1916 return currentIndex; 1917 } 1918 } 1919 if (top < (height / 2)) { 1920 parent.insertBefore(element, child); 1921 currentIndex = index - indexCorrection; 1922 return currentIndex; 1923 } else { 1924 parent.insertAfter(element, child); 1925 currentIndex = (index + 1) - indexCorrection; 1926 return currentIndex; 1927 } 1928 1929 } 1930 // not over any child position 1931 if ((currentIndex >= 0) && (element.getParentElement() == parent)) { 1932 // element is already attached to this parent and no new position available 1933 // don't do anything 1934 return currentIndex; 1935 } 1936 int top = CmsDomUtil.getRelativeY(y, parent); 1937 int offsetHeight = parent.getOffsetHeight(); 1938 if ((top >= (offsetHeight / 2))) { 1939 // over top half, insert as first child 1940 parent.insertFirst(element); 1941 currentIndex = 0; 1942 return currentIndex; 1943 } 1944 // over bottom half, insert as last child 1945 parent.appendChild(element); 1946 currentIndex = parent.getChildCount() - 1; 1947 return currentIndex; 1948 } 1949 1950 /** 1951 * Returns the first element matching the given CSS selector.<p> 1952 * 1953 * @param selector the CSS selector 1954 * @param context the context element, may be <code>null</code> 1955 * 1956 * @return the matching element 1957 */ 1958 public static native Element querySelector(String selector, Element context)/*-{ 1959 if (context != null) { 1960 return context.querySelector(selector); 1961 } else { 1962 return $doc.querySelector(selector); 1963 } 1964 }-*/; 1965 1966 /** 1967 * Returns a list of elements matching the given CSS selector.<p> 1968 * 1969 * @param selector the CSS selector 1970 * @param context the context element, may be <code>null</code> 1971 * 1972 * @return the list of matching elements 1973 */ 1974 public static native NodeList<Element> querySelectorAll(String selector, Element context)/*-{ 1975 if (context != null) { 1976 return context.querySelectorAll(selector); 1977 } else { 1978 $doc.querySelectorAll(selector); 1979 } 1980 }-*/; 1981 1982 /** 1983 * Removes any present overlay from the element and it's children.<p> 1984 * 1985 * @param element the element 1986 */ 1987 public static void removeDisablingOverlay(Element element) { 1988 1989 List<Element> overlays = CmsDomUtil.getElementsByClass( 1990 I_CmsLayoutBundle.INSTANCE.generalCss().disablingOverlay(), 1991 Tag.div, 1992 element); 1993 if (overlays == null) { 1994 return; 1995 } 1996 for (Element overlay : overlays) { 1997 overlay.getParentElement().getStyle().clearPosition(); 1998 overlay.removeFromParent(); 1999 } 2000 element.removeClassName(I_CmsLayoutBundle.INSTANCE.generalCss().hideOverlay()); 2001 } 2002 2003 /** 2004 * Removes all script tags from the given element.<p> 2005 * 2006 * @param element the element to remove the script tags from 2007 * 2008 * @return the resulting element 2009 */ 2010 public static Element removeScriptTags(Element element) { 2011 2012 NodeList<Element> scriptTags = element.getElementsByTagName(Tag.script.name()); 2013 // iterate backwards over list to ensure all tags get removed 2014 for (int i = scriptTags.getLength() - 1; i >= 0; i--) { 2015 scriptTags.getItem(i).removeFromParent(); 2016 } 2017 return element; 2018 } 2019 2020 /** 2021 * Removes all script tags from the given string.<p> 2022 * 2023 * @param source the source string 2024 * 2025 * @return the resulting string 2026 */ 2027 public static native String removeScriptTags(String source)/*-{ 2028 2029 var matchTag = /<script[^>]*?>[\s\S]*?<\/script>/g; 2030 return source.replace(matchTag, ""); 2031 }-*/; 2032 2033 /** 2034 * Calls {@link org.opencms.gwt.client.I_CmsDescendantResizeHandler#onResizeDescendant()} on the closest resizable ancestor.<p> 2035 * 2036 * @param parent the parent widget 2037 */ 2038 public static void resizeAncestor(Widget parent) { 2039 2040 while (parent != null) { 2041 if (parent instanceof I_CmsDescendantResizeHandler) { 2042 ((I_CmsDescendantResizeHandler)parent).onResizeDescendant(); 2043 return; 2044 } else { 2045 parent = parent.getParent(); 2046 } 2047 } 2048 } 2049 2050 /** 2051 * Loads a list of stylesheets and invokes a Javascript callback after everything has been loaded.<p> 2052 * 2053 * @param stylesheets the array of stylesheet uris 2054 * @param callback the callback to call after everything is loaded 2055 */ 2056 public static void safeLoadStylesheets(String[] stylesheets, JavaScriptObject callback) { 2057 2058 CmsStylesheetLoader loader = new CmsStylesheetLoader(Arrays.asList(stylesheets), new Runnable() { 2059 2060 public native void call(JavaScriptObject jsCallback) /*-{ 2061 jsCallback(); 2062 }-*/; 2063 2064 public void run() { 2065 2066 if (callback != null) { 2067 call(callback); 2068 } 2069 } 2070 2071 }); 2072 loader.loadWithTimeout(5000); 2073 } 2074 2075 /** 2076 * Sets an attribute on a Javascript object.<p> 2077 * 2078 * @param jso the Javascript object 2079 * @param key the attribute name 2080 * @param value the new attribute value 2081 */ 2082 public static native void setAttribute(JavaScriptObject jso, String key, JavaScriptObject value) /*-{ 2083 jso[key] = value; 2084 }-*/; 2085 2086 /** 2087 * Sets an attribute on a Javascript object.<p> 2088 * 2089 * @param jso the Javascript object 2090 * @param key the attribute name 2091 * @param value the new attribute value 2092 */ 2093 public static native void setAttribute(JavaScriptObject jso, String key, String value) /*-{ 2094 jso[key] = value; 2095 }-*/; 2096 2097 /** 2098 * Sets the stylesheet text for the stylesheet with the given ID.<p> 2099 * 2100 * If the stylesheet with the id does not already exist, it is created. 2101 * 2102 * @param id the stylesheet id 2103 * @param styleText the stylesheet text 2104 */ 2105 public static void setStylesheetText(String id, String styleText) { 2106 2107 Document document = Document.get(); 2108 Element elem = document.getElementById(id); 2109 if (elem == null) { 2110 elem = document.createStyleElement(); 2111 elem.setId(id); 2112 document.getHead().appendChild(elem); 2113 } 2114 elem.setInnerHTML(styleText); 2115 } 2116 2117 /** 2118 * Sets a CSS class to show or hide a given overlay. Will not add an overlay to the element.<p> 2119 * 2120 * @param element the parent element of the overlay 2121 * @param show <code>true</code> to show the overlay 2122 */ 2123 public static void showOverlay(Element element, boolean show) { 2124 2125 if (show) { 2126 element.removeClassName(I_CmsLayoutBundle.INSTANCE.generalCss().hideOverlay()); 2127 } else { 2128 element.addClassName(I_CmsLayoutBundle.INSTANCE.generalCss().hideOverlay()); 2129 } 2130 } 2131 2132 /** 2133 * Shows a message that a popup was blocked.<p> 2134 */ 2135 public static void showPopupBlockerMessage() { 2136 2137 CmsAlertDialog alertDialog = new CmsAlertDialog(messagePopupBlockedTitle(), messagePopupBlocked()); 2138 alertDialog.center(); 2139 } 2140 2141 /** 2142 * Returns the text content to any HTML. 2143 * 2144 * @param html the HTML 2145 * 2146 * @return the text content 2147 */ 2148 public static String stripHtml(String html) { 2149 2150 if (html == null) { 2151 return null; 2152 } 2153 Element el = DOM.createDiv(); 2154 el.setInnerHTML(html); 2155 return el.getInnerText(); 2156 } 2157 2158 /** 2159 * Updates a set of style properties on the given style object, and returns a map with the previous values. 2160 * 2161 * If a value in the map is null, it is interpreted as clearing the style property with that name. 2162 * 2163 * @param style the style object to update 2164 * @param properties the map of properties to change 2165 * 2166 * @return the map of previous values of the given properties 2167 */ 2168 public static Map<String, String> updateStyle( 2169 com.google.gwt.dom.client.Style style, 2170 Map<String, String> properties) { 2171 2172 Map<String, String> oldProps = new HashMap<>(); 2173 for (Map.Entry<String, String> entry : properties.entrySet()) { 2174 String prop = entry.getKey(); 2175 String value = entry.getValue(); 2176 String oldValue = style.getProperty(prop); 2177 oldProps.put(prop, oldValue); 2178 if (value != null) { 2179 style.setProperty(prop, value); 2180 } else { 2181 style.clearProperty(prop); 2182 } 2183 2184 } 2185 return oldProps; 2186 } 2187 2188 /** 2189 * Wraps a widget in a scrollable FlowPanel.<p> 2190 * 2191 * @param widget the original widget 2192 * @return the wrapped widget 2193 */ 2194 public static FlowPanel wrapScrollable(Widget widget) { 2195 2196 FlowPanel wrapper = new FlowPanel(); 2197 wrapper.add(widget); 2198 makeScrollable(wrapper); 2199 return wrapper; 2200 } 2201 2202 /** 2203 * Creates a hidden input field with the given name and value.<p> 2204 * 2205 * @param name the field name 2206 * @param value the field value 2207 * @return the input element 2208 */ 2209 private static InputElement createHiddenInput(String name, String value) { 2210 2211 InputElement input = Document.get().createHiddenInputElement(); 2212 input.setName(name); 2213 input.setValue(value); 2214 return input; 2215 } 2216 2217 /** 2218 * Returns the DOM implementation.<p> 2219 * 2220 * @return the DOM implementation 2221 */ 2222 private static DOMImpl getDOMImpl() { 2223 2224 if (domImpl == null) { 2225 domImpl = GWT.create(DOMImpl.class); 2226 } 2227 return domImpl; 2228 } 2229 2230 /** 2231 * Returns the document style implementation.<p> 2232 * 2233 * @return the document style implementation 2234 */ 2235 private static DocumentStyleImpl getStyleImpl() { 2236 2237 if (styleImpl == null) { 2238 styleImpl = GWT.create(DocumentStyleImpl.class); 2239 } 2240 return styleImpl; 2241 } 2242 2243 /** 2244 * Injects a script tag into the page head.<p> 2245 * 2246 * @param scriptLink the link to the javascript resource 2247 * @param async the value for the async attribute of the new script node 2248 * @param onload load handler for the script 2249 */ 2250 private static native void injectScript(String scriptLink, boolean async, JavaScriptObject onload)/*-{ 2251 var headID = $wnd.document.getElementsByTagName("head")[0]; 2252 var scriptNode = $wnd.document.createElement('script'); 2253 scriptNode.async = async; 2254 if (onload) { 2255 scriptNode.onload = onload; 2256 } 2257 scriptNode.src = scriptLink; 2258 headID.appendChild(scriptNode); 2259 }-*/; 2260 2261 /** 2262 * Internal method to indicate if the given element has a CSS class.<p> 2263 * 2264 * @param className the class name to look for 2265 * @param element the element 2266 * 2267 * @return <code>true</code> if the element has the given CSS class 2268 */ 2269 private static boolean internalHasClass(String className, Element element) { 2270 2271 boolean hasClass = false; 2272 try { 2273 String elementClass = element.getClassName().trim(); 2274 hasClass = elementClass.equals(className); 2275 hasClass |= elementClass.contains(" " + className + " "); 2276 hasClass |= elementClass.startsWith(className + " "); 2277 hasClass |= elementClass.endsWith(" " + className); 2278 } catch (Throwable t) { 2279 // ignore 2280 } 2281 return hasClass; 2282 } 2283 2284 /** 2285 * Checks if the given color value is transparent.<p> 2286 * 2287 * @param backgroundColor the color value 2288 * 2289 * @return <code>true</code> if transparent 2290 */ 2291 private static boolean isTransparent(String backgroundColor) { 2292 2293 // not only check 'transparent' but also 'rgba(0, 0, 0, 0)' as returned by chrome 2294 return StyleValue.transparent.toString().equalsIgnoreCase(backgroundColor) 2295 || "rgba(0, 0, 0, 0)".equalsIgnoreCase(backgroundColor); 2296 } 2297 2298}