diff options
author | John Ahlroos <john@vaadin.com> | 2013-03-06 11:23:48 +0200 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2013-03-13 08:05:24 +0000 |
commit | 41798f820c60c43c89d8fb480821d8bda4c42bfb (patch) | |
tree | c31d1e1da2b39ff215c185ce2064611d4a62549f | |
parent | 28aa793d8ede78aef0b458d6362b1475288d6495 (diff) | |
download | vaadin-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.java | 92 | ||||
-rw-r--r-- | server/src/com/vaadin/server/Page.java | 128 | ||||
-rw-r--r-- | uitest/src/com/vaadin/tests/themes/CSSInjectTest.html | 57 | ||||
-rw-r--r-- | uitest/src/com/vaadin/tests/themes/CSSInjectTest.java | 87 |
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; + } + +} |