Change-Id: Iaccffb4a3e137968d5f51672cdd56f803a9e9d2etags/7.1.0.beta1
@@ -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 |
@@ -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); | |||
} | |||
} | |||
/** |
@@ -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> |
@@ -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; | |||
} | |||
} |