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