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

com.google.gwt.core.client.ScriptInjector Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2011 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.gwt.core.client;

/*
 * Design note: This class intentionally does not use the GWT DOM wrappers so
 * that this code can pull in as few dependencies as possible and live in the
 * Core module.
 */

/**
 * Dynamically create a script tag and attach it to the DOM.
 * 
 * Usage with script as local string:
 * 

* *

 *   String scriptBody = "var foo = ...";
 *   ScriptInjector.fromString(scriptBody).inject();
 * 
*

* Usage with script loaded as URL: *

* *

 *   ScriptInjector.fromUrl("http://example.com/foo.js").setCallback(
 *     new Callback() {
 *        public void onFailure(Exception reason) {
 *          Window.alert("Script load failed.");
 *        }
 *        public void onSuccess(Void result) {
 *          Window.alert("Script load success.");
 *        }
 *     }).inject();
 * 
* * */ public class ScriptInjector { /** * Builder for directly injecting a script body into the DOM. */ public static class FromString { private boolean removeTag = true; private final String scriptBody; private JavaScriptObject window; /** * @param scriptBody The script text to install into the document. */ public FromString(String scriptBody) { this.scriptBody = scriptBody; } /** * Injects a script into the DOM. The JavaScript is evaluated and will be * available immediately when this call returns. * * By default, the script is installed in the same window that the GWT code * is installed in. * * @return the script element created for the injection. Note that it may be * removed from the DOM. */ public JavaScriptObject inject() { JavaScriptObject wnd = (window == null) ? nativeDefaultWindow() : window; assert wnd != null; JavaScriptObject doc = nativeGetDocument(wnd); assert doc != null; JavaScriptObject scriptElement = nativeMakeScriptElement(doc); assert scriptElement != null; nativeSetText(scriptElement, scriptBody); nativeAttachToHead(doc, scriptElement); if (removeTag) { nativeRemove(scriptElement); } return scriptElement; } /** * @param removeTag If true, remove the tag immediately after injecting the * source. This shrinks the DOM, possibly at the expense of * readability if you are debugging javaScript. * * Default value is {@code true}. */ public FromString setRemoveTag(boolean removeTag) { this.removeTag = removeTag; return this; } /** * @param window Specify which window to use to install the script. If not * specified, the top current window GWT is loaded in is used. */ public FromString setWindow(JavaScriptObject window) { this.window = window; return this; } } /** * Build an injection call for adding a script by URL. */ public static class FromUrl { private Callback callback; private boolean removeTag = false; private final String scriptUrl; private JavaScriptObject window; private FromUrl(String scriptUrl) { this.scriptUrl = scriptUrl; } /** * Injects an external JavaScript reference into the document and optionally * calls a callback when it finishes loading. * * @return the script element created for the injection. */ public JavaScriptObject inject() { JavaScriptObject wnd = (window == null) ? nativeDefaultWindow() : window; assert wnd != null; JavaScriptObject doc = nativeGetDocument(wnd); assert doc != null; JavaScriptObject scriptElement = nativeMakeScriptElement(doc); assert scriptElement != null; if (callback != null || removeTag) { attachListeners(scriptElement, callback, removeTag); } nativeSetSrc(scriptElement, scriptUrl); nativeAttachToHead(doc, scriptElement); return scriptElement; } /** * Specify a callback to be invoked when the script is loaded or loading * encounters an error. *

* Warning: This class does not control whether or not a URL * has already been injected into the document. The client of this class has * the responsibility of keeping score of the injected JavaScript files. *

* Known bugs: This class uses the script tag's onerror() * callback to attempt to invoke onFailure() if the * browser detects a load failure. This is not reliable on all browsers * (Doesn't work on IE or Safari 3 or less). *

* On Safari version 3 and prior, the onSuccess() callback may be invoked * even when the load of a page fails. *

* To support failure notification on IE and older browsers, you should * check some side effect of the script (such as a defined function) * to see if loading the script worked and include timeout logic. * * @param callback callback that gets invoked asynchronously. */ public FromUrl setCallback(Callback callback) { this.callback = callback; return this; } /** * @param removeTag If true, remove the tag after the script finishes * loading. This shrinks the DOM, possibly at the expense of * readability if you are debugging javaScript. * * Default value is {@code false}, but this may change in a future * release. */ public FromUrl setRemoveTag(boolean removeTag) { this.removeTag = removeTag; return this; } /** * This call allows you to specify which DOM window object to install the * script tag in. To install into the Top level window call * * * builder.setWindow(ScriptInjector.TOP_WINDOW); * * * @param window Specifies which window to install in. */ public FromUrl setWindow(JavaScriptObject window) { this.window = window; return this; } } /** * Returns the top level window object. Use this to inject a script so that * global variable references are available under $wnd in JSNI * access. *

* Note that if your GWT app is loaded from a different domain than the top * window, you may not be able to add a script element to the top window. */ public static final JavaScriptObject TOP_WINDOW = nativeTopWindow(); /** * Build an injection call for directly setting the script text in the DOM. * * @param scriptBody the script text to be injected and immediately executed. */ public static FromString fromString(String scriptBody) { return new FromString(scriptBody); } /** * Build an injection call for adding a script by URL. * * @param scriptUrl URL of the JavaScript to be injected. */ public static FromUrl fromUrl(String scriptUrl) { return new FromUrl(scriptUrl); } /** * Attaches event handlers to a script DOM element that will run just once a * callback when it gets successfully loaded. *

* IE Notes: Internet Explorer calls {@code onreadystatechanged} * several times while varying the {@code readyState} property: in theory, * {@code "complete"} means the content is loaded, parsed and ready to be * used, but in practice, {@code "complete"} happens when the JS file was * already cached, and {@code "loaded"} happens when it was transferred over * the network. Other browsers just call the {@code onload} event handler. To * ensure the callback will be called at most once, we clear out both event * handlers when the callback runs for the first time. More info at the phpied.com * blog. *

* In IE, do not trust the "order" of {@code readyState} values. For instance, * in IE 8 running in Vista, if the JS file is cached, only {@code "complete"} * will happen, but if the file has to be downloaded, {@code "loaded"} can * fire in parallel with {@code "loading"}. * * * @param scriptElement element to which the event handlers will be attached * @param callback callback that runs when the script is loaded and parsed. */ private static native void attachListeners(JavaScriptObject scriptElement, Callback callback, boolean removeTag) /*-{ function clearCallbacks() { scriptElement.onerror = scriptElement.onreadystatechange = scriptElement.onload = null; if (removeTag) { @com.google.gwt.core.client.ScriptInjector::nativeRemove(Lcom/google/gwt/core/client/JavaScriptObject;)(scriptElement); } } scriptElement.onload = $entry(function() { clearCallbacks(); if (callback) { [email protected]::onSuccess(Ljava/lang/Object;)(null); } }); // or possibly more portable script_tag.addEventListener('error', function(){...}, true); scriptElement.onerror = $entry(function() { clearCallbacks(); if (callback) { var ex = @com.google.gwt.core.client.CodeDownloadException::new(Ljava/lang/String;)("onerror() called."); [email protected]::onFailure(Ljava/lang/Object;)(ex); } }); scriptElement.onreadystatechange = $entry(function() { if (/loaded|complete/.test(scriptElement.readyState)) { scriptElement.onload(); } }); }-*/; private static native void nativeAttachToHead(JavaScriptObject doc, JavaScriptObject scriptElement) /*-{ // IE8 does not have document.head (doc.head || doc.getElementsByTagName("head")[0]).appendChild(scriptElement); }-*/; private static native JavaScriptObject nativeDefaultWindow() /*-{ return window; }-*/; private static native JavaScriptObject nativeGetDocument(JavaScriptObject wnd) /*-{ return wnd.document; }-*/; private static native JavaScriptObject nativeMakeScriptElement(JavaScriptObject doc) /*-{ return doc.createElement("script"); }-*/; private static native void nativeRemove(JavaScriptObject scriptElement) /*-{ scriptElement.parentNode.removeChild(scriptElement); }-*/; private static native void nativeSetSrc(JavaScriptObject element, String url) /*-{ element.src = url; }-*/; private static native void nativeSetText(JavaScriptObject element, String scriptBody) /*-{ element.text = scriptBody; }-*/; private static native JavaScriptObject nativeTopWindow() /*-{ return $wnd; }-*/; /** * Utility class - do not instantiate */ private ScriptInjector() { } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy