All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.polymer.Polymer Maven / Gradle / Ivy

The newest version!
package com.vaadin.polymer;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;

import com.vaadin.polymer.elemental.Function;
import com.vaadin.polymer.elemental.HTMLElement;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static jsinterop.annotations.JsPackage.GLOBAL;

import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsProperty;
import jsinterop.annotations.JsType;

@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class Polymer {

    private static PolymerRoot Polymer;
    public static Base Base;
    private static boolean hasHtmlImports = htmlImportsSupported();

    /**
     * Set the location of the bower_components for the application.
     * By default it is computed from the module base.
     *
     * Additionally this can be set from JS modifying the
     * `window.gwtBowerLocation` property.
     */
    @JsProperty(namespace = JsPackage.GLOBAL)
    public static native void setGwtBowerLocation(String s);

    @JsProperty(namespace = JsPackage.GLOBAL)
    public static native String getGwtBowerLocation();


    static {
        if (getGwtBowerLocation() == null) {
            String moduleBase = GWT.getModuleBaseForStaticFiles();
            String moduleName = GWT.getModuleName();
            // SSO linker does not set correctly the module base
            if (!moduleBase.contains(moduleName)) {
                moduleBase = moduleName + "/";
            }
            setGwtBowerLocation(moduleBase + "bower_components/");
        }
    }

    @JsType(isNative=true, namespace="Polymer")
    public interface DomApi {

         T querySelector(String selector);

        JsArray querySelectorAll(String selector);

        void appendChild(Object el);

        JsArray children();
    }

    @JsType(isNative=true, namespace=GLOBAL)
    public interface PolymerRoot {
        void updateStyles();

        DomApi dom(Object el);
    }

    @JsType(isNative=true, namespace="Polymer")
    public interface Base {

        /**
         * Returns the first node in this element’s local DOM that matches selector.
         */
        Element $$(String selector);

        /**
         * Toggles the named boolean class on the node, adding the class if bool is
         * truthy and removing it if bool is falsey.
         */
        void toggleClass(String name, boolean b, Element node);

        /**
         * Toggles the named boolean attribute on the node.
         */
        void toggleAttribute(String name, boolean b, Element node);

        /**
         * Moves a boolean attribute from oldNode to newNode, unsetting the attribute
         * (if set) on oldNode and setting it on newNode
         */
        void attributeFollows(String name, Element newNode, Element oldNode);

        /**
         *  Moves a class from oldNode to newNode, removing the class (if present)
         *  on oldNode and adding it to newNode.
         */
        void classFollows(String name, Element newNode, Element oldNode);


        /**
         * Fires a custom event. The options object can contain the following properties:
         *    node: Node to fire the event on (defaults to this).
         *    bubbles: Whether the event should bubble. Defaults to true.
         *    cancelable: Whether the event can be canceled with preventDefault. Defaults to false.
         */
        void fire(String type, JavaScriptObject detail, JavaScriptObject options);

        /**
         * Calls method asynchronously. If no wait time is specified, runs tasks with microtask
         * timing (after the current method finishes, but before the next event from the event
         * queue is processed). Returns a handle that can be used to cancel the task.
         */
        Object async(Function method, int wait);

        /**
         * Cancels the identified async task.
         */
        void cancelAsync(Object handle);

        /**
         *  Applies a CSS transform to the specified node, or host element if no node is
         *  specified. transform is specified as a string. For example:
         *    transform('rotateX(90deg)', elm);
         */
        void transform(String transform, Element node);

        /**
         * Transforms the specified node, or host element if no node is specified. For example:
         *   translate3d('100px', '100px', '100px', elm);
         */
        void translate3d(String x, String y, String z, Element node);

        /**
         *  Dynamically imports an HTML document.
         */
        void importHref(String href, Function onload, Function onerror);

        /**
         * Takes a URL relative to the  of an imported Polymer element, and returns
         * a path relative to the current document. This method can be used, for example,
         * to refer to an asset delivered alongside an HTML import.
         */
        String resolveUrl(String url);
    }

    private static Set urlImported = new HashSet<>();
    private static HashMap> whenImported = new HashMap<>();

    /**
     * Inserts the appropriate <import> of a component given by url.
     *
     * @param href either an absolute url or a path relative to bower_components folder.
     */
    public static void importHref(String href) {
        importHref(href, null, null);
    }

    /**
     * Inserts the appropriate <import> of a component given by url.
     *
     * @param href either an absolute url or a path relative to bower_components folder.
     * @param ok callback to run in case of success
     */
    public static void importHref(String href, Function ok) {
        importHref(href, ok, null);
    }

    private static String absoluteHref(String hrefOrTag) {
        if (!hrefOrTag.startsWith("http")) {
            // It's a tag
            if (hrefOrTag.matches("[\\w-]+")) {
                hrefOrTag = hrefOrTag + "/" + hrefOrTag;
            }
            // Not ending with html or js
            if (!hrefOrTag.matches(".*\\.(html|js)$")) {
                hrefOrTag += ".html";
            }
            hrefOrTag = getGwtBowerLocation() + hrefOrTag;
        }
        return hrefOrTag;
    }

    // Loads Polymer once if not done yet, and queue all callbaks until ready
    private static native void whenPolymerLoaded(Function ok)
    /*-{
        function resolve() {
          // Set our static reference to Base
          @com.vaadin.polymer.Polymer::Polymer = $wnd.Polymer;
          @com.vaadin.polymer.Polymer::Base = $wnd.Polymer.Base;
          // Polymer dynamic loaded does not remove unresolved
          $doc.body.removeAttribute('unresolved');
        }
        if (!$wnd.Polymer) {
            // Dynamic load Polymer and wait until ready
            if (!$wnd._pending_oks) {
                $wnd._pending_oks = [ok];
                var l = $doc.createElement('link');
                l.rel = 'import';
                l.href = @com.vaadin.polymer.Polymer::absoluteHref(*)('polymer');
                l.onload = function() {
                  // Run all tasks waiting for Polymer be ready
                  $wnd._pending_oks.forEach(function(ok){ok()});
                  $wnd._pending_oks = undefined;
                  resolve();
                };
                $doc.head.appendChild(l);
            }
            $wnd._pending_oks.push(ok);
        } else {
            resolve();
            ok();
        }
    }-*/;

    /**
     * Inserts the appropriate <import> of a component given by url.
     * If the components is already registered it does not import anything so as
     * the user could import elements in the hosted page, or vulcanize them.
     * If the component is being imported, all callbacks are queued until ready.
     *
     * @param hrefOrTag it can be an absolute url, a relative path or a tag name.
     *                  - if it is a relative path, we prefix it with bower_components
     *                  in case it is not already prefixed.
     *                  - if it is a tag, we compose the relative url:
     *                  bower_components/tagName/tagName.html
     * @param ok callback to run in case of success
     * @param err callback to run in case of failure
     */
    public static void importHref(String hrefOrTag, final Function ok, final Function err) {
        final String href = absoluteHref(hrefOrTag);

        Function done = arg -> {
                urlImported.add(href);
                List pending = whenImported.get(href);
                if (pending != null) {
                    for (Function f : pending) {
                        f.call(null);
                    }
                }
                whenImported.remove(href);
                return null;
        };
        if (Base == null) {
            whenPolymerLoaded(arg -> {
                importHref(hrefOrTag, ok, err);
                return null;
            });
            return;
        }
        if (!urlImported.contains(href)) {
            if (!isRegistered(href)) {
                List pending = whenImported.get(href);
                if (pending == null) {
                    pending = new ArrayList();
                    whenImported.put(href, pending);
                    Base.importHref(href, done, err);
                }
                if (ok != null) {
                    pending.add(ok);
                }
                return;
            }
            urlImported.add(href);
        }
        if (ok != null) {
            ok.call(null);
        }
    }

    /**
     * Inserts the appropriate <import> of a list of components
     *
     * @param hrefs a list of absolute urls or relative paths to load.
     */
    public static void importHref(final List hrefs) {
        importHref(hrefs, null, null);
    }

    /**
     * Inserts the appropriate <import> of a list of components
     *
     * @param hrefs a list of absolute urls or relative paths to load.
     * @param ok callback to run in case of all import success
     * @param err callback to run in case of failure
     */
    public static void importHref(final List hrefs, final Function ok) {
        importHref(hrefs, ok, null);
    }

    /**
     * Inserts the appropriate <import> of a list of components
     *
     * @param hrefs a list of absolute urls or relative paths to load.
     * @param ok callback to run in case of all import success
     * @param err callback to run in case of failure
     */
    public static void importHref(final List hrefs, final Function ok, Function err) {
        Function allOk = ok == null ? ok : new Function() {
            int count = hrefs.size();
            public Object call(Object arg) {
                if (--count == 0) {
                    ok.call(arg);
                }
                return null;
            }
        };
        for (String href : hrefs) {
            importHref(href, allOk, err);
        }
    }

    /**
     * Returns a new instance of the Element. It loads the web component
     * from the bower_components/src url if it was not loaded yet.
     */
    public static  T createElement(final String tagName, final String... imports) {
        final T e = (T)Document.get().createElement(tagName);
        if (imports.length > 0) {
            ensureCustomElement(e, imports);
        } else {
            ensureCustomElement(e, tagName);
        }
        return e;
    }

    public static  void ensureCustomElement(final T elem, String... imports) {
        if (isRegisteredElement(elem)) {
            return;
        }

        // Import all necessary stuff for this element
        for (String src : imports) {
            importHref(src, null, null);
        }
    }

    /**
     * Returns a new instance of the Element. It loads the web component
     * from the bower_components/tagName/tagName.html url if not loaded yet.
     */
    public static  T createElement(String tagName) {
        return createElement(tagName, new String[]{});
    }

    /**
     * Returns the JsInterop instance of Document
     */
    public static com.vaadin.polymer.elemental.Document getDocument() {
        return (com.vaadin.polymer.elemental.Document)Document.get();
    }

    /**
     * Return true if the element is already registered.
     * Useful when components are loaded previously, i.e. when vulcanizing imports.
     */
    private static boolean isRegistered(String hrefOrTag) {
        Element e = Document.get().createElement(hrefOrTag.replaceFirst("^.*/(.+).html$", "$1"));
        return isRegisteredElement(e);
    }

    /**
     * Check whether a certain custom element has been registered.
     */
    private native static boolean isRegisteredElement(Object e)
    /*-{
        return !!e && e.constructor !== $wnd.HTMLElement && e.constructor != $wnd.HTMLUnknownElement;
    }-*/;

    public static void ready(HTMLElement e, Function f) {
        whenReady(f, (Element)e);
    }

    public static void ready(Element e, Function f) {
        whenReady(f, e);
    }

    /**
     * Update all style elements on the page.
     */
    public static void updateStyles() {
        Polymer.updateStyles();
    }

    /**
     * Executes a function after all imports have been loaded.
     */
    public static void whenReady(Function f) {
        whenReady(f, null);
    }

    private native static boolean htmlImportsSupported()
    /*-{
        return 'import' in $doc.createElement('link');
    }-*/;

    /**
     * Executes a function after all imports have been loaded and when the
     * passed element is ready to use.
     * For browsers not supporting html imports, it loads the webcomponentsjs polyfill.
     */
    public static native void whenReady(Function f, Element e)
    /*-{
        function registered() {
          if (e) {
              var id = setInterval(function() {
                if (@com.vaadin.polymer.Polymer::isRegisteredElement(*)(e)) {
                  clearInterval(id);
                  if (f) f(e);
                }
              }, 10);
          } else {
              if (f) f();
          }
        }
        function done() {
            $wnd.HTMLImports.whenReady(registered);
        }
        function loadPolyfill() {
            var s = $doc.createElement('script');
            s.src = @com.vaadin.polymer.Polymer::absoluteHref(*)
                        ('webcomponentsjs/webcomponents-lite.min.js');
            s.onreadystatechange = s.onload = done;
            $doc.head.appendChild(s);
        }
        if (!$wnd.HTMLImports) {
            if (@com.vaadin.polymer.Polymer::hasHtmlImports) {
                registered();
            } else {
                loadPolyfill();
            }
        } else {
           done();
        }
    }-*/;

    /**
     * If an element is not ready, loops until it gets ready, then
     * run a Function (JsFunction or JavaFunction)
     * @deprecated use {@link #whenReady(Function, Element)} instead.
     */
    @Deprecated
    private static void onReady(Element e, Object f) {
        whenReady((Function)f, e);
    }

    /**
     * Utility method to show a loading element if there is no one in
     * hosting page.
     */
    public static void startLoading() {
        Element l = DOM.getElementById("loading");
        if (l == null) {
            l = DOM.createDiv();
            l.setAttribute("style", "position:fixed;top:0px;left:0px;width:100%;text-align:center;font-family:arial;font-size:24px;color:#4285f4;");
            l.setId("loading");
            l.setInnerText("loading" + "...");
            Document.get().getBody().appendChild(l);
        }
    }

    public static void endLoading(final Element container, Element webcomponent) {
        endLoading(container, webcomponent, null);
    }

    /**
     * Utility method to remove a loading message and show a container when a
     * web component becomes available.
     *
     * @param container : The container to show when the component is available
     * @param webcomponent : Web component to monitor
     * @param callback : Calback function
     */
    public static void endLoading(final Element container, Element webcomponent, final Function func) {
        container.getStyle().setOpacity(0);
        container.getStyle().setProperty("transition", "opacity 1.1s");
        ready(webcomponent, new Function() {
            public Object call(Object arg) {
                reFlow();
                container.getStyle().setOpacity(1);
                DOM.getElementById("loading").getStyle().setOpacity(0);
                return func != null ? func.call(arg) : null;
            }
        });
    }

    /**
     * Force a browser re-flow. For some reason in Chrome we need to force
     * it the very first time we initialize the UI. It seems it happens with
     * widgets and no with elements but not 100% positive. To test it try
     * to reload the app in SDM and do not move the mouse, moving or clicking
     * mouse on body also makes the UI re-draw.
     *
     */
    private static native void reFlow()
    /*-{
      if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
        var c = 0;
        var id = setInterval(function() {
         // Using $doc.body.offsetWidth in an if, otherwise closure
         // compiler prunes it.
         if (c++ >= 20 && $doc.body.offsetWidth > 0)
          clearInterval(id);
       }, 50);
      }
    }-*/;

    /**
     * Box a native JS array in a Java List. It does not have any performance
     * penalty because we directly set the native array of the super ArrayList
     * implementation.
     */
    public static native  List asList(JavaScriptObject o)
    /*-{
        var l = @java.util.ArrayList::new()();
        [email protected]::array = o;
        return l;
    }-*/;

    /**
     * UnBox the native JS array in a Java List. It does not have any performance
     * penalty because we directly take the native array of the super ArrayList
     * implementation.
     */
    public static native  JsArray asJsArray(List l)
    /*-{
        return [email protected]::array;
    }-*/;

    /**
     * Utility method for getting a property for a JS object
     */
    public native static  T property(Object jso, String name)
    /*-{
       return jso[name] || null;
    }-*/;

    /**
     * Utility method for setting properties to JS objects.
     * In case that value is a JSON string it must be based to Polymer attribute setter
     * so as it's able to parse. Needed when setting json values in UIBinder.
     */
    public native static void property(HTMLElement jso, String name, String value)
    /*-{
        if (jso.setAttribute && /^[\[\{]/.test(value)) {
          jso.setAttribute(name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(), value);
        } else {
          jso[name] = value;
        }
    }-*/;

    /**
     * Utility method for setting properties to JS objects.
     */
    public native static void property(Object jso, String name, Object value)
    /*-{
       jso[name] = value;
    }-*/;

    /**
     * Utility method for setting a function to a JS object.
     * Useful for binding functions to templates.
     */
    public static void function(Object jso, String name, Function fnc) {
        property(jso, name, fnc);
    }

    /**
     * Utility method for calling a function of a JS object.
     */
    public native static  T apply(Object jso, String methodName, Object... args)
    /*-{
       return jso[methodName].apply(jso, args);
    }-*/;

    public static native  T cast(Object o)
    /*-{
      return o;
    }-*/;

    /**
     * Return the dom API of one element.
     */
    public static DomApi dom(Object element) {
        return Polymer.dom(element);
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy