From 24e3409e6ff117ff954f36a9c9378690c76634f8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Leif=20=C3=85strand?= Date: Sun, 10 Jun 2012 15:10:10 +0300 Subject: [PATCH] Initial support for javascript components (#8888) --- WebContent/statictestfiles/jsconnector.js | 12 ++ WebContent/statictestfiles/jsextension.js | 13 ++ src/com/vaadin/annotations/LoadScripts.java | 16 ++ .../terminal/AbstractJavascriptExtension.java | 9 + .../gwt/client/ApplicationConnection.java | 43 ++--- .../gwt/client/JavascriptConnectorHelper.java | 179 ++++++++++++++++++ .../gwt/client/JavascriptExtension.java | 33 ++++ .../vaadin/terminal/gwt/client/WidgetSet.java | 8 +- .../HasJavascriptConnectorHelper.java | 11 ++ .../gwt/client/communication/JsonEncoder.java | 2 + .../gwt/client/communication/RpcManager.java | 59 ++++-- .../ui/JavascriptComponentConnector.java | 60 ++++++ .../gwt/client/ui/JavascriptWidget.java | 25 +++ .../terminal/gwt/server/BootstrapHandler.java | 57 +++++- .../ui/AbstractJavascriptComponent.java | 8 + .../BasicJavascriptComponent.java | 73 +++++++ .../SimpleJavascriptExtensionTest.java | 95 ++++++++++ 17 files changed, 658 insertions(+), 45 deletions(-) create mode 100644 WebContent/statictestfiles/jsconnector.js create mode 100644 WebContent/statictestfiles/jsextension.js create mode 100644 src/com/vaadin/annotations/LoadScripts.java create mode 100644 src/com/vaadin/terminal/AbstractJavascriptExtension.java create mode 100644 src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java create mode 100644 src/com/vaadin/terminal/gwt/client/JavascriptExtension.java create mode 100644 src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java create mode 100644 src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java create mode 100644 src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java create mode 100644 src/com/vaadin/ui/AbstractJavascriptComponent.java create mode 100644 tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java create mode 100644 tests/testbench/com/vaadin/tests/features/SimpleJavascriptExtensionTest.java diff --git a/WebContent/statictestfiles/jsconnector.js b/WebContent/statictestfiles/jsconnector.js new file mode 100644 index 0000000000..db8a065e86 --- /dev/null +++ b/WebContent/statictestfiles/jsconnector.js @@ -0,0 +1,12 @@ +window.com_vaadin_tests_components_javascriptcomponent_BasicJavascriptComponent_ExampleWidget = function() { + var connector = this; + + var rootElement = connector.getWidgetElement(); + rootElement.innerHTML = 'Hello world!'; + rootElement.onclick = function() { + connector.getRpcProxyFunction("com.vaadin.tests.components.javascriptcomponent.BasicJavascriptComponent$ExampleClickRpc", "onClick")("message"); + } + connector.onStateChange = function() { + console.log('state change:', this.getState()); + } +} \ No newline at end of file diff --git a/WebContent/statictestfiles/jsextension.js b/WebContent/statictestfiles/jsextension.js new file mode 100644 index 0000000000..df67db8927 --- /dev/null +++ b/WebContent/statictestfiles/jsextension.js @@ -0,0 +1,13 @@ +window.com_vaadin_tests_features_SimpleJavascriptExtensionTest_SimpleJavascriptExtension = function() { + var state = this.getState(); + var greetBack = this.getRpcProxyFunction('com.vaadin.tests.features.SimpleJavascriptExtensionTest$SimpleJavascriptExtensionServerRpc', 'greet'); + + this.registerRpc("com.vaadin.tests.features.SimpleJavascriptExtensionTest.SimpleJavascriptExtensionClientRpc", { + 'greet': function(greeting) { + var response = window.prompt(state.prefix + greeting); + if (response !== null) { + greetBack(response); + } + } + }); +} \ No newline at end of file diff --git a/src/com/vaadin/annotations/LoadScripts.java b/src/com/vaadin/annotations/LoadScripts.java new file mode 100644 index 0000000000..f2b72407f7 --- /dev/null +++ b/src/com/vaadin/annotations/LoadScripts.java @@ -0,0 +1,16 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface LoadScripts { + public String[] value(); + +} diff --git a/src/com/vaadin/terminal/AbstractJavascriptExtension.java b/src/com/vaadin/terminal/AbstractJavascriptExtension.java new file mode 100644 index 0000000000..c86f69c1be --- /dev/null +++ b/src/com/vaadin/terminal/AbstractJavascriptExtension.java @@ -0,0 +1,9 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +public class AbstractJavascriptExtension extends AbstractExtension { + +} diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 0a954f530b..93a51e6e96 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -40,6 +40,7 @@ import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConfiguration.ErrorMessage; +import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; import com.vaadin.terminal.gwt.client.communication.JsonDecoder; import com.vaadin.terminal.gwt.client.communication.JsonEncoder; import com.vaadin.terminal.gwt.client.communication.MethodInvocation; @@ -1411,12 +1412,19 @@ public class ApplicationConnection { .getConnector(connectorId); if (null != connector) { - JSONObject stateDataAndType = new JSONObject( + JSONObject stateJson = new JSONObject( states.getJavaScriptObject(connectorId)); + if (connector instanceof HasJavascriptConnectorHelper) { + ((HasJavascriptConnectorHelper) connector) + .getJavascriptConnectorHelper() + .setNativeState( + stateJson.getJavaScriptObject()); + } + SharedState state = connector.getState(); JsonDecoder.decodeValue(new Type(state.getClass() - .getName(), null), stateDataAndType, state, + .getName(), null), stateJson, state, ApplicationConnection.this); StateChangeEvent event = GWT @@ -1557,11 +1565,8 @@ public class ApplicationConnection { for (int i = 0; i < rpcLength; i++) { try { JSONArray rpcCall = (JSONArray) rpcCalls.get(i); - MethodInvocation invocation = parseMethodInvocation(rpcCall); - VConsole.log("Server to client RPC call: " - + invocation); - rpcManager.applyInvocation(invocation, - getConnectorMap()); + rpcManager.parseAndApplyInvocation(rpcCall, + ApplicationConnection.this); } catch (final Throwable e) { VConsole.error(e); } @@ -1574,26 +1579,6 @@ public class ApplicationConnection { ApplicationConfiguration.runWhenWidgetsLoaded(c); } - private MethodInvocation parseMethodInvocation(JSONArray rpcCall) { - String connectorId = ((JSONString) rpcCall.get(0)).stringValue(); - String interfaceName = ((JSONString) rpcCall.get(1)).stringValue(); - String methodName = ((JSONString) rpcCall.get(2)).stringValue(); - JSONArray parametersJson = (JSONArray) rpcCall.get(3); - - MethodInvocation methodInvocation = new MethodInvocation(connectorId, - interfaceName, methodName); - Type[] parameterTypes = rpcManager.getParameterTypes(methodInvocation); - - Object[] parameters = new Object[parametersJson.size()]; - for (int j = 0; j < parametersJson.size(); ++j) { - parameters[j] = JsonDecoder.decodeValue(parameterTypes[j], - parametersJson.get(j), null, this); - } - - methodInvocation.setParameters(parameters); - return methodInvocation; - } - // Redirect browser, null reloads current page private static native void redirect(String url) /*-{ @@ -2142,8 +2127,8 @@ public class ApplicationConnection { private ServerConnector createAndRegisterConnector(String connectorId, int connectorType) { // Create and register a new connector with the given type - ServerConnector p = widgetSet - .createConnector(connectorType, configuration); + ServerConnector p = widgetSet.createConnector(connectorType, + configuration); connectorMap.registerConnector(connectorId, p); p.doInit(connectorId, this); diff --git a/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java new file mode 100644 index 0000000000..633a4bc2c6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java @@ -0,0 +1,179 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.ArrayList; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.json.client.JSONArray; +import com.vaadin.terminal.gwt.client.communication.MethodInvocation; + +public class JavascriptConnectorHelper { + + private final ServerConnector connector; + private final JavaScriptObject nativeState = JavaScriptObject + .createObject(); + private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); + + private JavaScriptObject connectorWrapper; + private int tag; + + public JavascriptConnectorHelper(ServerConnector connector) { + this.connector = connector; + } + + public boolean init() { + ApplicationConfiguration conf = connector.getConnection() + .getConfiguration(); + ArrayList attemptedNames = new ArrayList(); + Integer tag = Integer.valueOf(this.tag); + while (tag != null) { + String serverSideClassName = conf.getServerSideClassNameForTag(tag); + String initFunctionName = serverSideClassName + .replaceAll("\\.", "_"); + if (tryInitJs(initFunctionName, getConnectorWrapper())) { + VConsole.log("Javascript connector initialized using " + + initFunctionName); + return true; + } else { + VConsole.log("No javascript function " + initFunctionName + + " found"); + attemptedNames.add(initFunctionName); + tag = conf.getParentTag(tag.intValue()); + } + } + VConsole.log("No javascript init for connector not found"); + showInitProblem(attemptedNames); + return false; + } + + protected void showInitProblem(ArrayList attemptedNames) { + // Default does nothing + } + + private static native boolean tryInitJs(String initFunctionName, + JavaScriptObject connectorWrapper) + /*-{ + if (typeof $wnd[initFunctionName] == 'function') { + $wnd[initFunctionName].apply(connectorWrapper); + return true; + } else { + return false; + } + }-*/; + + private JavaScriptObject getConnectorWrapper() { + if (connectorWrapper == null) { + connectorWrapper = createConnectorWrapper(); + } + + return connectorWrapper; + } + + protected JavaScriptObject createConnectorWrapper() { + return createConnectorWrapper(this, nativeState, rpcMap, + connector.getConnectorId()); + } + + public void fireNativeStateChange() { + fireNativeStateChange(getConnectorWrapper()); + } + + private static native void fireNativeStateChange( + JavaScriptObject connectorWrapper) + /*-{ + if (typeof connectorWrapper.onStateChange == 'function') { + connectorWrapper.onStateChange(); + } + }-*/; + + private static native JavaScriptObject createConnectorWrapper( + JavascriptConnectorHelper h, JavaScriptObject nativeState, + JavaScriptObject registeredRpc, String connectorId) + /*-{ + return { + 'getConnectorId': function() { + return connectorId; + }, + 'getState': function() { + return nativeState; + }, + 'getRpcProxyFunction': function(iface, method) { + return $entry(function() { + h.@com.vaadin.terminal.gwt.client.JavascriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); + }); + }, + 'registerRpc': function(iface, rpcHandler) { + if (!registeredRpc[iface]) { + registeredRpc[iface] = []; + } + registeredRpc[iface].push(rpcHandler); + }, + }; + }-*/; + + private void fireRpc(String iface, String method, + JsArray arguments) { + JSONArray argumentsArray = new JSONArray(arguments); + Object[] parameters = new Object[arguments.length()]; + for (int i = 0; i < parameters.length; i++) { + parameters[i] = argumentsArray.get(i); + } + connector.getConnection().addMethodInvocationToQueue( + new MethodInvocation(connector.getConnectorId(), iface, method, + parameters), true); + } + + public void setNativeState(JavaScriptObject state) { + updateNativeState(nativeState, state); + } + + private static native void updateNativeState(JavaScriptObject state, + JavaScriptObject input) + /*-{ + // Copy all fields to existing state object + for(var key in state) { + if (state.hasOwnProperty(key)) { + delete state[key]; + } + } + + for(var key in input) { + if (input.hasOwnProperty(key)) { + state[key] = input[key]; + } + } + }-*/; + + public Object[] decodeRpcParameters(JSONArray parametersJson) { + return new Object[] { parametersJson.getJavaScriptObject() }; + } + + public void setTag(int tag) { + this.tag = tag; + } + + public void invokeJsRpc(MethodInvocation invocation, + JSONArray parametersJson) { + invokeJsRpc(rpcMap, invocation.getInterfaceName(), + invocation.getMethodName(), + parametersJson.getJavaScriptObject()); + } + + private static native void invokeJsRpc(JavaScriptObject rpcMap, + String interfaceName, String methodName, JavaScriptObject parameters) + /*-{ + var targets = rpcMap[interfaceName]; + if (!targets) { + return; + } + for(var i = 0; i < targets.length; i++) { + var target = targets[i]; + target[methodName].apply(target, parameters); + } + }-*/; + +} diff --git a/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java b/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java new file mode 100644 index 0000000000..6c098a52f6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java @@ -0,0 +1,33 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import com.vaadin.terminal.AbstractJavascriptExtension; +import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.ui.AbstractConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; + +@Connect(AbstractJavascriptExtension.class) +public class JavascriptExtension extends AbstractConnector implements + HasJavascriptConnectorHelper { + private final JavascriptConnectorHelper helper = new JavascriptConnectorHelper( + this); + + @Override + protected void init() { + helper.init(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + helper.fireNativeStateChange(); + } + + public JavascriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/WidgetSet.java b/src/com/vaadin/terminal/gwt/client/WidgetSet.java index d7cc2df00d..ecbfb0ecc9 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetSet.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetSet.java @@ -5,6 +5,7 @@ package com.vaadin.terminal.gwt.client; import com.google.gwt.core.client.GWT; +import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class WidgetSet { @@ -52,7 +53,12 @@ public class WidgetSet { /* * let the auto generated code instantiate this type */ - return widgetMap.instantiate(classType); + ServerConnector connector = widgetMap.instantiate(classType); + if (connector instanceof HasJavascriptConnectorHelper) { + ((HasJavascriptConnectorHelper) connector) + .getJavascriptConnectorHelper().setTag(tag); + } + return connector; } } diff --git a/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java new file mode 100644 index 0000000000..74bc75da66 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.communication; + +import com.vaadin.terminal.gwt.client.JavascriptConnectorHelper; + +public interface HasJavascriptConnectorHelper { + public JavascriptConnectorHelper getJavascriptConnectorHelper(); +} diff --git a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java index 657f44896d..cb7dbe5e72 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java +++ b/src/com/vaadin/terminal/gwt/client/communication/JsonEncoder.java @@ -59,6 +59,8 @@ public class JsonEncoder { boolean restrictToInternalTypes, ApplicationConnection connection) { if (null == value) { return JSONNull.getInstance(); + } else if (value instanceof JSONValue) { + return (JSONValue) value; } else if (value instanceof String[]) { String[] array = (String[]) value; JSONArray jsonArray = new JSONArray(); diff --git a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java index 1d3447687d..e0ffb40125 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java +++ b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java @@ -9,8 +9,12 @@ import java.util.HashMap; import java.util.Map; import com.google.gwt.core.client.GWT; +import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONString; +import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.ConnectorMap; import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.VConsole; /** * Client side RPC manager that can invoke methods based on RPC calls received @@ -41,20 +45,10 @@ public class RpcManager { * * @param invocation * method to invoke - * @param connectorMap - * mapper used to find Connector for the method call and any - * connectors referenced in parameters */ public void applyInvocation(MethodInvocation invocation, - ConnectorMap connectorMap) { - ServerConnector connector = connectorMap.getConnector(invocation - .getConnectorId()); + ServerConnector connector) { String signature = getSignature(invocation); - if (connector == null) { - throw new IllegalStateException("Target connector (" - + invocation.getConnectorId() + ") not found for RCC to " - + signature); - } RpcMethod rpcMethod = getRpcMethod(signature); Collection implementations = connector @@ -82,4 +76,47 @@ public class RpcManager { return getRpcMethod(getSignature(invocation)).getParameterTypes(); } + public void parseAndApplyInvocation(JSONArray rpcCall, + ApplicationConnection connection) { + ConnectorMap connectorMap = ConnectorMap.get(connection); + + String connectorId = ((JSONString) rpcCall.get(0)).stringValue(); + String interfaceName = ((JSONString) rpcCall.get(1)).stringValue(); + String methodName = ((JSONString) rpcCall.get(2)).stringValue(); + JSONArray parametersJson = (JSONArray) rpcCall.get(3); + + ServerConnector connector = connectorMap.getConnector(connectorId); + + MethodInvocation invocation = new MethodInvocation(connectorId, + interfaceName, methodName); + if (connector instanceof HasJavascriptConnectorHelper) { + ((HasJavascriptConnectorHelper) connector) + .getJavascriptConnectorHelper().invokeJsRpc(invocation, + parametersJson); + } else { + if (connector == null) { + throw new IllegalStateException("Target connector (" + + connector + ") not found for RCC to " + + getSignature(invocation)); + } + + parseMethodParameters(invocation, parametersJson, connection); + VConsole.log("Server to client RPC call: " + invocation); + applyInvocation(invocation, connector); + } + } + + private void parseMethodParameters(MethodInvocation methodInvocation, + JSONArray parametersJson, ApplicationConnection connection) { + Type[] parameterTypes = getParameterTypes(methodInvocation); + + Object[] parameters = new Object[parametersJson.size()]; + for (int j = 0; j < parametersJson.size(); ++j) { + parameters[j] = JsonDecoder.decodeValue(parameterTypes[j], + parametersJson.get(j), null, connection); + } + + methodInvocation.setParameters(parameters); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java new file mode 100644 index 0000000000..57e65e91c6 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java @@ -0,0 +1,60 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.user.client.Element; +import com.vaadin.terminal.gwt.client.JavascriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.HasJavascriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.ui.AbstractJavascriptComponent; + +@Connect(AbstractJavascriptComponent.class) +public class JavascriptComponentConnector extends AbstractComponentConnector + implements HasJavascriptConnectorHelper { + + private final JavascriptConnectorHelper helper = new JavascriptConnectorHelper( + this) { + @Override + protected void showInitProblem( + java.util.ArrayList attemptedNames) { + getWidget().showNoInitFound(attemptedNames); + } + + @Override + protected JavaScriptObject createConnectorWrapper() { + JavaScriptObject connectorWrapper = super.createConnectorWrapper(); + addGetWidgetElement(connectorWrapper, getWidget().getElement()); + return connectorWrapper; + } + }; + + @Override + protected void init() { + helper.init(); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + helper.fireNativeStateChange(); + } + + private static native void addGetWidgetElement( + JavaScriptObject connectorWrapper, Element element) + /*-{ + connectorWrapper.getWidgetElement = function() { + return element; + }; + }-*/; + + @Override + public JavascriptWidget getWidget() { + return (JavascriptWidget) super.getWidget(); + } + + public JavascriptConnectorHelper getJavascriptConnectorHelper() { + return helper; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java b/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java new file mode 100644 index 0000000000..93a4417b1c --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java @@ -0,0 +1,25 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.ArrayList; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.user.client.ui.Widget; + +public class JavascriptWidget extends Widget { + public JavascriptWidget() { + setElement(Document.get().createDivElement()); + } + + public void showNoInitFound(ArrayList attemptedNames) { + String message = "Could not initialize JavascriptConnector because no javascript init function was found. Make sure one of these functions are defined:
    "; + for (String name : attemptedNames) { + message += "
  • " + name + "
  • "; + } + message += "
"; + + getElement().setInnerHTML(message); + } +} diff --git a/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java b/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java index 8a0c700121..ae1fadd91b 100644 --- a/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java +++ b/src/com/vaadin/terminal/gwt/server/BootstrapHandler.java @@ -9,6 +9,10 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Serializable; import java.io.Writer; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletResponse; @@ -16,6 +20,7 @@ import javax.servlet.http.HttpServletResponse; import com.vaadin.Application; import com.vaadin.RootRequiresMoreInformationException; import com.vaadin.Version; +import com.vaadin.annotations.LoadScripts; import com.vaadin.external.json.JSONException; import com.vaadin.external.json.JSONObject; import com.vaadin.terminal.DeploymentConfiguration; @@ -467,15 +472,15 @@ public abstract class BootstrapHandler implements RequestHandler { page.write("\n"); page.write(""); + + "html, body {height:100%;margin:0;}\n"); // Add favicon links if (themeName != null) { String themeUri = getThemeUri(context, themeName); page.write(""); + + themeUri + "/favicon.ico\" />\n"); page.write(""); + + themeUri + "/favicon.ico\" />\n"); } Root root = context.getRoot(); @@ -484,7 +489,51 @@ public abstract class BootstrapHandler implements RequestHandler { page.write("" + AbstractApplicationServlet.safeEscapeForHtml(title) - + ""); + + "\n"); + + if (root != null) { + List loadScriptsAnnotations = getAnnotationsFor( + root.getClass(), LoadScripts.class); + Collections.reverse(loadScriptsAnnotations); + // Begin from the end as a class might requests scripts that depend + // on script loaded by a super class + for (int i = loadScriptsAnnotations.size() - 1; i >= 0; i--) { + LoadScripts loadScripts = loadScriptsAnnotations.get(i); + String[] value = loadScripts.value(); + if (value != null) { + for (String script : value) { + page.write("\n"); + } + } + } + + } + } + + private static List getAnnotationsFor( + Class type, Class annotationType) { + List list = new ArrayList(); + // Find from the class hierarchy + Class currentType = type; + while (currentType != Object.class) { + T annotation = currentType.getAnnotation(annotationType); + if (annotation != null) { + list.add(annotation); + } + currentType = currentType.getSuperclass(); + } + + // Find from an implemented interface + for (Class iface : type.getInterfaces()) { + T annotation = iface.getAnnotation(annotationType); + if (annotation != null) { + list.add(annotation); + } + } + + return list; } /** diff --git a/src/com/vaadin/ui/AbstractJavascriptComponent.java b/src/com/vaadin/ui/AbstractJavascriptComponent.java new file mode 100644 index 0000000000..458db29172 --- /dev/null +++ b/src/com/vaadin/ui/AbstractJavascriptComponent.java @@ -0,0 +1,8 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +public class AbstractJavascriptComponent extends AbstractComponent { + +} diff --git a/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java b/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java new file mode 100644 index 0000000000..2240fc246b --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java @@ -0,0 +1,73 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.tests.components.javascriptcomponent; + +import java.util.Arrays; +import java.util.List; + +import com.vaadin.annotations.LoadScripts; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.AbstractJavascriptComponent; +import com.vaadin.ui.Root; + +@LoadScripts({ "/statictestfiles/jsconnector.js" }) +public class BasicJavascriptComponent extends AbstractTestRoot { + + public interface ExampleClickRpc extends ServerRpc { + public void onClick(String message); + } + + public static class SpecialState extends ComponentState { + private List data; + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + } + + public static class ExampleWidget extends AbstractJavascriptComponent { + public ExampleWidget() { + registerRpc(new ExampleClickRpc() { + public void onClick(String message) { + Root.getCurrentRoot().showNotification( + "Got a click: " + message); + } + }); + getState().setData(Arrays.asList("a", "b", "c")); + } + + @Override + public SpecialState getState() { + return (SpecialState) super.getState(); + } + } + + @Override + protected void setup(WrappedRequest request) { + ExampleWidget c = new ExampleWidget(); + c.setCaption("test caption"); + c.setDescription("Some description"); + addComponent(c); + } + + @Override + protected String getTestDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/tests/testbench/com/vaadin/tests/features/SimpleJavascriptExtensionTest.java b/tests/testbench/com/vaadin/tests/features/SimpleJavascriptExtensionTest.java new file mode 100644 index 0000000000..591689b4b0 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/features/SimpleJavascriptExtensionTest.java @@ -0,0 +1,95 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.features; + +import com.vaadin.annotations.LoadScripts; +import com.vaadin.terminal.AbstractJavascriptExtension; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.gwt.client.communication.ClientRpc; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.communication.SharedState; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.Root; + +@LoadScripts({ "/statictestfiles/jsextension.js" }) +public class SimpleJavascriptExtensionTest extends AbstractTestRoot { + + public static class SimpleJavascriptExtensionState extends SharedState { + private String prefix; + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return prefix; + } + } + + public static interface SimpleJavascriptExtensionClientRpc extends + ClientRpc { + public void greet(String message); + } + + public static interface SimpleJavascriptExtensionServerRpc extends + ServerRpc { + public void greet(String message); + } + + public static class SimpleJavascriptExtension extends + AbstractJavascriptExtension { + + public SimpleJavascriptExtension() { + registerRpc(new SimpleJavascriptExtensionServerRpc() { + public void greet(String message) { + Root.getCurrentRoot().showNotification( + getState().getPrefix() + message); + } + }); + } + + @Override + public SimpleJavascriptExtensionState getState() { + return (SimpleJavascriptExtensionState) super.getState(); + } + + public void setPrefix(String prefix) { + getState().setPrefix(prefix); + requestRepaint(); + } + + public void greet(String message) { + getRpcProxy(SimpleJavascriptExtensionClientRpc.class) + .greet(message); + } + } + + @Override + protected void setup(WrappedRequest request) { + final SimpleJavascriptExtension simpleJavascriptExtension = new SimpleJavascriptExtension(); + simpleJavascriptExtension.setPrefix("Prefix: "); + addExtension(simpleJavascriptExtension); + addComponent(new Button("Send greeting", new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + simpleJavascriptExtension.greet("Greeted by button"); + } + })); + } + + @Override + protected String getTestDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +} -- 2.39.5