summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Ahlroos <john@vaadin.com>2013-03-06 11:23:48 +0200
committerVaadin Code Review <review@vaadin.com>2013-03-13 08:05:24 +0000
commit41798f820c60c43c89d8fb480821d8bda4c42bfb (patch)
treec31d1e1da2b39ff215c185ce2064611d4a62549f
parent28aa793d8ede78aef0b458d6362b1475288d6495 (diff)
downloadvaadin-framework-41798f820c60c43c89d8fb480821d8bda4c42bfb.tar.gz
vaadin-framework-41798f820c60c43c89d8fb480821d8bda4c42bfb.zip
Implemented injection of css styles #5500
Change-Id: Iaccffb4a3e137968d5f51672cdd56f803a9e9d2e
-rw-r--r--client/src/com/vaadin/client/ui/ui/UIConnector.java92
-rw-r--r--server/src/com/vaadin/server/Page.java128
-rw-r--r--uitest/src/com/vaadin/tests/themes/CSSInjectTest.html57
-rw-r--r--uitest/src/com/vaadin/tests/themes/CSSInjectTest.java87
4 files changed, 363 insertions, 1 deletions
diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java
index 0fb7439587..7f3c7010dd 100644
--- a/client/src/com/vaadin/client/ui/ui/UIConnector.java
+++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java
@@ -17,13 +17,19 @@ package com.vaadin.client.ui.ui;
import java.util.ArrayList;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.HeadElement;
+import com.google.gwt.dom.client.LinkElement;
import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.StyleInjector;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
@@ -56,6 +62,7 @@ import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.VUI;
import com.vaadin.client.ui.layout.MayScrollChildren;
import com.vaadin.client.ui.window.WindowConnector;
+import com.vaadin.server.Page.StyleSheet;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.ui.Connect;
@@ -256,6 +263,8 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
final UIDL notification = (UIDL) it.next();
VNotification.showNotification(client, notification);
}
+ } else if (tag == "css-injections") {
+ injectCSS(childUidl);
}
}
@@ -324,6 +333,89 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
getWidget().rendering = false;
}
+ /**
+ * Reads CSS strings and resources injected by {@link StyleSheet#inject}
+ * from the UIDL stream.
+ *
+ * @param uidl
+ * The uidl which contains "css-resource" and "css-string" tags
+ */
+ private void injectCSS(UIDL uidl) {
+
+ final HeadElement head = HeadElement.as(Document.get()
+ .getElementsByTagName(HeadElement.TAG).getItem(0));
+
+ /*
+ * Search the UIDL stream for CSS resources and strings to be injected.
+ */
+ final List<String> resourcesToInject = new LinkedList<String>();
+ final StringBuilder cssToInject = new StringBuilder();
+ for (Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
+ UIDL cssInjectionsUidl = (UIDL) it.next();
+
+ // Check if we have resources to inject
+ if (cssInjectionsUidl.getTag().equals("css-resource")) {
+ String url = getWidget().connection
+ .translateVaadinUri(cssInjectionsUidl
+ .getStringAttribute("url"));
+
+ // Check if url already has been injected
+ boolean injected = false;
+ NodeList<com.google.gwt.dom.client.Element> links = head
+ .getElementsByTagName(LinkElement.TAG);
+ for (int i = 0; i < links.getLength(); i++) {
+ LinkElement link = LinkElement.as(links.getItem(i));
+ if (link.getHref().equals(url)) {
+ injected = true;
+ break;
+ }
+ }
+
+ if (!injected) {
+ // Ensure duplicates do not get injected
+ resourcesToInject.add(url);
+ }
+
+ // Check if we have CSS string to inject
+ } else if (cssInjectionsUidl.getTag().equals("css-string")) {
+ for (Iterator<?> it2 = cssInjectionsUidl.getChildIterator(); it2
+ .hasNext();) {
+ cssToInject.append((String) it2.next());
+ }
+ }
+ }
+
+ /*
+ * Inject resources as deferred to ensure other Vaadin resources that
+ * are located before in the DOM get applied first so the injected ones
+ * can override them.
+ */
+ if (!resourcesToInject.isEmpty()) {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+
+ @Override
+ public void execute() {
+ for (String url : resourcesToInject) {
+ LinkElement link = LinkElement.as(DOM
+ .createElement(LinkElement.TAG));
+ link.setRel("stylesheet");
+ link.setHref(url);
+ link.setType("text/css");
+ head.appendChild(link);
+ }
+ }
+ });
+ }
+
+ /*
+ * Inject the string CSS injections as a combined style tag. Not
+ * injected as deferred since StyleInjector will do it for us.
+ */
+ if (cssToInject.length() > 0) {
+ StyleInjector.injectAtEnd(cssToInject.toString());
+ }
+ }
+
public void init(String rootPanelId,
ApplicationConnection applicationConnection) {
DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN
diff --git a/server/src/com/vaadin/server/Page.java b/server/src/com/vaadin/server/Page.java
index 8737b478c3..f7b65a8e9b 100644
--- a/server/src/com/vaadin/server/Page.java
+++ b/server/src/com/vaadin/server/Page.java
@@ -303,6 +303,115 @@ public class Page implements Serializable {
}
}
+ /**
+ * Contains dynamically injected styles injected in the HTML document at
+ * runtime.
+ *
+ * @since 7.1
+ */
+ public static class StyleSheet implements Serializable {
+
+ /*
+ * Points to last injected string injection
+ */
+ private int injectedStringPointer;
+
+ private final List<String> stringInjections = new LinkedList<String>();
+
+ /*
+ * Points to last injected resource injection
+ */
+ private int injectedResourcesPointer;
+
+ private final List<Resource> resourceInjections = new LinkedList<Resource>();
+
+ private final UI ui;
+
+ private StyleSheet(UI ui) {
+ this.ui = ui;
+ }
+
+ /**
+ * Injects a raw CSS string into the page.
+ *
+ * @param css
+ * The CSS to inject
+ */
+ public void inject(String css) {
+ if (css == null) {
+ throw new IllegalArgumentException(
+ "Cannot inject null CSS string");
+ }
+
+ /*
+ * Check if last injection was the same, in that case ignore it.
+ */
+ if (!stringInjections.isEmpty() && injectedStringPointer > 0) {
+ String lastInjection = stringInjections
+ .get(injectedStringPointer - 1);
+ if (lastInjection.equals(css.trim())) {
+ return;
+ }
+ }
+
+ stringInjections.add(css.trim());
+ ui.markAsDirty();
+ }
+
+ /**
+ * Injects a CSS resource into the page
+ *
+ * @param resource
+ * The resource to inject.
+ */
+ public void inject(Resource resource) {
+ if (resource == null) {
+ throw new IllegalArgumentException(
+ "Cannot inject null resource");
+ }
+
+ resourceInjections.add(resource);
+ ui.markAsDirty();
+ }
+
+ private void paint(PaintTarget target) throws PaintException {
+
+ // If full repaint repaint all injections
+ if (target.isFullRepaint()) {
+ injectedStringPointer = 0;
+ injectedResourcesPointer = 0;
+ }
+
+ target.startTag("css-injections");
+
+ // Paint pending string injections
+ List<String> injections = stringInjections.subList(
+ injectedStringPointer, stringInjections.size());
+
+ for (String css : injections) {
+ target.startTag("css-string");
+ target.addText(css);
+ target.endTag("css-string");
+ }
+
+ injectedStringPointer = stringInjections.size();
+
+ // Paint pending resource injections
+ List<Resource> resInjections = resourceInjections.subList(
+ injectedResourcesPointer, resourceInjections.size());
+
+ for (Resource res : resInjections) {
+ target.startTag("css-resource");
+ target.addAttribute("url", res);
+ target.endTag("css-resource");
+ }
+
+ target.endTag("css-injections");
+
+ injectedResourcesPointer = resourceInjections.size();
+ }
+ }
+
private EventRouter eventRouter;
private final UI uI;
@@ -312,6 +421,8 @@ public class Page implements Serializable {
private JavaScript javaScript;
+ private StyleSheet styleSheet;
+
/**
* The current browser location.
*/
@@ -576,10 +687,22 @@ public class Page implements Serializable {
javaScript = new JavaScript();
javaScript.extend(uI);
}
-
return javaScript;
}
+ /**
+ * Returns that stylesheet associated with this Page. The stylesheet
+ * contains additional styles injected at runtime into the HTML document.
+ *
+ * @since 7.1
+ */
+ public StyleSheet getStyleSheet() {
+ if (styleSheet == null) {
+ styleSheet = new StyleSheet(uI);
+ }
+ return styleSheet;
+ }
+
public void paintContent(PaintTarget target) throws PaintException {
if (!openList.isEmpty()) {
for (final Iterator<OpenResource> i = openList.iterator(); i
@@ -637,6 +760,9 @@ public class Page implements Serializable {
location.toString());
}
+ if (styleSheet != null) {
+ styleSheet.paint(target);
+ }
}
/**
diff --git a/uitest/src/com/vaadin/tests/themes/CSSInjectTest.html b/uitest/src/com/vaadin/tests/themes/CSSInjectTest.html
new file mode 100644
index 0000000000..05a0f256c2
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/themes/CSSInjectTest.html
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost:8888/" />
+<title>New Test</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">New Test</td></tr>
+</thead><tbody>
+<tr>
+ <td>open</td>
+ <td>/run/com.vaadin.tests.themes.CSSInjectTest?restartApplication</td>
+ <td></td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>hello-world-gray</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>vaadin=runcomvaadinteststhemesCSSInjectTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VTextArea[0]</td>
+ <td>.hello{color:blue;}</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadinteststhemesCSSInjectTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>hello-blue</td>
+</tr>
+<tr>
+ <td>type</td>
+ <td>vaadin=runcomvaadinteststhemesCSSInjectTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[1]/VTextArea[0]</td>
+ <td>.world{color:red;}</td>
+</tr>
+<tr>
+ <td>click</td>
+ <td>vaadin=runcomvaadinteststhemesCSSInjectTest::/VVerticalLayout[0]/Slot[1]/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]</td>
+ <td></td>
+</tr>
+<tr>
+ <td>screenCapture</td>
+ <td></td>
+ <td>world-red</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/uitest/src/com/vaadin/tests/themes/CSSInjectTest.java b/uitest/src/com/vaadin/tests/themes/CSSInjectTest.java
new file mode 100644
index 0000000000..bedbf47fe2
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/themes/CSSInjectTest.java
@@ -0,0 +1,87 @@
+package com.vaadin.tests.themes;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.UUID;
+
+import com.vaadin.server.Page;
+import com.vaadin.server.Page.StyleSheet;
+import com.vaadin.server.StreamResource;
+import com.vaadin.shared.ui.label.ContentMode;
+import com.vaadin.tests.components.TestBase;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.TextArea;
+
+public class CSSInjectTest extends TestBase {
+
+ @Override
+ protected void setup() {
+
+ final StyleSheet stylesheet = Page.getCurrent().getStyleSheet();
+
+ // Inject some resources initially
+ stylesheet.inject(new StreamResource(new StreamResource.StreamSource() {
+
+ @Override
+ public InputStream getStream() {
+ return new ByteArrayInputStream(
+ ".hello, .world { color:silver; }".getBytes());
+ }
+ }, "mystyles-" + System.currentTimeMillis() + ".css"));
+
+ Label hello = new Label(
+ "<span class='hello'>Hello</span> <span class='world'>world</span>",
+ ContentMode.HTML);
+ addComponent(hello);
+
+ final TextArea cssToInject = new TextArea();
+ cssToInject.setImmediate(true);
+ addComponent(cssToInject);
+
+ Button inject = new Button("Inject!", new Button.ClickListener() {
+
+ @Override
+ public void buttonClick(ClickEvent event) {
+ stylesheet.inject(cssToInject.getValue());
+ cssToInject.setValue("");
+ }
+ });
+ addComponent(inject);
+
+ Button injectRandom = new Button("Inject as resource!",
+ new Button.ClickListener() {
+
+ @Override
+ public void buttonClick(ClickEvent event) {
+
+ final String css = cssToInject.getValue();
+
+ stylesheet.inject(new StreamResource(
+ new StreamResource.StreamSource() {
+
+ @Override
+ public InputStream getStream() {
+ return new ByteArrayInputStream(css
+ .getBytes());
+ }
+ }, UUID.randomUUID().toString() + ".css"));
+
+ cssToInject.setValue("");
+ }
+ });
+ addComponent(injectRandom);
+ }
+
+ @Override
+ protected String getDescription() {
+ return "Demonstrates how CSS injections can be used to theme the \"Hello world\" label below";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 5500;
+ }
+
+}