Browse Source

Implemented injection of css styles #5500

Change-Id: Iaccffb4a3e137968d5f51672cdd56f803a9e9d2e
tags/7.1.0.beta1
John Ahlroos 11 years ago
parent
commit
41798f820c

+ 92
- 0
client/src/com/vaadin/client/ui/ui/UIConnector.java View File

@@ -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

+ 127
- 1
server/src/com/vaadin/server/Page.java View File

@@ -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);
}
}

/**

+ 57
- 0
uitest/src/com/vaadin/tests/themes/CSSInjectTest.html View File

@@ -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>

+ 87
- 0
uitest/src/com/vaadin/tests/themes/CSSInjectTest.java View File

@@ -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;
}

}

Loading…
Cancel
Save