diff options
author | Artur <artur@vaadin.com> | 2017-01-25 11:27:49 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-01-25 11:27:49 +0200 |
commit | be694984fb35262b32c89be075e6d4a059931b62 (patch) | |
tree | d66a85f91404b5bb7f82d845a25b96ec9cebf350 /server/src/main/java | |
parent | e397ea01e5ddb0c977561c008b84c6ed7c0ef706 (diff) | |
download | vaadin-framework-be694984fb35262b32c89be075e6d4a059931b62.tar.gz vaadin-framework-be694984fb35262b32c89be075e6d4a059931b62.zip |
Support loading of HTML imports using @HtmlImport (#8301)
Note that not all browsers yet support HTML imports. If a polyfill
is needed to load HTML imports, it must be loaded before HTML Imports
can be loaded. There is no automatic loading of any polyfills.
Diffstat (limited to 'server/src/main/java')
6 files changed, 333 insertions, 73 deletions
diff --git a/server/src/main/java/com/vaadin/annotations/HtmlImport.java b/server/src/main/java/com/vaadin/annotations/HtmlImport.java new file mode 100644 index 0000000000..f0c52ff171 --- /dev/null +++ b/server/src/main/java/com/vaadin/annotations/HtmlImport.java @@ -0,0 +1,73 @@ +/* + * Copyright 2000-2016 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.vaadin.server.ClientConnector; + +/** + * If this annotation is present on a {@link ClientConnector} class, the + * framework ensures the referenced HTML imports are loaded before the init + * method for the corresponding client-side connector is invoked. + * <p> + * Note that not all browsers yet support HTML imports. If a polyfill is needed + * to load HTML imports, it must be loaded before HTML Imports can be loaded. + * There is no automatic loading of any polyfill. + * <p> + * <ul> + * <li>Relative URLs are interpreted as relative to the {@code VAADIN} folder, + * i.e. {@literal bower_components/paper-slider/paper-slider.html} is equal to + * {@literal vaadin://bower_components/paper-slider/paper-slider.html}. + * <li>Absolute URLs including protocol and host are used as is on the + * client-side. + * </ul> + * Note that it is a good idea to use relative URLs and place all HTML imports + * in the same folder. Polymer elements rely on importing dependencies using + * relative paths {@literal ../../other-element/other-element.html}, which will + * not work if they are installed in different locations. + * <p> + * HTML imports are added to the page after any {@code @JavaScript} dependencies + * added at the same time. + * <p> + * Example: + * <code>@HtmlImport("bower_components/paper-slider/paper-slider.html")</code> + * on the class com.example.MyConnector would load the file + * http://host.com/VAADIN/bower_components/paper-slider/paper-slider.html before + * the {@code init()} method of the client side connector is invoked. + * + * @author Vaadin Ltd + * @since 8.0.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +@Repeatable(InternalContainerAnnotationForHtml.class) +public @interface HtmlImport { + + /** + * HTML file URL(s) to load before using the annotated + * {@link ClientConnector} in the browser. + * + * @return html file URL(s) to load + */ + String[] value(); +} diff --git a/server/src/main/java/com/vaadin/annotations/InternalContainerAnnotationForHtml.java b/server/src/main/java/com/vaadin/annotations/InternalContainerAnnotationForHtml.java new file mode 100644 index 0000000000..be009bc4ce --- /dev/null +++ b/server/src/main/java/com/vaadin/annotations/InternalContainerAnnotationForHtml.java @@ -0,0 +1,45 @@ +/* + * Copyright 2000-2016 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation enabling using multiple {@link HtmlImport @HtmlImport} + * annotations. + * <p> + * <b>NOT meant to be used</b>, for multiple HTML dependencies, + * {@link HtmlImport @HtmlImport} should be used instead. + * + * @author Vaadin Ltd + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface InternalContainerAnnotationForHtml { + + /** + * Not to be used, instead multiple {@link HtmlImport @HtmlImport} + * annotations should be used. + * + * @return an array of the HtmlImport annotations + */ + HtmlImport[] value(); +} diff --git a/server/src/main/java/com/vaadin/annotations/JavaScript.java b/server/src/main/java/com/vaadin/annotations/JavaScript.java index 6ea8904c79..d6c35bdd4c 100644 --- a/server/src/main/java/com/vaadin/annotations/JavaScript.java +++ b/server/src/main/java/com/vaadin/annotations/JavaScript.java @@ -69,4 +69,5 @@ public @interface JavaScript { * @return an array of JavaScript file URLs */ public String[] value(); + } diff --git a/server/src/main/java/com/vaadin/server/BootstrapHandler.java b/server/src/main/java/com/vaadin/server/BootstrapHandler.java index be0e9e4593..d94101152e 100644 --- a/server/src/main/java/com/vaadin/server/BootstrapHandler.java +++ b/server/src/main/java/com/vaadin/server/BootstrapHandler.java @@ -23,12 +23,15 @@ import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.logging.Logger; import javax.servlet.http.HttpServletResponse; @@ -39,8 +42,6 @@ import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.jsoup.parser.Tag; -import com.vaadin.annotations.JavaScript; -import com.vaadin.annotations.StyleSheet; import com.vaadin.annotations.Viewport; import com.vaadin.annotations.ViewportGeneratorClass; import com.vaadin.server.communication.AtmospherePushConnection; @@ -48,6 +49,8 @@ import com.vaadin.shared.ApplicationConstants; import com.vaadin.shared.VaadinUriResolver; import com.vaadin.shared.Version; import com.vaadin.shared.communication.PushMode; +import com.vaadin.ui.Dependency; +import com.vaadin.ui.Dependency.Type; import com.vaadin.ui.UI; import elemental.json.Json; @@ -437,29 +440,25 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { .attr("href", themeUri + "/favicon.ico"); } - JavaScript[] javaScripts = uiClass - .getAnnotationsByType(JavaScript.class); - if (javaScripts != null) { - for (JavaScript javaScript : javaScripts) { - String[] resources = javaScript.value(); - for (String resource : resources) { - String url = registerDependency(context, uiClass, resource); - head.appendElement("script").attr("type", "text/javascript") - .attr("src", url); - } - } - } - - StyleSheet[] styleSheets = uiClass - .getAnnotationsByType(StyleSheet.class); - if (styleSheets != null) { - for (StyleSheet styleSheet : styleSheets) { - String[] resources = styleSheet.value(); - for (String resource : resources) { - String url = registerDependency(context, uiClass, resource); - head.appendElement("link").attr("rel", "stylesheet") - .attr("type", "text/css").attr("href", url); - } + Collection<? extends Dependency> deps = Dependency.findDependencies( + Collections.singletonList(uiClass), + context.getSession().getCommunicationManager()); + for (Dependency dependency : deps) { + Type type = dependency.getType(); + String url = context.getUriResolver() + .resolveVaadinUri(dependency.getUrl()); + if (type == Type.HTMLIMPORT) { + head.appendElement("link").attr("rel", "import").attr("href", + url); + } else if (type == Type.JAVASCRIPT) { + head.appendElement("script").attr("type", "text/javascript") + .attr("src", url); + } else if (type == Type.STYLESHEET) { + head.appendElement("link").attr("rel", "stylesheet") + .attr("type", "text/css").attr("href", url); + } else { + getLogger().severe("Ignoring unknown dependency type " + + dependency.getType()); } } @@ -468,14 +467,8 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler { body.addClass(ApplicationConstants.GENERATED_BODY_CLASSNAME); } - private String registerDependency(BootstrapContext context, - Class<? extends UI> uiClass, String resource) { - String url = context.getSession().getCommunicationManager() - .registerDependency(resource, uiClass); - - url = context.getUriResolver().resolveVaadinUri(url); - - return url; + private static Logger getLogger() { + return Logger.getLogger(BootstrapHandler.class.getName()); } protected String getMainDivStyle(BootstrapContext context) { diff --git a/server/src/main/java/com/vaadin/server/communication/UidlWriter.java b/server/src/main/java/com/vaadin/server/communication/UidlWriter.java index f8f6c2fc5b..4f94f4780b 100644 --- a/server/src/main/java/com/vaadin/server/communication/UidlWriter.java +++ b/server/src/main/java/com/vaadin/server/communication/UidlWriter.java @@ -29,8 +29,6 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import com.vaadin.annotations.JavaScript; -import com.vaadin.annotations.StyleSheet; import com.vaadin.server.ClientConnector; import com.vaadin.server.JsonPaintTarget; import com.vaadin.server.LegacyCommunicationManager; @@ -40,10 +38,12 @@ import com.vaadin.server.VaadinService; import com.vaadin.server.VaadinSession; import com.vaadin.shared.ApplicationConstants; import com.vaadin.ui.ConnectorTracker; +import com.vaadin.ui.Dependency; import com.vaadin.ui.UI; import elemental.json.Json; import elemental.json.JsonArray; +import elemental.json.JsonObject; import elemental.json.impl.JsonUtil; /** @@ -284,43 +284,13 @@ public class UidlWriter implements Serializable { } }); - List<String> scriptDependencies = new ArrayList<>(); - List<String> styleDependencies = new ArrayList<>(); - - for (Class<? extends ClientConnector> class1 : newConnectorTypes) { - JavaScript[] jsAnnotations = class1 - .getAnnotationsByType(JavaScript.class); - if (jsAnnotations != null) { - for (JavaScript jsAnnotation : jsAnnotations) { - for (String uri : jsAnnotation.value()) { - scriptDependencies.add( - manager.registerDependency(uri, class1)); - } - } - } - - StyleSheet[] styleAnnotations = class1 - .getAnnotationsByType(StyleSheet.class); - if (styleAnnotations != null) { - for (StyleSheet styleAnnotation : styleAnnotations) { - for (String uri : styleAnnotation.value()) { - styleDependencies.add( - manager.registerDependency(uri, class1)); - } - } - } - } - - // Include script dependencies in output if there are any - if (!scriptDependencies.isEmpty()) { - writer.write(", \"scriptDependencies\": " - + JsonUtil.stringify(toJsonArray(scriptDependencies))); - } + List<Dependency> dependencies = Dependency + .findDependencies(newConnectorTypes, manager); - // Include style dependencies in output if there are any - if (!styleDependencies.isEmpty()) { - writer.write(", \"styleDependencies\": " - + JsonUtil.stringify(toJsonArray(styleDependencies))); + // Include dependencies in output if there are any + if (!dependencies.isEmpty()) { + writer.write(", \"dependencies\": " + + JsonUtil.stringify(toJsonArray(dependencies))); } session.getDragAndDropService().printJSONResponse(writer); @@ -339,10 +309,14 @@ public class UidlWriter implements Serializable { } } - private JsonArray toJsonArray(List<String> list) { + private JsonArray toJsonArray(List<Dependency> list) { JsonArray result = Json.createArray(); for (int i = 0; i < list.size(); i++) { - result.set(i, list.get(i)); + JsonObject dep = Json.createObject(); + Dependency dependency = list.get(i); + dep.put("type", dependency.getType().name()); + dep.put("url", dependency.getUrl()); + result.set(i, dep); } return result; diff --git a/server/src/main/java/com/vaadin/ui/Dependency.java b/server/src/main/java/com/vaadin/ui/Dependency.java new file mode 100644 index 0000000000..8c82dc0275 --- /dev/null +++ b/server/src/main/java/com/vaadin/ui/Dependency.java @@ -0,0 +1,174 @@ +/* + * Copyright 2000-2016 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.ui; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; + +import com.vaadin.annotations.HtmlImport; +import com.vaadin.annotations.JavaScript; +import com.vaadin.annotations.StyleSheet; +import com.vaadin.server.ClientConnector; +import com.vaadin.server.LegacyCommunicationManager; + +/** + * Represents a stylesheet or JavaScript to include on the page. + * + * @author Vaadin Ltd + */ +public class Dependency implements Serializable { + /** + * The type of dependency. + */ + public enum Type { + STYLESHEET(StyleSheet.class), // + JAVASCRIPT(JavaScript.class), // + HTMLIMPORT(HtmlImport.class); + + private Class<? extends Annotation> annotationType; + + private Type(Class<? extends Annotation> annotationType) { + this.annotationType = annotationType; + } + } + + private final Type type; + private final String url; + + /** + * Creates a new dependency of the given type, to be loaded from the given + * URL. + * <p> + * The URL is passed through the translation mechanism before loading, so + * custom protocols such as "vaadin://" can be used. + * + * @param type + * the type of dependency, not <code>null</code> + * @param url + * the URL to load the dependency from, not <code>null</code> + */ + public Dependency(Type type, String url) { + if (url == null) { + throw new IllegalArgumentException("url cannot be null"); + } + assert type != null; + this.type = type; + this.url = url; + } + + /** + * Gets the untranslated URL for the dependency. + * + * @return the URL for the dependency + */ + public String getUrl() { + return url; + } + + /** + * Gets the type of the dependency. + * + * @return the type of the dependency + */ + public Type getType() { + return type; + } + + /** + * Finds all the URLs defined for the given class using annotations for the + * given type, registers the URLs to the communication manager and adds the + * registered dependencies to the given list. + * + * @param type + * the type of dependencies to look for + * @param cls + * the class to scan + * @param manager + * a reference to the communication manager which tracks + * dependencies + * @param dependencies + * the list to add registered dependencies to + * + * @return a stream of resource URLs in the order defined by the annotations + */ + @SuppressWarnings("deprecation") + private static void findAndRegisterResources(Type type, + Class<? extends ClientConnector> cls, + LegacyCommunicationManager manager, List<Dependency> dependencies) { + Annotation[] annotations = cls + .getAnnotationsByType(type.annotationType); + if (annotations != null) { + for (Annotation annotation : annotations) { + String[] resources; + if (annotation instanceof StyleSheet) { + resources = ((StyleSheet) annotation).value(); + } else if (annotation instanceof JavaScript) { + resources = ((JavaScript) annotation).value(); + } else if (annotation instanceof HtmlImport) { + resources = ((HtmlImport) annotation).value(); + } else { + throw new IllegalArgumentException( + "Unknown annotation type: " + + annotation.annotationType().getName()); + } + + for (String resource : resources) { + String url = manager.registerDependency(resource, cls); + dependencies.add(new Dependency(type, url)); + } + } + } + } + + /** + * Finds all the URLs defined for the given classes, registers the URLs to + * the communication manager and returns the registered dependencies. + * <p> + * The returned collection contains all types of dependencies for each class + * in the given list in the order the classes are in the list, i.e. all + * dependencies for the first class before all dependencies for the next + * class. + * <p> + * JavaScript dependencies are returned before HTML imports. + * + * @param connectorTypes + * the collection of connector classes to scan + * @param manager + * a reference to the communication manager which tracks + * dependencies + * @return + */ + @SuppressWarnings("deprecation") + public static List<Dependency> findDependencies( + List<Class<? extends ClientConnector>> connectorTypes, + LegacyCommunicationManager manager) { + List<Dependency> dependencies = new ArrayList<>(); + + for (Class<? extends ClientConnector> connectorType : connectorTypes) { + findAndRegisterResources(Type.JAVASCRIPT, connectorType, manager, + dependencies); + findAndRegisterResources(Type.HTMLIMPORT, connectorType, manager, + dependencies); + findAndRegisterResources(Type.STYLESHEET, connectorType, manager, + dependencies); + } + + return dependencies; + } + +} |