From 755adc2731383e6e04ddfa5bee04b6ed6feaf243 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Thu, 14 Jun 2012 17:42:04 +0300 Subject: Rename Javascript -> JavaScript --- src/com/vaadin/ui/AbstractJavaScriptComponent.java | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/com/vaadin/ui/AbstractJavaScriptComponent.java (limited to 'src/com/vaadin/ui/AbstractJavaScriptComponent.java') diff --git a/src/com/vaadin/ui/AbstractJavaScriptComponent.java b/src/com/vaadin/ui/AbstractJavaScriptComponent.java new file mode 100644 index 0000000000..881ac3f9d9 --- /dev/null +++ b/src/com/vaadin/ui/AbstractJavaScriptComponent.java @@ -0,0 +1,20 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.ui; + +import com.vaadin.terminal.JavaScriptCallbackHelper; + +public class AbstractJavaScriptComponent extends AbstractComponent { + private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( + this); + + protected void registerCallback(String functionName, + JavaScriptCallback javascriptCallback) { + callbackHelper.registerCallback(functionName, javascriptCallback); + } + + protected void invokeCallback(String name, Object... arguments) { + callbackHelper.invokeCallback(name, arguments); + } +} -- cgit v1.2.3 From 72c0f66e2aa588acbb517e6d9b482c7a75faf9f1 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Thu, 14 Jun 2012 21:18:49 +0300 Subject: Use callbacks directly as fields in connector object (#8888) --- WebContent/statictestfiles/jsconnector.js | 1 + WebContent/statictestfiles/jsextension.js | 8 +++ .../terminal/AbstractJavaScriptExtension.java | 6 ++ .../vaadin/terminal/JavaScriptCallbackHelper.java | 17 +++++ .../gwt/client/JavaScriptConnectorHelper.java | 78 ++++++++++++++-------- .../terminal/gwt/client/JavaScriptExtension.java | 12 ++-- .../gwt/client/JavaScriptExtensionState.java | 26 ++++++++ .../client/ui/JavaScriptComponentConnector.java | 12 ++-- .../gwt/client/ui/JavaScriptComponentState.java | 26 ++++++++ src/com/vaadin/ui/AbstractJavaScriptComponent.java | 6 ++ .../BasicJavaScriptComponent.java | 13 +++- .../extensions/SimpleJavaScriptExtensionTest.java | 38 ++++++++--- 12 files changed, 193 insertions(+), 50 deletions(-) create mode 100644 src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java create mode 100644 src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java (limited to 'src/com/vaadin/ui/AbstractJavaScriptComponent.java') diff --git a/WebContent/statictestfiles/jsconnector.js b/WebContent/statictestfiles/jsconnector.js index 1432c20698..e08c748aa6 100644 --- a/WebContent/statictestfiles/jsconnector.js +++ b/WebContent/statictestfiles/jsconnector.js @@ -5,6 +5,7 @@ window.com_vaadin_tests_components_javascriptcomponent_BasicJavaScriptComponent_ rootElement.innerHTML = 'Hello world!'; rootElement.onclick = function() { connector.getRpcProxyFunction("com.vaadin.tests.components.javascriptcomponent.BasicJavaScriptComponent$ExampleClickRpc", "onClick")("message"); + connector.onclick("another message"); } connector.onStateChange = function() { console.log('state change:', this.getState()); diff --git a/WebContent/statictestfiles/jsextension.js b/WebContent/statictestfiles/jsextension.js index be551746ca..d824218560 100644 --- a/WebContent/statictestfiles/jsextension.js +++ b/WebContent/statictestfiles/jsextension.js @@ -1,4 +1,5 @@ window.com_vaadin_tests_extensions_SimpleJavaScriptExtensionTest_SimpleJavascriptExtension = function() { + var self = this; var state = this.getState(); var greetBack = this.getRpcProxyFunction('com.vaadin.tests.extensions.SimpleJavaScriptExtensionTest$SimpleJavaScriptExtensionServerRpc', 'greet'); @@ -10,4 +11,11 @@ window.com_vaadin_tests_extensions_SimpleJavaScriptExtensionTest_SimpleJavascrip } } }); + + this.greetToClient = function(greeting) { + var response = window.prompt(state.prefix + greeting); + if (response !== null) { + self.greetToServer(response); + } + } } \ No newline at end of file diff --git a/src/com/vaadin/terminal/AbstractJavaScriptExtension.java b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java index 49f361f343..22c8dd4561 100644 --- a/src/com/vaadin/terminal/AbstractJavaScriptExtension.java +++ b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java @@ -4,6 +4,7 @@ package com.vaadin.terminal; +import com.vaadin.terminal.gwt.client.JavaScriptExtensionState; import com.vaadin.ui.JavaScriptCallback; public class AbstractJavaScriptExtension extends AbstractExtension { @@ -18,4 +19,9 @@ public class AbstractJavaScriptExtension extends AbstractExtension { protected void invokeCallback(String name, Object... arguments) { callbackHelper.invokeCallback(name, arguments); } + + @Override + public JavaScriptExtensionState getState() { + return (JavaScriptExtensionState) super.getState(); + } } diff --git a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java index 5ea5e56255..c1da8b36ba 100644 --- a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java +++ b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java @@ -11,6 +11,7 @@ import java.util.Map; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; import com.vaadin.tools.ReflectTools; import com.vaadin.ui.JavaScript.JavaScriptCallbackRpc; import com.vaadin.ui.JavaScriptCallback; @@ -31,9 +32,19 @@ public class JavaScriptCallbackHelper { public void registerCallback(String functionName, JavaScriptCallback javaScriptCallback) { callbacks.put(functionName, javaScriptCallback); + JavaScriptConnectorState state = getConnectorState(); + if (state.getCallbackNames().add(functionName)) { + connector.requestRepaint(); + } ensureRpc(); } + private JavaScriptConnectorState getConnectorState() { + JavaScriptConnectorState state = (JavaScriptConnectorState) connector + .getState(); + return state; + } + private void ensureRpc() { if (javascriptCallbackRpc == null) { javascriptCallbackRpc = new JavaScriptCallbackRpc() { @@ -51,6 +62,12 @@ public class JavaScriptCallbackHelper { } public void invokeCallback(String name, Object... arguments) { + if (callbacks.containsKey(name)) { + throw new IllegalStateException( + "Can't call callback " + + name + + " on the client because a callback with the same name is registered on the server."); + } JSONArray args = new JSONArray(Arrays.asList(arguments)); connector.addMethodInvocationToQueue( JavaScriptCallbackRpc.class.getName(), CALL_METHOD, diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java index 2c7ca22157..d07cf0036c 100644 --- a/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java @@ -5,14 +5,21 @@ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; +import java.util.Set; 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; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; public class JavaScriptConnectorHelper { + public interface JavaScriptConnectorState { + public Set getCallbackNames(); + } + private final ServerConnector connector; private final JavaScriptObject nativeState = JavaScriptObject .createObject(); @@ -23,6 +30,18 @@ public class JavaScriptConnectorHelper { public JavaScriptConnectorHelper(ServerConnector connector) { this.connector = connector; + connector.addStateChangeHandler(new StateChangeHandler() { + public void onStateChanged(StateChangeEvent stateChangeEvent) { + JavaScriptObject wrapper = getConnectorWrapper(); + + for (String callback : getConnectorState().getCallbackNames()) { + ensureCallback(JavaScriptConnectorHelper.this, wrapper, + callback); + } + + fireNativeStateChange(wrapper); + } + }); } public boolean init() { @@ -78,10 +97,6 @@ public class JavaScriptConnectorHelper { connector.getConnectorId()); } - public void fireNativeStateChange() { - fireNativeStateChange(getConnectorWrapper()); - } - private static native void fireNativeStateChange( JavaScriptObject connectorWrapper) /*-{ @@ -106,21 +121,6 @@ public class JavaScriptConnectorHelper { h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); }); }, - 'getCallback': function(name) { - return $entry(function() { - var args = [name, Array.prototype.slice.call(arguments, 0)]; - var iface = "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc"; - var method = "call"; - h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, args); - }); - }, - 'registerCallback': function(name, callback) { - //TODO maintain separate map - if (!registeredRpc[name]) { - registeredRpc[name] = []; - } - registeredRpc[name].push(callback); - }, 'registerRpc': function(iface, rpcHandler) { if (!registeredRpc[iface]) { registeredRpc[iface] = []; @@ -142,6 +142,14 @@ public class JavaScriptConnectorHelper { parameters), true); } + private void fireCallback(String name, JsArray arguments) { + MethodInvocation invocation = new MethodInvocation( + connector.getConnectorId(), + "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", "call", + new Object[] { name, new JSONArray(arguments) }); + connector.getConnection().addMethodInvocationToQueue(invocation, true); + } + public void setNativeState(JavaScriptObject state) { updateNativeState(nativeState, state); } @@ -176,8 +184,11 @@ public class JavaScriptConnectorHelper { if ("com.vaadin.ui.JavaScript$JavaScriptCallbackRpc".equals(invocation .getInterfaceName()) && "call".equals(invocation.getMethodName())) { - invokeJsRpc(rpcMap, parametersJson.get(0).isString().stringValue(), - null, parametersJson.get(1).isArray().getJavaScriptObject()); + String callbackName = parametersJson.get(0).isString() + .stringValue(); + JavaScriptObject arguments = parametersJson.get(1).isArray() + .getJavaScriptObject(); + invokeCallback(getConnectorWrapper(), callbackName, arguments); } else { invokeJsRpc(rpcMap, invocation.getInterfaceName(), invocation.getMethodName(), @@ -185,6 +196,12 @@ public class JavaScriptConnectorHelper { } } + private static native void invokeCallback(JavaScriptObject connector, + String name, JavaScriptObject arguments) + /*-{ + connector[name](arguments); + }-*/; + private static native void invokeJsRpc(JavaScriptObject rpcMap, String interfaceName, String methodName, JavaScriptObject parameters) /*-{ @@ -194,12 +211,21 @@ public class JavaScriptConnectorHelper { } for(var i = 0; i < targets.length; i++) { var target = targets[i]; - if (methodName === null && typeof target === 'function') { - target.apply($wnd, parameters); - } else { - target[methodName].apply(target, parameters); - } + target[methodName].apply(target, parameters); } }-*/; + private static native void ensureCallback(JavaScriptConnectorHelper h, + JavaScriptObject connector, String name) + /*-{ + connector[name] = $entry(function() { + var args = Array.prototype.slice.call(arguments, 0); + h.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireCallback(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); + }); + }-*/; + + private JavaScriptConnectorState getConnectorState() { + return (JavaScriptConnectorState) connector.getState(); + } + } diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java index 121208d754..47aa7eab28 100644 --- a/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java @@ -6,7 +6,6 @@ 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.extensions.AbstractExtensionConnector; import com.vaadin.terminal.gwt.client.ui.Connect; @@ -21,13 +20,12 @@ public class JavaScriptExtension extends AbstractExtensionConnector implements helper.init(); } - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - helper.fireNativeStateChange(); - } - public JavaScriptConnectorHelper getJavascriptConnectorHelper() { return helper; } + + @Override + public JavaScriptExtensionState getState() { + return (JavaScriptExtensionState) super.getState(); + } } diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java new file mode 100644 index 0000000000..fecf24d450 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java @@ -0,0 +1,26 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client; + +import java.util.HashSet; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; +import com.vaadin.terminal.gwt.client.communication.SharedState; + +public class JavaScriptExtensionState extends SharedState implements + JavaScriptConnectorState { + + private Set callbackNames = new HashSet(); + + public Set getCallbackNames() { + return callbackNames; + } + + public void setCallbackNames(Set callbackNames) { + this.callbackNames = callbackNames; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java index c8b22a4239..ecab58bb9a 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java @@ -7,7 +7,6 @@ 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) @@ -35,12 +34,6 @@ public class JavaScriptComponentConnector extends AbstractComponentConnector helper.init(); } - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - super.onStateChanged(stateChangeEvent); - helper.fireNativeStateChange(); - } - private static native void addGetWidgetElement( JavaScriptObject connectorWrapper, Element element) /*-{ @@ -57,4 +50,9 @@ public class JavaScriptComponentConnector extends AbstractComponentConnector public JavaScriptConnectorHelper getJavascriptConnectorHelper() { return helper; } + + @Override + public JavaScriptComponentState getState() { + return (JavaScriptComponentState) super.getState(); + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java new file mode 100644 index 0000000000..bc8ed08bba --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java @@ -0,0 +1,26 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashSet; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.ComponentState; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; + +public class JavaScriptComponentState extends ComponentState implements + JavaScriptConnectorState { + + private Set callbackNames = new HashSet(); + + public Set getCallbackNames() { + return callbackNames; + } + + public void setCallbackNames(Set callbackNames) { + this.callbackNames = callbackNames; + } + +} diff --git a/src/com/vaadin/ui/AbstractJavaScriptComponent.java b/src/com/vaadin/ui/AbstractJavaScriptComponent.java index 881ac3f9d9..e5759a1efc 100644 --- a/src/com/vaadin/ui/AbstractJavaScriptComponent.java +++ b/src/com/vaadin/ui/AbstractJavaScriptComponent.java @@ -4,6 +4,7 @@ package com.vaadin.ui; import com.vaadin.terminal.JavaScriptCallbackHelper; +import com.vaadin.terminal.gwt.client.ui.JavaScriptComponentState; public class AbstractJavaScriptComponent extends AbstractComponent { private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( @@ -17,4 +18,9 @@ public class AbstractJavaScriptComponent extends AbstractComponent { protected void invokeCallback(String name, Object... arguments) { callbackHelper.invokeCallback(name, arguments); } + + @Override + public JavaScriptComponentState getState() { + return (JavaScriptComponentState) super.getState(); + } } diff --git a/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavaScriptComponent.java b/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavaScriptComponent.java index 4de80aaf9d..6519e4ae74 100644 --- a/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavaScriptComponent.java +++ b/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavaScriptComponent.java @@ -7,11 +7,14 @@ import java.util.Arrays; import java.util.List; import com.vaadin.annotations.LoadScripts; +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; import com.vaadin.terminal.WrappedRequest; -import com.vaadin.terminal.gwt.client.ComponentState; import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.ui.JavaScriptComponentState; import com.vaadin.tests.components.AbstractTestRoot; import com.vaadin.ui.AbstractJavaScriptComponent; +import com.vaadin.ui.JavaScriptCallback; import com.vaadin.ui.Root; @LoadScripts({ "/statictestfiles/jsconnector.js" }) @@ -21,7 +24,7 @@ public class BasicJavaScriptComponent extends AbstractTestRoot { public void onClick(String message); } - public static class SpecialState extends ComponentState { + public static class SpecialState extends JavaScriptComponentState { private List data; public List getData() { @@ -41,6 +44,12 @@ public class BasicJavaScriptComponent extends AbstractTestRoot { "Got a click: " + message); } }); + registerCallback("onclick", new JavaScriptCallback() { + public void call(JSONArray arguments) throws JSONException { + Root.getCurrentRoot().showNotification( + "Got a callback: " + arguments.getString(0)); + } + }); getState().setData(Arrays.asList("a", "b", "c")); } diff --git a/tests/testbench/com/vaadin/tests/extensions/SimpleJavaScriptExtensionTest.java b/tests/testbench/com/vaadin/tests/extensions/SimpleJavaScriptExtensionTest.java index 983520ee93..8bcf873cd5 100644 --- a/tests/testbench/com/vaadin/tests/extensions/SimpleJavaScriptExtensionTest.java +++ b/tests/testbench/com/vaadin/tests/extensions/SimpleJavaScriptExtensionTest.java @@ -5,20 +5,24 @@ package com.vaadin.tests.extensions; import com.vaadin.annotations.LoadScripts; +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; import com.vaadin.terminal.AbstractJavaScriptExtension; import com.vaadin.terminal.WrappedRequest; +import com.vaadin.terminal.gwt.client.JavaScriptExtensionState; 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.JavaScriptCallback; import com.vaadin.ui.Root; @LoadScripts({ "/statictestfiles/jsextension.js" }) public class SimpleJavaScriptExtensionTest extends AbstractTestRoot { - public static class SimpleJavaScriptExtensionState extends SharedState { + public static class SimpleJavaScriptExtensionState extends + JavaScriptExtensionState { private String prefix; public void setPrefix(String prefix) { @@ -50,6 +54,12 @@ public class SimpleJavaScriptExtensionTest extends AbstractTestRoot { getState().getPrefix() + message); } }); + registerCallback("greetToServer", new JavaScriptCallback() { + public void call(JSONArray arguments) throws JSONException { + Root.getCurrentRoot().showNotification( + getState().getPrefix() + arguments.getString(0)); + } + }); } @Override @@ -62,10 +72,14 @@ public class SimpleJavaScriptExtensionTest extends AbstractTestRoot { requestRepaint(); } - public void greet(String message) { + public void greetRpc(String message) { getRpcProxy(SimpleJavaScriptExtensionClientRpc.class) .greet(message); } + + public void greetCallback(String message) { + invokeCallback("greetToClient", message); + } } @Override @@ -73,11 +87,19 @@ public class SimpleJavaScriptExtensionTest extends AbstractTestRoot { 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"); - } - })); + addComponent(new Button("Send rpc greeting", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + simpleJavascriptExtension.greetRpc("Rpc greeting"); + } + })); + addComponent(new Button("Send callback greeting", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + simpleJavascriptExtension + .greetCallback("Callback greeting"); + } + })); } @Override -- cgit v1.2.3 From 51ce4a2535146a3b8861bd02f655c18d9a397522 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Thu, 14 Jun 2012 22:57:28 +0300 Subject: Implement js rpc proxy objects and wildcard rpc support (#8888) --- WebContent/statictestfiles/jsconnector.js | 2 +- WebContent/statictestfiles/jsextension.js | 9 +- .../terminal/AbstractJavaScriptExtension.java | 7 ++ .../vaadin/terminal/JavaScriptCallbackHelper.java | 22 ++++ .../gwt/client/JavaScriptConnectorHelper.java | 133 ++++++++++++++++++--- .../terminal/gwt/client/JavaScriptExtension.java | 5 - .../gwt/client/JavaScriptExtensionState.java | 10 ++ .../client/ui/JavaScriptComponentConnector.java | 5 - .../gwt/client/ui/JavaScriptComponentState.java | 11 ++ src/com/vaadin/ui/AbstractJavaScriptComponent.java | 7 ++ 10 files changed, 183 insertions(+), 28 deletions(-) (limited to 'src/com/vaadin/ui/AbstractJavaScriptComponent.java') diff --git a/WebContent/statictestfiles/jsconnector.js b/WebContent/statictestfiles/jsconnector.js index e08c748aa6..d7f697dbf5 100644 --- a/WebContent/statictestfiles/jsconnector.js +++ b/WebContent/statictestfiles/jsconnector.js @@ -4,7 +4,7 @@ window.com_vaadin_tests_components_javascriptcomponent_BasicJavaScriptComponent_ var rootElement = connector.getWidgetElement(); rootElement.innerHTML = 'Hello world!'; rootElement.onclick = function() { - connector.getRpcProxyFunction("com.vaadin.tests.components.javascriptcomponent.BasicJavaScriptComponent$ExampleClickRpc", "onClick")("message"); + connector.getRpcProxy().onClick("message"); connector.onclick("another message"); } connector.onStateChange = function() { diff --git a/WebContent/statictestfiles/jsextension.js b/WebContent/statictestfiles/jsextension.js index d824218560..1a46300e45 100644 --- a/WebContent/statictestfiles/jsextension.js +++ b/WebContent/statictestfiles/jsextension.js @@ -1,13 +1,16 @@ window.com_vaadin_tests_extensions_SimpleJavaScriptExtensionTest_SimpleJavascriptExtension = function() { var self = this; var state = this.getState(); - var greetBack = this.getRpcProxyFunction('com.vaadin.tests.extensions.SimpleJavaScriptExtensionTest$SimpleJavaScriptExtensionServerRpc', 'greet'); + + //var rpc = this.getRpcProxy("com.vaadin.tests.extensions.SimpleJavaScriptExtensionTest.SimpleJavaScriptExtensionServerRpc"); + var rpc = this.getRpcProxy(); - this.registerRpc("com.vaadin.tests.extensions.SimpleJavaScriptExtensionTest.SimpleJavaScriptExtensionClientRpc", { +// this.registerRpc("com.vaadin.tests.extensions.SimpleJavaScriptExtensionTest.SimpleJavaScriptExtensionClientRpc", { + this.registerRpc({ 'greet': function(greeting) { var response = window.prompt(state.prefix + greeting); if (response !== null) { - greetBack(response); + rpc.greet(response); } } }); diff --git a/src/com/vaadin/terminal/AbstractJavaScriptExtension.java b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java index 22c8dd4561..a2f8019858 100644 --- a/src/com/vaadin/terminal/AbstractJavaScriptExtension.java +++ b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java @@ -11,6 +11,13 @@ public class AbstractJavaScriptExtension extends AbstractExtension { private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( this); + @Override + protected void registerRpc(T implementation, + java.lang.Class rpcInterfaceType) { + super.registerRpc(implementation, rpcInterfaceType); + callbackHelper.registerRpc(rpcInterfaceType); + } + protected void registerCallback(String functionName, JavaScriptCallback javaScriptCallback) { callbackHelper.registerCallback(functionName, javaScriptCallback); diff --git a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java index c1da8b36ba..eabbcf4925 100644 --- a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java +++ b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java @@ -7,7 +7,9 @@ package com.vaadin.terminal; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; @@ -75,4 +77,24 @@ public class JavaScriptCallbackHelper { connector.requestRepaint(); } + public void registerRpc(Class rpcInterfaceType) { + if (rpcInterfaceType == JavaScriptCallbackRpc.class) { + // Ignore + return; + } + Map> rpcInterfaces = getConnectorState() + .getRpcInterfaces(); + String interfaceName = rpcInterfaceType.getName(); + if (!rpcInterfaces.containsKey(interfaceName)) { + Set methodNames = new HashSet(); + + for (Method method : rpcInterfaceType.getMethods()) { + methodNames.add(method.getName()); + } + + rpcInterfaces.put(interfaceName, methodNames); + connector.requestRepaint(); + } + } + } diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java index d07cf0036c..77adcd4217 100644 --- a/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java @@ -5,6 +5,10 @@ package com.vaadin.terminal.gwt.client; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import com.google.gwt.core.client.JavaScriptObject; @@ -18,6 +22,8 @@ public class JavaScriptConnectorHelper { public interface JavaScriptConnectorState { public Set getCallbackNames(); + + public Map> getRpcInterfaces(); } private final ServerConnector connector; @@ -25,26 +31,80 @@ public class JavaScriptConnectorHelper { .createObject(); private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); + private final Map rpcObjects = new HashMap(); + private final Map> rpcMethods = new HashMap>(); + private JavaScriptObject connectorWrapper; private int tag; + private boolean inited = false; + public JavaScriptConnectorHelper(ServerConnector connector) { this.connector = connector; + + // Wildcard rpc object + rpcObjects.put("", JavaScriptObject.createObject()); + connector.addStateChangeHandler(new StateChangeHandler() { public void onStateChanged(StateChangeEvent stateChangeEvent) { JavaScriptObject wrapper = getConnectorWrapper(); + JavaScriptConnectorState state = getConnectorState(); - for (String callback : getConnectorState().getCallbackNames()) { + for (String callback : state.getCallbackNames()) { ensureCallback(JavaScriptConnectorHelper.this, wrapper, callback); } + for (Entry> entry : state + .getRpcInterfaces().entrySet()) { + String rpcName = entry.getKey(); + String jsName = getJsInterfaceName(rpcName); + if (!rpcObjects.containsKey(jsName)) { + Set methods = entry.getValue(); + rpcObjects.put(jsName, + createRpcObject(rpcName, methods)); + + // Init all methods for wildcard rpc + for (String method : methods) { + JavaScriptObject wildcardRpcObject = rpcObjects + .get(""); + Set interfaces = rpcMethods.get(method); + if (interfaces == null) { + interfaces = new HashSet(); + rpcMethods.put(method, interfaces); + attachRpcMethod(wildcardRpcObject, null, method); + } + interfaces.add(rpcName); + } + } + } + + // Init after setting up callbacks & rpc + if (!inited) { + init(); + inited = true; + } + fireNativeStateChange(wrapper); } }); } - public boolean init() { + private static String getJsInterfaceName(String rpcName) { + return rpcName.replace('$', '.'); + } + + protected JavaScriptObject createRpcObject(String iface, Set methods) { + JavaScriptObject object = JavaScriptObject.createObject(); + + for (String method : methods) { + attachRpcMethod(object, iface, method); + } + + return object; + } + + private boolean init() { ApplicationConfiguration conf = connector.getConnection() .getConfiguration(); ArrayList attemptedNames = new ArrayList(); @@ -94,7 +154,7 @@ public class JavaScriptConnectorHelper { protected JavaScriptObject createConnectorWrapper() { return createConnectorWrapper(this, nativeState, rpcMap, - connector.getConnectorId()); + connector.getConnectorId(), rpcObjects); } private static native void fireNativeStateChange( @@ -107,7 +167,8 @@ public class JavaScriptConnectorHelper { private static native JavaScriptObject createConnectorWrapper( JavaScriptConnectorHelper h, JavaScriptObject nativeState, - JavaScriptObject registeredRpc, String connectorId) + JavaScriptObject registeredRpc, String connectorId, + Map rpcObjects) /*-{ return { 'getConnectorId': function() { @@ -116,12 +177,18 @@ public class JavaScriptConnectorHelper { '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); - }); + 'getRpcProxy': function(iface) { + if (!iface) { + iface = ''; + } + return rpcObjects.@java.util.Map::get(Ljava/lang/Object;)(iface); }, 'registerRpc': function(iface, rpcHandler) { + //registerRpc(handler) -> registerRpc('', handler); + if (!rpcHandler) { + rpcHandler = iface; + iface = ''; + } if (!registeredRpc[iface]) { registeredRpc[iface] = []; } @@ -130,8 +197,21 @@ public class JavaScriptConnectorHelper { }; }-*/; + private native void attachRpcMethod(JavaScriptObject rpc, String iface, + String method) + /*-{ + var self = this; + rpc[method] = $entry(function() { + self.@com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper::fireRpc(Ljava/lang/String;Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(iface, method, arguments); + }); + }-*/; + private void fireRpc(String iface, String method, JsArray arguments) { + if (iface == null) { + iface = findWildcardInterface(method); + } + JSONArray argumentsArray = new JSONArray(arguments); Object[] parameters = new Object[arguments.length()]; for (int i = 0; i < parameters.length; i++) { @@ -142,6 +222,29 @@ public class JavaScriptConnectorHelper { parameters), true); } + private String findWildcardInterface(String method) { + Set interfaces = rpcMethods.get(method); + if (interfaces.size() == 1) { + return interfaces.iterator().next(); + } else { + // TODO Resolve conflicts using argument count and types + String interfaceList = ""; + for (String iface : interfaces) { + if (interfaceList.length() != 0) { + interfaceList += ", "; + } + interfaceList += getJsInterfaceName(iface); + } + + throw new IllegalStateException( + "Can not call method " + + method + + " for wildcard rpc proxy because the function is defined for multiple rpc interfaces: " + + interfaceList + + ". Retrieve a rpc proxy for a specific interface using getRpcProxy(interfaceName) to use the function."); + } + } + private void fireCallback(String name, JsArray arguments) { MethodInvocation invocation = new MethodInvocation( connector.getConnectorId(), @@ -181,18 +284,20 @@ public class JavaScriptConnectorHelper { public void invokeJsRpc(MethodInvocation invocation, JSONArray parametersJson) { - if ("com.vaadin.ui.JavaScript$JavaScriptCallbackRpc".equals(invocation - .getInterfaceName()) - && "call".equals(invocation.getMethodName())) { + String iface = invocation.getInterfaceName(); + String method = invocation.getMethodName(); + if ("com.vaadin.ui.JavaScript$JavaScriptCallbackRpc".equals(iface) + && "call".equals(method)) { String callbackName = parametersJson.get(0).isString() .stringValue(); JavaScriptObject arguments = parametersJson.get(1).isArray() .getJavaScriptObject(); invokeCallback(getConnectorWrapper(), callbackName, arguments); } else { - invokeJsRpc(rpcMap, invocation.getInterfaceName(), - invocation.getMethodName(), - parametersJson.getJavaScriptObject()); + JavaScriptObject arguments = parametersJson.getJavaScriptObject(); + invokeJsRpc(rpcMap, iface, method, arguments); + // Also invoke wildcard interface + invokeJsRpc(rpcMap, "", method, arguments); } } diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java index 47aa7eab28..e3dafab9bd 100644 --- a/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java @@ -15,11 +15,6 @@ public class JavaScriptExtension extends AbstractExtensionConnector implements private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( this); - @Override - protected void init() { - helper.init(); - } - public JavaScriptConnectorHelper getJavascriptConnectorHelper() { return helper; } diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java index fecf24d450..e7bfbc4bb2 100644 --- a/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java @@ -4,7 +4,9 @@ package com.vaadin.terminal.gwt.client; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; @@ -14,6 +16,7 @@ public class JavaScriptExtensionState extends SharedState implements JavaScriptConnectorState { private Set callbackNames = new HashSet(); + private Map> rpcInterfaces = new HashMap>(); public Set getCallbackNames() { return callbackNames; @@ -23,4 +26,11 @@ public class JavaScriptExtensionState extends SharedState implements this.callbackNames = callbackNames; } + public Map> getRpcInterfaces() { + return rpcInterfaces; + } + + public void setRpcInterfaces(Map> rpcInterfaces) { + this.rpcInterfaces = rpcInterfaces; + } } diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java index ecab58bb9a..47afe95771 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java @@ -29,11 +29,6 @@ public class JavaScriptComponentConnector extends AbstractComponentConnector } }; - @Override - protected void init() { - helper.init(); - } - private static native void addGetWidgetElement( JavaScriptObject connectorWrapper, Element element) /*-{ diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java index bc8ed08bba..6728f85ec9 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java @@ -4,7 +4,9 @@ package com.vaadin.terminal.gwt.client.ui; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; import java.util.Set; import com.vaadin.terminal.gwt.client.ComponentState; @@ -14,6 +16,7 @@ public class JavaScriptComponentState extends ComponentState implements JavaScriptConnectorState { private Set callbackNames = new HashSet(); + private Map> rpcInterfaces = new HashMap>(); public Set getCallbackNames() { return callbackNames; @@ -23,4 +26,12 @@ public class JavaScriptComponentState extends ComponentState implements this.callbackNames = callbackNames; } + public Map> getRpcInterfaces() { + return rpcInterfaces; + } + + public void setRpcInterfaces(Map> rpcInterfaces) { + this.rpcInterfaces = rpcInterfaces; + } + } diff --git a/src/com/vaadin/ui/AbstractJavaScriptComponent.java b/src/com/vaadin/ui/AbstractJavaScriptComponent.java index e5759a1efc..343caf4abb 100644 --- a/src/com/vaadin/ui/AbstractJavaScriptComponent.java +++ b/src/com/vaadin/ui/AbstractJavaScriptComponent.java @@ -10,6 +10,13 @@ public class AbstractJavaScriptComponent extends AbstractComponent { private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( this); + @Override + protected void registerRpc(T implementation, + java.lang.Class rpcInterfaceType) { + super.registerRpc(implementation, rpcInterfaceType); + callbackHelper.registerRpc(rpcInterfaceType); + } + protected void registerCallback(String functionName, JavaScriptCallback javascriptCallback) { callbackHelper.registerCallback(functionName, javascriptCallback); -- cgit v1.2.3 From f505d99754fa0139fbbe1b52236ad622c8c3246d Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Wed, 20 Jun 2012 14:36:21 +0300 Subject: Javadocs and minor tweaks for extensions, JavaScript and js wrappers --- .../vaadin/terminal/AbstractClientConnector.java | 31 ++++- src/com/vaadin/terminal/AbstractExtension.java | 42 ++++++- .../terminal/AbstractJavaScriptExtension.java | 130 +++++++++++++++++++- src/com/vaadin/terminal/Extension.java | 18 ++- .../vaadin/terminal/JavaScriptCallbackHelper.java | 14 +++ .../terminal/gwt/client/JavaScriptExtension.java | 4 +- .../client/ui/JavaScriptComponentConnector.java | 4 +- .../terminal/gwt/server/ClientConnector.java | 56 ++++----- src/com/vaadin/ui/AbstractJavaScriptComponent.java | 136 ++++++++++++++++++++- src/com/vaadin/ui/JavaScript.java | 74 +++++++++++ src/com/vaadin/ui/JavaScriptCallback.java | 27 ++++ 11 files changed, 488 insertions(+), 48 deletions(-) (limited to 'src/com/vaadin/ui/AbstractJavaScriptComponent.java') diff --git a/src/com/vaadin/terminal/AbstractClientConnector.java b/src/com/vaadin/terminal/AbstractClientConnector.java index ee1a2ab981..6a87f58c71 100644 --- a/src/com/vaadin/terminal/AbstractClientConnector.java +++ b/src/com/vaadin/terminal/AbstractClientConnector.java @@ -31,7 +31,12 @@ import com.vaadin.ui.HasComponents; import com.vaadin.ui.Root; /** - * + * An abstract base class for ClientConnector implementations. This class + * provides all the basic functionality required for connectors. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 */ public abstract class AbstractClientConnector implements ClientConnector { /** @@ -115,6 +120,7 @@ public abstract class AbstractClientConnector implements ClientConnector { throw new RuntimeException( "Use registerRpc(T implementation, Class rpcInterfaceType) if the Rpc implementation implements more than one interface"); } + @SuppressWarnings("unchecked") Class type = (Class) interfaces[0]; registerRpc(implementation, type); } @@ -371,6 +377,14 @@ public abstract class AbstractClientConnector implements ClientConnector { } } + /** + * Get an Iterable for iterating over all child connectors, including both + * extensions and child components. + * + * @param connector + * the connector to get children for + * @return an Iterable giving all child connectors. + */ public static Iterable getAllChildrenIterable( final ClientConnector connector) { return new AllChildrenIterable(connector); @@ -380,6 +394,13 @@ public abstract class AbstractClientConnector implements ClientConnector { return Collections.unmodifiableCollection(extensions); } + /** + * Add an extension to this connector. This method is protected to allow + * extensions to select which targets they can extend. + * + * @param extension + * the extension to add + */ protected void addExtension(Extension extension) { ClientConnector previousParent = extension.getParent(); if (previousParent == this) { @@ -439,6 +460,14 @@ public abstract class AbstractClientConnector implements ClientConnector { } } + /** + * {@inheritDoc} + * + *

+ * The {@link #getApplication()} and {@link #getRoot()} methods might return + * null after this method is called. + *

+ */ public void detach() { for (ClientConnector connector : getAllChildrenIterable(this)) { connector.detach(); diff --git a/src/com/vaadin/terminal/AbstractExtension.java b/src/com/vaadin/terminal/AbstractExtension.java index 6753d6f929..33a60e39ef 100644 --- a/src/com/vaadin/terminal/AbstractExtension.java +++ b/src/com/vaadin/terminal/AbstractExtension.java @@ -6,19 +6,51 @@ package com.vaadin.terminal; import com.vaadin.terminal.gwt.server.ClientConnector; +/** + * An extension is an entity that is attached to a Component or another + * Extension and independently communicates between client and server. + *

+ * Extensions can use shared state and RPC in the same way as components. + *

+ * AbstractExtension adds a mechanism for adding the extension to any Connector + * (extend). To let the Extension determine what kind target it can be added to, + * the extend method is declared as protected. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ public abstract class AbstractExtension extends AbstractClientConnector implements Extension { private boolean previouslyAttached = false; - protected Class getAcceptedParentType() { + /** + * Gets a type that the parent must be an instance of. Override this if the + * extension only support certain targets, e.g. if only TextFields can be + * extended. + * + * @return a type that the parent must be an instance of + */ + protected Class getSupportedParentType() { return ClientConnector.class; } + /** + * Add this extension to the target connector. This method is protected to + * allow subclasses to require a more specific type of target. + * + * @param target + * the connector to attach this extension to + */ protected void extend(AbstractClientConnector target) { target.addExtension(this); } - protected void removeFromTarget() { + /** + * Remove this extension from its target. After an extension has been + * removed, it can not be attached again. + */ + public void removeFromTarget() { getParent().removeExtension(this); } @@ -29,14 +61,14 @@ public abstract class AbstractExtension extends AbstractClientConnector "An extension can not be set to extend a new target after getting detached from the previous."); } - Class acceptedParentType = getAcceptedParentType(); - if (parent == null || acceptedParentType.isInstance(parent)) { + Class supportedParentType = getSupportedParentType(); + if (parent == null || supportedParentType.isInstance(parent)) { super.setParent(parent); previouslyAttached = true; } else { throw new IllegalArgumentException(getClass().getName() + " can only be attached to targets of type " - + acceptedParentType.getName() + " but attach to " + + supportedParentType.getName() + " but attach to " + parent.getClass().getName() + " was attempted."); } } diff --git a/src/com/vaadin/terminal/AbstractJavaScriptExtension.java b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java index a2f8019858..bdcd948c74 100644 --- a/src/com/vaadin/terminal/AbstractJavaScriptExtension.java +++ b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java @@ -7,22 +7,146 @@ package com.vaadin.terminal; import com.vaadin.terminal.gwt.client.JavaScriptExtensionState; import com.vaadin.ui.JavaScriptCallback; -public class AbstractJavaScriptExtension extends AbstractExtension { +/** + * Base class for Extensions with all client-side logic implemented using + * JavaScript. + *

+ * When a new JavaScript extension is initialized in the browser, the framework + * will look for a globally defined JavaScript function that will initialize the + * extension. The name of the initialization function is formed by replacing . + * with _ in the name of the server-side class. If no such function is defined, + * each super class is used in turn until a match is found. The framework will + * thus first attempt with com_example_MyExtension for the + * server-side + * com.example.MyExtension extends AbstractJavaScriptExtension + * class. If MyExtension instead extends com.example.SuperExtension + * , then com_example_SuperExtension will also be attempted if + * com_example_MyExtension has not been defined. + *

+ * + * The initialization function will be called with this pointing to + * a connector wrapper object providing integration to Vaadin with the following + * functions: + *

    + *
  • getConnectorId() - returns a string with the id of the + * connector.
  • + *
  • getParentId([connectorId]) - returns a string with the id of + * the connector's parent. If connectorId is provided, the id of + * the parent of the corresponding connector with the passed id is returned + * instead.
  • + *
  • getWidgetElement([connectorId]) - returns the DOM Element + * that is the root of a connector's widget. null is returned if + * the connector can not be found or if the connector doesn't have a widget. If + * connectorId is not provided, the connector id of the current + * connector will be used.
  • + *
  • getState() - returns an object corresponding to the shared + * state defined on the server. The scheme for conversion between Java and + * JavaScript types is described bellow.
  • + *
  • registerRpc([name, ] rpcObject) - registers the + * rpcObject as a RPC handler. rpcObject should be an + * object with field containing functions for all eligible RPC functions. If + * name is provided, the RPC handler will only used for RPC calls + * for the RPC interface with the same fully qualified Java name. If no + * name is provided, the RPC handler will be used for all incoming + * RPC invocations where the RPC method name is defined as a function field in + * the handler. The scheme for conversion between Java types in the RPC + * interface definition and the JavaScript values passed as arguments to the + * handler functions is described bellow.
  • + *
  • getRpcProxy([name]) - returns an RPC proxy object. If + * name is provided, the proxy object will contain functions for + * all methods in the RPC interface with the same fully qualified name, provided + * a RPC handler has been registered by the server-side code. If no + * name is provided, the returned RPC proxy object will contain + * functions for all methods in all RPC interfaces registered for the connector + * on the server. If the same method name is present in multiple registered RPC + * interfaces, the corresponding function in the RPC proxy object will throw an + * exception when called. The scheme for conversion between Java types in the + * RPC interface and the JavaScript values that should be passed to the + * functions is described bellow.
  • + *
+ * The connector wrapper also supports these special functions: + *
    + *
  • onStateChange - If the JavaScript code assigns a function to + * the field, that function is called whenever the contents of the shared state + * is changed.
  • + *
  • Any field name corresponding to a call to + * {@link #registerCallback(String, JavaScriptCallback)} on the server will + * automatically be present as a function that triggers the registered callback + * on the server.
  • + *
  • Any field name referred to using + * {@link #invokeCallback(String, Object...)} on the server will be called if a + * function has been assigned to the field.
  • + *
+ *

+ * + * Values in the Shared State and in RPC calls are converted between Java and + * JavaScript using the following conventions: + *

    + *
  • Primitive Java numbers (byte, char, int, long, float, double) and their + * boxed types (Byte, Character, Integer, Long, Float, Double) are represented + * by JavaScript numbers.
  • + *
  • The primitive Java boolean and the boxed Boolean are represented by + * JavaScript booleans.
  • + *
  • Java Strings are represented by JavaScript strings.
  • + *
  • List, Set and all arrays in Java are represented by JavaScript arrays.
  • + *
  • Map in Java is represented by JavaScript object with fields + * corresponding to the map keys.
  • + *
  • Any other Java Map is represented by a JavaScript array containing two + * arrays, the first contains the keys and the second contains the values in the + * same order.
  • + *
  • A Java Bean is represented by a JavaScript object with fields + * corresponding to the bean's properties.
  • + *
  • A Java Connector is represented by a JavaScript string containing the + * connector's id.
  • + *
  • A pluggable serialization mechanism is provided for types not described + * here. Please refer to the documentation for specific types for serialization + * information.
  • + *
+ * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ +public abstract class AbstractJavaScriptExtension extends AbstractExtension { private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( this); @Override - protected void registerRpc(T implementation, - java.lang.Class rpcInterfaceType) { + protected void registerRpc(T implementation, Class rpcInterfaceType) { super.registerRpc(implementation, rpcInterfaceType); callbackHelper.registerRpc(rpcInterfaceType); } + /** + * Register a {@link JavaScriptCallback} that can be called from the + * JavaScript using the provided name. A JavaScript function with the + * provided name will be added to the connector wrapper object (initially + * available as this). Calling that JavaScript function will + * cause the call method in the registered {@link JavaScriptCallback} to be + * invoked with the same arguments. + * + * @param functionName + * the name that should be used for client-side callback + * @param javaScriptCallback + * the callback object that will be invoked when the JavaScript + * function is called + */ protected void registerCallback(String functionName, JavaScriptCallback javaScriptCallback) { callbackHelper.registerCallback(functionName, javaScriptCallback); } + /** + * Invoke a named function that the connector JavaScript has added to the + * JavaScript connector wrapper object. The arguments should only contain + * data types that can be represented in JavaScript, including primitive + * boxing types, arrays, String, List, Set, Map, Connector and JavaBeans. + * + * @param name + * the name of the function + * @param arguments + * function arguments + */ protected void invokeCallback(String name, Object... arguments) { callbackHelper.invokeCallback(name, arguments); } diff --git a/src/com/vaadin/terminal/Extension.java b/src/com/vaadin/terminal/Extension.java index 7769423f0d..ef5bb4cf8d 100644 --- a/src/com/vaadin/terminal/Extension.java +++ b/src/com/vaadin/terminal/Extension.java @@ -6,6 +6,22 @@ package com.vaadin.terminal; import com.vaadin.terminal.gwt.server.ClientConnector; +/** + * An extension is an entity that is attached to a Component or another + * Extension and independently communicates between client and server. + *

+ * An extension can only be attached once. It is not supported to move an + * extension from one target to another. + *

+ * Extensions can use shared state and RPC in the same way as components. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ public interface Extension extends ClientConnector { - + /* + * Currently just an empty marker interface to distinguish between + * extensions and other connectors, e.g. components + */ } diff --git a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java index 2da64d7b10..01db0267d9 100644 --- a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java +++ b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java @@ -14,11 +14,25 @@ import java.util.Set; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper; import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.AbstractJavaScriptComponent; import com.vaadin.ui.JavaScript.JavaScriptCallbackRpc; import com.vaadin.ui.JavaScriptCallback; +/** + * Internal helper class used to implement functionality common to + * {@link AbstractJavaScriptComponent} and {@link AbstractJavaScriptExtension}. + * Corresponding support in client-side code is in + * {@link JavaScriptConnectorHelper}. + *

+ * You should most likely no use this class directly. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ public class JavaScriptCallbackHelper implements Serializable { private static final Method CALL_METHOD = ReflectTools.findMethod( diff --git a/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java index 61c50dbcb4..2a97e4a770 100644 --- a/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java @@ -10,8 +10,8 @@ import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; import com.vaadin.terminal.gwt.client.ui.Connect; @Connect(AbstractJavaScriptExtension.class) -public class JavaScriptExtension extends AbstractExtensionConnector implements - HasJavaScriptConnectorHelper { +public final class JavaScriptExtension extends AbstractExtensionConnector + implements HasJavaScriptConnectorHelper { private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( this); diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java index ab5280fee9..bb062a6677 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java @@ -8,8 +8,8 @@ import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper import com.vaadin.ui.AbstractJavaScriptComponent; @Connect(AbstractJavaScriptComponent.class) -public class JavaScriptComponentConnector extends AbstractComponentConnector - implements HasJavaScriptConnectorHelper { +public final class JavaScriptComponentConnector extends + AbstractComponentConnector implements HasJavaScriptConnectorHelper { private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( this) { diff --git a/src/com/vaadin/terminal/gwt/server/ClientConnector.java b/src/com/vaadin/terminal/gwt/server/ClientConnector.java index 860627a352..dfdd58879d 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientConnector.java +++ b/src/com/vaadin/terminal/gwt/server/ClientConnector.java @@ -12,7 +12,6 @@ import com.vaadin.terminal.gwt.client.Connector; import com.vaadin.terminal.gwt.client.communication.SharedState; import com.vaadin.ui.Component; import com.vaadin.ui.ComponentContainer; -import com.vaadin.ui.Root; /** * Interface implemented by all connectors that are capable of communicating @@ -30,8 +29,6 @@ public interface ClientConnector extends Connector, RpcTarget { * * @return an unmodifiable ordered list of pending server to client method * calls (not null) - * - * @since 7.0 */ public List retrievePendingRpcCalls(); @@ -61,7 +58,7 @@ public interface ClientConnector extends Connector, RpcTarget { * Causes a repaint of this connector, and all connectors below it. * * This should only be used in special cases, e.g when the state of a - * descendant depends on the state of a ancestor. + * descendant depends on the state of an ancestor. */ public void requestRepaintAll(); @@ -69,18 +66,18 @@ public interface ClientConnector extends Connector, RpcTarget { * Sets the parent connector of the connector. * *

- * This method automatically calls {@link #attach()} if the parent becomes - * attached to the application, regardless of whether it was attached - * previously. Conversely, if the parent is {@code null} and the connector - * is attached to the application, {@link #detach()} is called for the - * connector. + * This method automatically calls {@link #attach()} if the connector + * becomes attached to the application, regardless of whether it was + * attached previously. Conversely, if the parent is {@code null} and the + * connector is attached to the application, {@link #detach()} is called for + * the connector. *

*

* This method is rarely called directly. One of the * {@link ComponentContainer#addComponent(Component)} or - * {@link AbstractClientConnector#addFeature(Feature)} methods is normally - * used for adding connectors to a container and it will call this method - * implicitly. + * {@link AbstractClientConnector#addExtension(Extension)} methods are + * normally used for adding connectors to a parent and they will call this + * method implicitly. *

* *

@@ -103,15 +100,13 @@ public interface ClientConnector extends Connector, RpcTarget { * The caller of this method is {@link #setParent(ClientConnector)} if the * parent is itself already attached to the application. If not, the parent * will call the {@link #attach()} for all its children when it is attached - * to the application. This method is always called before the connector is - * painted for the first time. + * to the application. This method is always called before the connector's + * data is sent to the client-side for the first time. *

* *

* The attachment logic is implemented in {@link AbstractClientConnector}. *

- * - * @see #getApplication() */ public void attach(); @@ -119,25 +114,26 @@ public interface ClientConnector extends Connector, RpcTarget { * Notifies the component that it is detached from the application. * *

- * The {@link #getApplication()} and {@link #getRoot()} methods might return - * null after this method is called. - *

- * - *

- * This method must call {@link Root#componentDetached(Component)} to let - * the Root know that a new Component has been attached. - *

- * * - *

- * The caller of this method is {@link #setParent(Component)} if the parent - * is in the application. When the parent is detached from the application - * it is its response to call {@link #detach()} for all the children and to - * detach itself from the terminal. + * The caller of this method is {@link #setParent(ClientConnector)} if the + * parent is in the application. When the parent is detached from the + * application it is its response to call {@link #detach()} for all the + * children and to detach itself from the terminal. *

*/ public void detach(); + /** + * Get a read-only collection of all extensions attached to this connector. + * + * @return a collection of extensions + */ public Collection getExtensions(); + /** + * Remove an extension from this connector. + * + * @param extension + * the extension to remove. + */ public void removeExtension(Extension extension); } diff --git a/src/com/vaadin/ui/AbstractJavaScriptComponent.java b/src/com/vaadin/ui/AbstractJavaScriptComponent.java index 343caf4abb..95c45f55f9 100644 --- a/src/com/vaadin/ui/AbstractJavaScriptComponent.java +++ b/src/com/vaadin/ui/AbstractJavaScriptComponent.java @@ -5,23 +5,151 @@ package com.vaadin.ui; import com.vaadin.terminal.JavaScriptCallbackHelper; import com.vaadin.terminal.gwt.client.ui.JavaScriptComponentState; +import com.vaadin.terminal.gwt.client.ui.JavaScriptWidget; +/** + * Base class for Components with all client-side logic implemented using + * JavaScript. + *

+ * When a new JavaScript component is initialized in the browser, the framework + * will look for a globally defined JavaScript function that will initialize the + * component. The name of the initialization function is formed by replacing . + * with _ in the name of the server-side class. If no such function is defined, + * each super class is used in turn until a match is found. The framework will + * thus first attempt with com_example_MyComponent for the + * server-side + * com.example.MyComponent extends AbstractJavaScriptComponent + * class. If MyComponent instead extends com.example.SuperComponent + * , then com_example_SuperComponent will also be attempted if + * com_example_MyComponent has not been defined. + *

+ * JavaScript components have a very simple GWT widget ({@link JavaScriptWidget} + * ) just consisting of a div element to which the JavaScript code + * should initialize its own user interface. + *

+ * The initialization function will be called with this pointing to + * a connector wrapper object providing integration to Vaadin with the following + * functions: + *

    + *
  • getConnectorId() - returns a string with the id of the + * connector.
  • + *
  • getParentId([connectorId]) - returns a string with the id of + * the connector's parent. If connectorId is provided, the id of + * the parent of the corresponding connector with the passed id is returned + * instead.
  • + *
  • getWidgetElement([connectorId]) - returns the DOM Element + * that is the root of a connector's widget. null is returned if + * the connector can not be found or if the connector doesn't have a widget. If + * connectorId is not provided, the connector id of the current + * connector will be used.
  • + *
  • getState() - returns an object corresponding to the shared + * state defined on the server. The scheme for conversion between Java and + * JavaScript types is described bellow.
  • + *
  • registerRpc([name, ] rpcObject) - registers the + * rpcObject as a RPC handler. rpcObject should be an + * object with field containing functions for all eligible RPC functions. If + * name is provided, the RPC handler will only used for RPC calls + * for the RPC interface with the same fully qualified Java name. If no + * name is provided, the RPC handler will be used for all incoming + * RPC invocations where the RPC method name is defined as a function field in + * the handler. The scheme for conversion between Java types in the RPC + * interface definition and the JavaScript values passed as arguments to the + * handler functions is described bellow.
  • + *
  • getRpcProxy([name]) - returns an RPC proxy object. If + * name is provided, the proxy object will contain functions for + * all methods in the RPC interface with the same fully qualified name, provided + * a RPC handler has been registered by the server-side code. If no + * name is provided, the returned RPC proxy object will contain + * functions for all methods in all RPC interfaces registered for the connector + * on the server. If the same method name is present in multiple registered RPC + * interfaces, the corresponding function in the RPC proxy object will throw an + * exception when called. The scheme for conversion between Java types in the + * RPC interface and the JavaScript values that should be passed to the + * functions is described bellow.
  • + *
+ * The connector wrapper also supports these special functions: + *
    + *
  • onStateChange - If the JavaScript code assigns a function to + * the field, that function is called whenever the contents of the shared state + * is changed.
  • + *
  • Any field name corresponding to a call to + * {@link #registerCallback(String, JavaScriptCallback)} on the server will + * automatically be present as a function that triggers the registered callback + * on the server.
  • + *
  • Any field name referred to using + * {@link #invokeCallback(String, Object...)} on the server will be called if a + * function has been assigned to the field.
  • + *
+ *

+ * + * Values in the Shared State and in RPC calls are converted between Java and + * JavaScript using the following conventions: + *

    + *
  • Primitive Java numbers (byte, char, int, long, float, double) and their + * boxed types (Byte, Character, Integer, Long, Float, Double) are represented + * by JavaScript numbers.
  • + *
  • The primitive Java boolean and the boxed Boolean are represented by + * JavaScript booleans.
  • + *
  • Java Strings are represented by JavaScript strings.
  • + *
  • List, Set and all arrays in Java are represented by JavaScript arrays.
  • + *
  • Map in Java is represented by JavaScript object with fields + * corresponding to the map keys.
  • + *
  • Any other Java Map is represented by a JavaScript array containing two + * arrays, the first contains the keys and the second contains the values in the + * same order.
  • + *
  • A Java Bean is represented by a JavaScript object with fields + * corresponding to the bean's properties.
  • + *
  • A Java Connector is represented by a JavaScript string containing the + * connector's id.
  • + *
  • A pluggable serialization mechanism is provided for types not described + * here. Please refer to the documentation for specific types for serialization + * information.
  • + *
+ * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ public class AbstractJavaScriptComponent extends AbstractComponent { private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( this); @Override - protected void registerRpc(T implementation, - java.lang.Class rpcInterfaceType) { + protected void registerRpc(T implementation, Class rpcInterfaceType) { super.registerRpc(implementation, rpcInterfaceType); callbackHelper.registerRpc(rpcInterfaceType); } + /** + * Register a {@link JavaScriptCallback} that can be called from the + * JavaScript using the provided name. A JavaScript function with the + * provided name will be added to the connector wrapper object (initially + * available as this). Calling that JavaScript function will + * cause the call method in the registered {@link JavaScriptCallback} to be + * invoked with the same arguments. + * + * @param functionName + * the name that should be used for client-side callback + * @param javaScriptCallback + * the callback object that will be invoked when the JavaScript + * function is called + */ protected void registerCallback(String functionName, - JavaScriptCallback javascriptCallback) { - callbackHelper.registerCallback(functionName, javascriptCallback); + JavaScriptCallback javaScriptCallback) { + callbackHelper.registerCallback(functionName, javaScriptCallback); } + /** + * Invoke a named function that the connector JavaScript has added to the + * JavaScript connector wrapper object. The arguments should only contain + * data types that can be represented in JavaScript, including primitive + * boxing types, arrays, String, List, Set, Map, Connector and JavaBeans. + * + * @param name + * the name of the function + * @param arguments + * function arguments + */ protected void invokeCallback(String name, Object... arguments) { callbackHelper.invokeCallback(name, arguments); } diff --git a/src/com/vaadin/ui/JavaScript.java b/src/com/vaadin/ui/JavaScript.java index 241d477506..d256717711 100644 --- a/src/com/vaadin/ui/JavaScript.java +++ b/src/com/vaadin/ui/JavaScript.java @@ -15,6 +15,16 @@ import com.vaadin.terminal.gwt.client.communication.ServerRpc; import com.vaadin.terminal.gwt.client.extensions.javascriptmanager.ExecuteJavaScriptRpc; import com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavaScriptManagerState; +/** + * Provides access to JavaScript functionality in the web browser. To get an + * instance of JavaScript, either use Page.getJavaScript() or + * JavaScript.getCurrent() as a shorthand for getting the JavaScript object + * corresponding to the current Page. + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ public class JavaScript extends AbstractExtension { private Map callbacks = new HashMap(); @@ -24,6 +34,11 @@ public class JavaScript extends AbstractExtension { public void call(String name, JSONArray arguments); } + /** + * Creates a new JavaScript object. You should typically not this, but + * instead use the JavaScript object already associated with your Page + * object. + */ public JavaScript() { registerRpc(new JavaScriptCallbackRpc() { public void call(String name, JSONArray arguments) { @@ -43,6 +58,27 @@ public class JavaScript extends AbstractExtension { return (JavaScriptManagerState) super.getState(); } + /** + * Add a new function to the global JavaScript namespace (i.e. the window + * object). The call method in the passed + * {@link JavaScriptCallback} object will be invoked with the same + * parameters whenever the JavaScript function is called in the browser. + * + * A callback added with the name "myCallback" can thus be + * invoked with the following JavaScript code: + * window.myCallback(argument1, argument2). + * + * If the name parameter contains dots, simple objects are created on demand + * to allow calling the function using the same name (e.g. + * window.myObject.myFunction). + * + * @param name + * the name that the callback function should get in the global + * JavaScript namespace. + * @param callback + * the JavaScriptCallback that will be invoked if the JavaScript + * function is called. + */ public void addCallback(String name, JavaScriptCallback callback) { callbacks.put(name, callback); if (getState().getNames().add(name)) { @@ -50,6 +86,17 @@ public class JavaScript extends AbstractExtension { } } + /** + * Removes a JavaScripCallback from the browser's global JavaScript + * namespace. + * + * If the name contains dots and intermediate were created by + * {@link #addCallback(String, JavaScriptCallback)}addCallback, these + * objects will not be removed when the callback is removed. + * + * @param name + * the name of the callback to remove + */ public void removeCallback(String name) { callbacks.remove(name); if (getState().getNames().remove(name)) { @@ -57,10 +104,25 @@ public class JavaScript extends AbstractExtension { } } + /** + * Executes the given JavaScript code in the browser. + * + * @param script + * The JavaScript code to run. + */ public void execute(String script) { getRpcProxy(ExecuteJavaScriptRpc.class).executeJavaScript(script); } + /** + * Get the JavaScript object for the current Page, or null if there is no + * current page. + * + * @see Page#getCurrent() + * + * @return the JavaScript object corresponding to the current Page, or + * null if there is no current page. + */ public static JavaScript getCurrent() { Page page = Page.getCurrent(); if (page == null) { @@ -69,4 +131,16 @@ public class JavaScript extends AbstractExtension { return page.getJavaScript(); } + /** + * JavaScript is not designed to be removed. + * + * @throws UnsupportedOperationException + * when invoked + */ + @Override + public void removeFromTarget() { + throw new UnsupportedOperationException( + "JavaScript is not designed to be removed."); + } + } diff --git a/src/com/vaadin/ui/JavaScriptCallback.java b/src/com/vaadin/ui/JavaScriptCallback.java index e6b16010e4..49f7695e89 100644 --- a/src/com/vaadin/ui/JavaScriptCallback.java +++ b/src/com/vaadin/ui/JavaScriptCallback.java @@ -8,7 +8,34 @@ import java.io.Serializable; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.AbstractJavaScriptExtension; +/** + * Defines a method that is called by a client-side JavaScript function. When + * the corresponding JavaScript function is called, the {@link #call(JSONArray)} + * method is invoked. + * + * @see JavaScript#addCallback(String, JavaScriptCallback) + * @see AbstractJavaScriptComponent#registerCallback(String, JavaScriptCallback) + * @see AbstractJavaScriptExtension#registerCallback(String, JavaScriptCallback) + * + * @author Vaadin Ltd + * @version @VERSION@ + * @since 7.0.0 + */ public interface JavaScriptCallback extends Serializable { + /** + * Invoked whenever the corresponding JavaScript function is called in the + * browser. + *

+ * Because of the asynchronous nature of the communication between client + * and server, no return value can be sent back to the browser. + * + * @param arguments + * an array with JSON representations of the arguments with which + * the JavaScript function was called. + * @throws JSONException + * if the arguments can not be interpreted + */ public void call(JSONArray arguments) throws JSONException; } -- cgit v1.2.3