com.vaadin.client.ResourceLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vaadin-client Show documentation
Show all versions of vaadin-client Show documentation
Vaadin is a web application framework for Rich Internet Applications (RIA).
Vaadin enables easy development and maintenance of fast and
secure rich web
applications with a stunning look and feel and a wide browser support.
It features a server-side architecture with the majority of the logic
running
on the server. Ajax technology is used at the browser-side to ensure a
rich
and interactive user experience.
/*
* Copyright 2000-2014 Vaadin Ltd.
*
* 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.vaadin.client;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.LinkElement;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.ObjectElement;
import com.google.gwt.dom.client.ScriptElement;
import com.google.gwt.user.client.Timer;
/**
* ResourceLoader lets you dynamically include external scripts and styles on
* the page and lets you know when the resource has been loaded.
*
* You can also preload resources, allowing them to get cached by the browser
* without being evaluated. This enables downloading multiple resources at once
* while still controlling in which order e.g. scripts are executed.
*
* @author Vaadin Ltd
* @since 7.0.0
*/
public class ResourceLoader {
/**
* Event fired when a resource has been loaded.
*/
public static class ResourceLoadEvent {
private final ResourceLoader loader;
private final String resourceUrl;
private final boolean preload;
/**
* Creates a new event.
*
* @param loader
* the resource loader that has loaded the resource
* @param resourceUrl
* the url of the loaded resource
* @param preload
* true if the resource has only been preloaded, false if
* it's fully loaded
*/
public ResourceLoadEvent(ResourceLoader loader, String resourceUrl,
boolean preload) {
this.loader = loader;
this.resourceUrl = resourceUrl;
this.preload = preload;
}
/**
* Gets the resource loader that has fired this event
*
* @return the resource loader
*/
public ResourceLoader getResourceLoader() {
return loader;
}
/**
* Gets the absolute url of the loaded resource.
*
* @return the absolute url of the loaded resource
*/
public String getResourceUrl() {
return resourceUrl;
}
/**
* Returns true if the resource has been preloaded, false if it's fully
* loaded
*
* @see ResourceLoader#preloadResource(String, ResourceLoadListener)
*
* @return true if the resource has been preloaded, false if it's fully
* loaded
*/
public boolean isPreload() {
return preload;
}
}
/**
* Event listener that gets notified when a resource has been loaded
*/
public interface ResourceLoadListener {
/**
* Notifies this ResourceLoadListener that a resource has been loaded.
* Some browsers do not support any way of detecting load errors. In
* these cases, onLoad will be called regardless of the status.
*
* @see ResourceLoadEvent
*
* @param event
* a resource load event with information about the loaded
* resource
*/
public void onLoad(ResourceLoadEvent event);
/**
* Notifies this ResourceLoadListener that a resource could not be
* loaded, e.g. because the file could not be found or because the
* server did not respond. Some browsers do not support any way of
* detecting load errors. In these cases, onLoad will be called
* regardless of the status.
*
* @see ResourceLoadEvent
*
* @param event
* a resource load event with information about the resource
* that could not be loaded.
*/
public void onError(ResourceLoadEvent event);
}
private static final ResourceLoader INSTANCE = GWT
.create(ResourceLoader.class);
private ApplicationConnection connection;
private final Set loadedResources = new HashSet();
private final Set preloadedResources = new HashSet();
private final Map> loadListeners = new HashMap>();
private final Map> preloadListeners = new HashMap>();
private final Element head;
/**
* Creates a new resource loader. You should generally not create you own
* resource loader, but instead use {@link ResourceLoader#get()} to get an
* instance.
*/
protected ResourceLoader() {
Document document = Document.get();
head = document.getElementsByTagName("head").getItem(0);
// detect already loaded scripts and stylesheets
NodeList scripts = document.getElementsByTagName("script");
for (int i = 0; i < scripts.getLength(); i++) {
ScriptElement element = ScriptElement.as(scripts.getItem(i));
String src = element.getSrc();
if (src != null && src.length() != 0) {
loadedResources.add(src);
}
}
NodeList links = document.getElementsByTagName("link");
for (int i = 0; i < links.getLength(); i++) {
LinkElement linkElement = LinkElement.as(links.getItem(i));
String rel = linkElement.getRel();
String href = linkElement.getHref();
if ("stylesheet".equalsIgnoreCase(rel) && href != null
&& href.length() != 0) {
loadedResources.add(href);
}
}
}
/**
* Returns the default ResourceLoader
*
* @return the default ResourceLoader
*/
public static ResourceLoader get() {
return INSTANCE;
}
/**
* Load a script and notify a listener when the script is loaded. Calling
* this method when the script is currently loading or already loaded
* doesn't cause the script to be loaded again, but the listener will still
* be notified when appropriate.
*
*
* @param scriptUrl
* the url of the script to load
* @param resourceLoadListener
* the listener that will get notified when the script is loaded
*/
public void loadScript(final String scriptUrl,
final ResourceLoadListener resourceLoadListener) {
loadScript(scriptUrl, resourceLoadListener,
!supportsInOrderScriptExecution());
}
/**
* Load a script and notify a listener when the script is loaded. Calling
* this method when the script is currently loading or already loaded
* doesn't cause the script to be loaded again, but the listener will still
* be notified when appropriate.
*
*
* @param scriptUrl
* url of script to load
* @param resourceLoadListener
* listener to notify when script is loaded
* @param async
* What mode the script.async attribute should be set to
* @since 7.2.4
*/
public void loadScript(final String scriptUrl,
final ResourceLoadListener resourceLoadListener, boolean async) {
final String url = WidgetUtil.getAbsoluteUrl(scriptUrl);
ResourceLoadEvent event = new ResourceLoadEvent(this, url, false);
if (loadedResources.contains(url)) {
if (resourceLoadListener != null) {
resourceLoadListener.onLoad(event);
}
return;
}
if (preloadListeners.containsKey(url)) {
// Preload going on, continue when preloaded
preloadResource(url, new ResourceLoadListener() {
@Override
public void onLoad(ResourceLoadEvent event) {
loadScript(url, resourceLoadListener);
}
@Override
public void onError(ResourceLoadEvent event) {
// Preload failed -> signal error to own listener
if (resourceLoadListener != null) {
resourceLoadListener.onError(event);
}
}
});
return;
}
if (addListener(url, resourceLoadListener, loadListeners)) {
ScriptElement scriptTag = Document.get().createScriptElement();
scriptTag.setSrc(url);
scriptTag.setType("text/javascript");
scriptTag.setPropertyBoolean("async", async);
addOnloadHandler(scriptTag, new ResourceLoadListener() {
@Override
public void onLoad(ResourceLoadEvent event) {
fireLoad(event);
}
@Override
public void onError(ResourceLoadEvent event) {
fireError(event);
}
}, event);
head.appendChild(scriptTag);
}
}
/**
* The current browser supports script.async='false' for maintaining
* execution order for dynamically-added scripts.
*
* @return Browser supports script.async='false'
* @since 7.2.4
*/
public static boolean supportsInOrderScriptExecution() {
return BrowserInfo.get().isIE()
&& BrowserInfo.get().getBrowserMajorVersion() >= 11;
}
/**
* Download a resource and notify a listener when the resource is loaded
* without attempting to interpret the resource. When a resource has been
* preloaded, it will be present in the browser's cache (provided the HTTP
* headers allow caching), making a subsequent load operation complete
* without having to wait for the resource to be downloaded again.
*
* Calling this method when the resource is currently loading, currently
* preloading, already preloaded or already loaded doesn't cause the
* resource to be preloaded again, but the listener will still be notified
* when appropriate.
*
* @param url
* the url of the resource to preload
* @param resourceLoadListener
* the listener that will get notified when the resource is
* preloaded
*/
public void preloadResource(String url,
ResourceLoadListener resourceLoadListener) {
url = WidgetUtil.getAbsoluteUrl(url);
ResourceLoadEvent event = new ResourceLoadEvent(this, url, true);
if (loadedResources.contains(url) || preloadedResources.contains(url)) {
// Already loaded or preloaded -> just fire listener
if (resourceLoadListener != null) {
resourceLoadListener.onLoad(event);
}
return;
}
if (addListener(url, resourceLoadListener, preloadListeners)
&& !loadListeners.containsKey(url)) {
// Inject loader element if this is the first time this is preloaded
// AND the resources isn't already being loaded in the normal way
final Element element = getPreloadElement(url);
addOnloadHandler(element, new ResourceLoadListener() {
@Override
public void onLoad(ResourceLoadEvent event) {
fireLoad(event);
Document.get().getBody().removeChild(element);
}
@Override
public void onError(ResourceLoadEvent event) {
fireError(event);
Document.get().getBody().removeChild(element);
}
}, event);
Document.get().getBody().appendChild(element);
}
}
private static Element getPreloadElement(String url) {
/*-
* TODO
* In Chrome, FF:
*