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>&lt;</b> with &amp;lt;
951     * <li><b>&gt;</b> with &amp;gt;
952     * <li><b>&amp;</b> with &amp;amp;
953     * <li><b>&quot;</b> with &amp;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("&lt;");
972                    break;
973                case '>':
974                    result.append("&gt;");
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("&amp;");
988                    break;
989                case '"':
990                    result.append("&quot;");
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 &lt;html&gt; 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}