Browse Source

Add an API for modifying the bootstrap page. (#9274)

tags/7.0.0.beta1
Leif Åstrand 12 years ago
parent
commit
2b0cb55ef2

+ 2
- 1
src/com/vaadin/terminal/gwt/server/AbstractCommunicationManager.java View File

@@ -2394,7 +2394,8 @@ public abstract class AbstractCommunicationManager implements Serializable {
// Use the same logic as for determined roots
BootstrapHandler bootstrapHandler = getBootstrapHandler();
BootstrapContext context = bootstrapHandler.createContext(
combinedRequest, response, application, root.getRootId());
combinedRequest, response, application, root.getRootId(),
null);

String widgetset = context.getWidgetsetName();
String theme = context.getThemeName();

+ 9
- 0
src/com/vaadin/terminal/gwt/server/BootstrapDom.java View File

@@ -0,0 +1,9 @@
/*
@VaadinApache2LicenseForJavaFiles@
*/

package com.vaadin.terminal.gwt.server;

public class BootstrapDom {

}

+ 147
- 110
src/com/vaadin/terminal/gwt/server/BootstrapHandler.java View File

@@ -8,11 +8,19 @@ import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.Writer;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import org.jsoup.nodes.DataNode;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.DocumentType;
import org.jsoup.nodes.Element;
import org.jsoup.parser.Tag;

import com.vaadin.Application;
import com.vaadin.RootRequiresMoreInformationException;
import com.vaadin.Version;
@@ -31,24 +39,16 @@ public abstract class BootstrapHandler implements RequestHandler {
protected class BootstrapContext implements Serializable {

private final WrappedResponse response;
private final WrappedRequest request;
private final Application application;
private final Integer rootId;
private final BootstrapResponse bootstrapResponse;

private Writer writer;
private Root root;
private String widgetsetName;
private String themeName;
private String appId;

private boolean rootFetched = false;

public BootstrapContext(WrappedResponse response,
WrappedRequest request, Application application, Integer rootId) {
BootstrapResponse bootstrapResponse) {
this.response = response;
this.request = request;
this.application = application;
this.rootId = rootId;
this.bootstrapResponse = bootstrapResponse;
}

public WrappedResponse getResponse() {
@@ -56,32 +56,19 @@ public abstract class BootstrapHandler implements RequestHandler {
}

public WrappedRequest getRequest() {
return request;
return bootstrapResponse.getRequest();
}

public Application getApplication() {
return application;
}

public Writer getWriter() throws IOException {
if (writer == null) {
response.setContentType("text/html");
writer = new BufferedWriter(new OutputStreamWriter(
response.getOutputStream(), "UTF-8"));
}
return writer;
return bootstrapResponse.getApplication();
}

public Integer getRootId() {
return rootId;
return bootstrapResponse.getRootId();
}

public Root getRoot() {
if (!rootFetched) {
root = Root.getCurrent();
rootFetched = true;
}
return root;
return bootstrapResponse.getRoot();
}

public String getWidgetsetName() {
@@ -111,6 +98,10 @@ public abstract class BootstrapHandler implements RequestHandler {
return appId;
}

public BootstrapResponse getBootstrapResponse() {
return bootstrapResponse;
}

}

@Override
@@ -145,8 +136,9 @@ public abstract class BootstrapHandler implements RequestHandler {
WrappedResponse response, Application application, Integer rootId)
throws IOException, JSONException {

Map<String, Object> headers = new LinkedHashMap<String, Object>();
BootstrapContext context = createContext(request, response,
application, rootId);
application, rootId, headers);

DeploymentConfiguration deploymentConfiguration = request
.getDeploymentConfiguration();
@@ -154,28 +146,61 @@ public abstract class BootstrapHandler implements RequestHandler {
boolean standalone = deploymentConfiguration.isStandalone(request);
if (standalone) {
setBootstrapPageHeaders(context);
writeBootstrapPageHtmlHeadStart(context);
writeBootstrapPageHtmlHeader(context);
writeBootstrapPageHtmlBodyStart(context);
setBasicHtml(context);
setBootstrapPageHtmlHeader(context);
setBodyTag(context);
}

// TODO include initial UIDL in the scripts?
writeBootstrapPageHtmlVaadinScripts(context);
setBootstrapPageHtmlVaadinScripts(context);

setMainDiv(context);

writeBootstrapPageHtmlMainDiv(context);
request.getDeploymentConfiguration().getVaadinContext()
.fireModifyBootstrapEvent(context.getBootstrapResponse());

Writer page = context.getWriter();
response.setContentType("text/html");
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
response.getOutputStream(), "UTF-8"));
if (standalone) {
page.write("</body>\n</html>\n");
Set<Entry<String, Object>> entrySet = headers.entrySet();
for (Entry<String, Object> header : entrySet) {
Object value = header.getValue();
if (value instanceof String) {
response.setHeader(header.getKey(), (String) value);
} else if (value instanceof Long) {
response.setDateHeader(header.getKey(),
((Long) value).longValue());
} else {
throw new RuntimeException("Unsupported header value: "
+ value);
}
}
writer.append(context.getBootstrapResponse().getDocument()
.outerHtml());
} else {
writer.append(context.getBootstrapResponse().getApplicationTag()
.outerHtml());
}

page.close();
writer.close();
}

public BootstrapContext createContext(WrappedRequest request,
WrappedResponse response, Application application, Integer rootId) {
BootstrapContext context = new BootstrapContext(response, request,
application, rootId);
WrappedResponse response, Application application, Integer rootId,
Map<String, Object> headers) {
boolean standalone = request.getDeploymentConfiguration().isStandalone(
request);
Document document;
Element applicationTag;
if (standalone) {
document = Document.createShell("");
applicationTag = document.body();
} else {
document = null;
applicationTag = new Element(Tag.valueOf("div"), "");
}
BootstrapContext context = new BootstrapContext(response,
new BootstrapResponse(this, request, document, applicationTag,
headers, application, rootId));
return context;
}

@@ -219,9 +244,7 @@ public abstract class BootstrapHandler implements RequestHandler {
*
* @throws IOException
*/
protected void writeBootstrapPageHtmlMainDiv(BootstrapContext context)
throws IOException {
Writer page = context.getWriter();
protected void setMainDiv(BootstrapContext context) throws IOException {
String style = getMainDivStyle(context);

/*- Add classnames;
@@ -237,14 +260,16 @@ public abstract class BootstrapHandler implements RequestHandler {

String classNames = "v-app " + appClass;

Element applicationTag = context.getBootstrapResponse()
.getApplicationTag();
Element mainDiv = applicationTag.appendElement("div");
mainDiv.attr("id", context.getAppId());
mainDiv.addClass(classNames);
if (style != null && style.length() != 0) {
style = " style=\"" + style + "\"";
mainDiv.attr("style", style);
}
page.write("<div id=\"" + context.getAppId() + "\" class=\""
+ classNames + "\"" + style + ">");
page.write("<div class=\"v-app-loading\"></div>");
page.write("</div>\n");
page.write("<noscript>" + getNoScriptMessage() + "</noscript>");
mainDiv.appendElement("div").addClass("v-app-loading");
mainDiv.appendElement("noscript").append(getNoScriptMessage());
}

/**
@@ -279,11 +304,10 @@ public abstract class BootstrapHandler implements RequestHandler {
*
* @throws IOException
*/
protected void writeBootstrapPageHtmlBodyStart(BootstrapContext context)
throws IOException {
Writer page = context.getWriter();
page.write("\n</head>\n<body scroll=\"auto\" class=\""
+ ApplicationConnection.GENERATED_BODY_CLASSNAME + "\">\n");
protected void setBodyTag(BootstrapContext context) throws IOException {
Element body = context.getBootstrapResponse().getDocument().body();
body.attr("scroll", "auto");
body.addClass(ApplicationConnection.GENERATED_BODY_CLASSNAME);
}

/**
@@ -297,61 +321,70 @@ public abstract class BootstrapHandler implements RequestHandler {
* @throws IOException
* @throws JSONException
*/
protected void writeBootstrapPageHtmlVaadinScripts(BootstrapContext context)
protected void setBootstrapPageHtmlVaadinScripts(BootstrapContext context)
throws IOException, JSONException {
WrappedRequest request = context.getRequest();
Writer page = context.getWriter();

DeploymentConfiguration deploymentConfiguration = request
.getDeploymentConfiguration();
String staticFileLocation = deploymentConfiguration
.getStaticFileLocation(request);

page.write("<iframe tabIndex=\"-1\" id=\"__gwt_historyFrame\" "
+ "style=\"position:absolute;width:0;height:0;border:0;overflow:"
+ "hidden;\" src=\"javascript:false\"></iframe>");
Element applicationTag = context.getBootstrapResponse()
.getApplicationTag();

applicationTag
.appendElement("iframe")
.attr("tabIndex", "-1")
.attr("id", "__gwt_historyFrame")
.attr("style",
"position:absolute;width:0;height:0;border:0;overflow:hidden")
.attr("src", "javascript:false");

String bootstrapLocation = staticFileLocation
+ "/VAADIN/vaadinBootstrap.js";
page.write("<script type=\"text/javascript\" src=\"");
page.write(bootstrapLocation);
page.write("\"></script>\n");

page.write("<script type=\"text/javascript\">\n");
page.write("//<![CDATA[\n");
page.write("if (!window.vaadin) alert("
applicationTag.appendElement("script").attr("type", "text/javascript")
.attr("src", bootstrapLocation);
Element mainScriptTag = applicationTag.appendElement("script").attr(
"type", "text/javascript");

StringBuilder builder = new StringBuilder();
builder.append("//<![CDATA[\n");
builder.append("if (!window.vaadin) alert("
+ JSONObject.quote("Failed to load the bootstrap javascript: "
+ bootstrapLocation) + ");\n");

writeMainScriptTagContents(context);
page.write("//]]>\n</script>\n");
appendMainScriptTagContents(context, builder);

builder.append("//]]>");
mainScriptTag.appendChild(new DataNode(builder.toString(),
mainScriptTag.baseUri()));
}

protected void writeMainScriptTagContents(BootstrapContext context)
throws JSONException, IOException {
protected void appendMainScriptTagContents(BootstrapContext context,
StringBuilder builder) throws JSONException, IOException {
JSONObject defaults = getDefaultParameters(context);
JSONObject appConfig = getApplicationParameters(context);

boolean isDebug = !context.getApplication().isProductionMode();
Writer page = context.getWriter();

page.write("vaadin.setDefaults(");
printJsonObject(page, defaults, isDebug);
page.write(");\n");
builder.append("vaadin.setDefaults(");
appendJsonObject(builder, defaults, isDebug);
builder.append(");\n");

page.write("vaadin.initApplication(\"");
page.write(context.getAppId());
page.write("\",");
printJsonObject(page, appConfig, isDebug);
page.write(");\n");
builder.append("vaadin.initApplication(\"");
builder.append(context.getAppId());
builder.append("\",");
appendJsonObject(builder, appConfig, isDebug);
builder.append(");\n");
}

private static void printJsonObject(Writer page, JSONObject jsonObject,
boolean isDebug) throws IOException, JSONException {
private static void appendJsonObject(StringBuilder builder,
JSONObject jsonObject, boolean isDebug) throws JSONException {
if (isDebug) {
page.write(jsonObject.toString(4));
builder.append(jsonObject.toString(4));
} else {
page.write(jsonObject.toString());
builder.append(jsonObject.toString());
}
}

@@ -456,36 +489,37 @@ public abstract class BootstrapHandler implements RequestHandler {
*
* @throws IOException
*/
protected void writeBootstrapPageHtmlHeader(BootstrapContext context)
protected void setBootstrapPageHtmlHeader(BootstrapContext context)
throws IOException {
Writer page = context.getWriter();
String themeName = context.getThemeName();

page.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n");
Element head = context.getBootstrapResponse().getDocument().head();
head.appendElement("meta").attr("http-equiv", "Content-Type")
.attr("content", "text/html; charset=utf-8");

// Chrome frame in all versions of IE (only if Chrome frame is
// installed)
page.write("<meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\"/>\n");
head.appendElement("meta").attr("http-equiv", "X-UA-Compatible")
.attr("content", "chrome=1");

page.write("<style type=\"text/css\">"
+ "html, body {height:100%;margin:0;}</style>\n");
head.appendElement("style").attr("type", "text/css")
.appendText("html, body {height:100%;margin:0;}");

// Add favicon links
if (themeName != null) {
String themeUri = getThemeUri(context, themeName);
page.write("<link rel=\"shortcut icon\" type=\"image/vnd.microsoft.icon\" href=\""
+ themeUri + "/favicon.ico\" />\n");
page.write("<link rel=\"icon\" type=\"image/vnd.microsoft.icon\" href=\""
+ themeUri + "/favicon.ico\" />\n");
head.appendElement("link").attr("rel", "shortcut icon")
.attr("type", "image/vnd.microsoft.icon")
.attr("href", themeUri + "/favicon.ico");
head.appendElement("link").attr("rel", "icon")
.attr("type", "image/vnd.microsoft.icon")
.attr("href", themeUri + "/favicon.ico");
}

Root root = context.getRoot();
String title = ((root == null || root.getCaption() == null) ? "" : root
.getCaption());

page.write("<title>"
+ AbstractApplicationServlet.safeEscapeForHtml(title)
+ "</title>\n");
head.appendElement("title").appendText(title);
}

/**
@@ -517,18 +551,21 @@ public abstract class BootstrapHandler implements RequestHandler {
* @param context
* @throws IOException
*/
protected void writeBootstrapPageHtmlHeadStart(BootstrapContext context)
throws IOException {
Writer page = context.getWriter();
protected void setBasicHtml(BootstrapContext context) throws IOException {

// write html header
page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD "
+ "XHTML 1.0 Transitional//EN\" "
+ "\"http://www.w3.org/TR/xhtml1/"
+ "DTD/xhtml1-transitional.dtd\">\n");

page.write("<html xmlns=\"http://www.w3.org/1999/xhtml\""
+ ">\n<head>\n");
// page.write("<!DOCTYPE html PUBLIC \"-//W3C//DTD "
// + "XHTML 1.0 Transitional//EN\" "
// + "\"http://www.w3.org/TR/xhtml1/"
// + "DTD/xhtml1-transitional.dtd\">\n");
Document document = context.getBootstrapResponse().getDocument();
DocumentType doctype = new DocumentType("html",
"//W3C//DTD XHTML 1.0 Transitional//EN",
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd",
document.baseUri());
document.child(0).before(doctype);

document.body().parent().attr("xmlns", "http://www.w3.org/1999/xhtml");
}

/**

+ 11
- 0
src/com/vaadin/terminal/gwt/server/BootstrapListener.java View File

@@ -0,0 +1,11 @@
/*
@VaadinApache2LicenseForJavaFiles@
*/

package com.vaadin.terminal.gwt.server;

import java.util.EventListener;

public interface BootstrapListener extends EventListener {
public void modifyBootstrap(BootstrapResponse response);
}

+ 72
- 0
src/com/vaadin/terminal/gwt/server/BootstrapResponse.java View File

@@ -0,0 +1,72 @@
/*
@VaadinApache2LicenseForJavaFiles@
*/

package com.vaadin.terminal.gwt.server;

import java.util.EventObject;
import java.util.Map;

import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import com.vaadin.Application;
import com.vaadin.terminal.WrappedRequest;
import com.vaadin.ui.Root;

public class BootstrapResponse extends EventObject {
private final Document document;
private final WrappedRequest request;
private final Map<String, Object> headers;
private final Element applicationTag;
private final Application application;
private final Integer rootId;

public BootstrapResponse(BootstrapHandler handler, WrappedRequest request,
Document document, Element applicationTag,
Map<String, Object> headers, Application application, Integer rootId) {
super(handler);
this.request = request;
this.document = document;
this.applicationTag = applicationTag;
this.headers = headers;
this.application = application;
this.rootId = rootId;
}

public void setHeader(String name, String value) {
headers.put(name, value);
}

public void setDateHeader(String name, long timestamp) {
headers.put(name, Long.valueOf(timestamp));
}

public BootstrapHandler getBootstrapHandler() {
return (BootstrapHandler) getSource();
}

public WrappedRequest getRequest() {
return request;
}

public Document getDocument() {
return document;
}

public Element getApplicationTag() {
return applicationTag;
}

public Application getApplication() {
return application;
}

public Integer getRootId() {
return rootId;
}

public Root getRoot() {
return Root.getCurrent();
}
}

+ 5
- 4
src/com/vaadin/terminal/gwt/server/PortletCommunicationManager.java View File

@@ -100,7 +100,8 @@ public class PortletCommunicationManager extends AbstractCommunicationManager {
}

@Override
protected void writeMainScriptTagContents(BootstrapContext context)
protected void appendMainScriptTagContents(
BootstrapContext context, StringBuilder builder)
throws JSONException, IOException {
// fixed base theme to use - all portal pages with Vaadin
// applications will load this exactly once
@@ -112,11 +113,11 @@ public class PortletCommunicationManager extends AbstractCommunicationManager {
&& !portalTheme.equals(context.getThemeName())) {
String portalThemeUri = getThemeUri(context, portalTheme);
// XSS safe - originates from portal properties
context.getWriter().write(
"vaadin.loadTheme('" + portalThemeUri + "');");
builder.append("vaadin.loadTheme('" + portalThemeUri
+ "');");
}

super.writeMainScriptTagContents(context);
super.appendMainScriptTagContents(context, builder);
}

@Override

+ 15
- 0
src/com/vaadin/terminal/gwt/server/VaadinContext.java View File

@@ -4,12 +4,18 @@

package com.vaadin.terminal.gwt.server;

import java.lang.reflect.Method;
import java.util.Iterator;

import com.vaadin.event.EventRouter;
import com.vaadin.terminal.DeploymentConfiguration;
import com.vaadin.tools.ReflectTools;

public class VaadinContext {
private static final Method BOOTSTRAP_GENERATE_METHOD = ReflectTools
.findMethod(BootstrapListener.class, "modifyBootstrap",
BootstrapResponse.class);

private final DeploymentConfiguration deploymentConfiguration;

private final EventRouter eventRouter = new EventRouter();
@@ -43,4 +49,13 @@ public class VaadinContext {
}
}

public void addBootstrapListener(BootstrapListener listener) {
eventRouter.addListener(BootstrapResponse.class, listener,
BOOTSTRAP_GENERATE_METHOD);
}

public void fireModifyBootstrapEvent(BootstrapResponse bootstrapResponse) {
eventRouter.fireEvent(bootstrapResponse);
}

}

+ 1
- 0
tests/testbench/META-INF/services/com.vaadin.terminal.gwt.server.VaadinContextListener View File

@@ -0,0 +1 @@
com.vaadin.tests.vaadincontext.TestVaadinContextListener

+ 31
- 0
tests/testbench/com/vaadin/tests/vaadincontext/BoostrapModify.html View File

@@ -0,0 +1,31 @@
<?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="" />
<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.vaadincontext.BoostrapModifyRoot?restartApplication</td>
<td></td>
</tr>
<tr>
<td>assertText</td>
<td>vaadin=runcomvaadintestsvaadincontextBoostrapModifyRoot::/VVerticalLayout[0]/VLabel[0]</td>
<td>There should be a static h1 in the HTML of the bootstrap page for this Root</td>
</tr>
<tr>
<td>assertText</td>
<td>//h1</td>
<td>This is a heading</td>
</tr>
</tbody></table>
</body>
</html>

+ 28
- 0
tests/testbench/com/vaadin/tests/vaadincontext/BoostrapModifyRoot.java View File

@@ -0,0 +1,28 @@
/*
@VaadinApache2LicenseForJavaFiles@
*/

package com.vaadin.tests.vaadincontext;

import com.vaadin.terminal.WrappedRequest;
import com.vaadin.tests.components.AbstractTestRoot;

public class BoostrapModifyRoot extends AbstractTestRoot {

@Override
protected void setup(WrappedRequest request) {
// TODO Auto-generated method stub

}

@Override
protected String getTestDescription() {
return "There should be a static h1 in the HTML of the bootstrap page for this Root";
}

@Override
protected Integer getTicketNumber() {
return Integer.valueOf(9274);
}

}

+ 33
- 0
tests/testbench/com/vaadin/tests/vaadincontext/TestVaadinContextListener.java View File

@@ -0,0 +1,33 @@
/*
@VaadinApache2LicenseForJavaFiles@
*/

package com.vaadin.tests.vaadincontext;

import com.vaadin.terminal.gwt.server.BootstrapListener;
import com.vaadin.terminal.gwt.server.BootstrapResponse;
import com.vaadin.terminal.gwt.server.VaadinContextEvent;
import com.vaadin.terminal.gwt.server.VaadinContextListener;
import com.vaadin.ui.Root;

public class TestVaadinContextListener implements VaadinContextListener {
@Override
public void contextCreated(VaadinContextEvent event) {
event.getVaadinContext().addBootstrapListener(new BootstrapListener() {
@Override
public void modifyBootstrap(BootstrapResponse response) {
Root root = response.getRoot();
if (root != null && root.getClass() == BoostrapModifyRoot.class) {
response.getApplicationTag().before(
"<h1>This is a heading</h1>");
}
}
});
}

@Override
public void contextDestoryed(VaadinContextEvent event) {
// Nothing to do
}

}

Loading…
Cancel
Save