From: Leif Åstrand Date: Mon, 11 Jun 2012 18:52:26 +0000 (+0300) Subject: Initial javascript callback support (#6730) X-Git-Tag: 7.0.0.alpha3~116^2~12 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=9b757f047fc8450d8a7d3d7aa5a7b7e43bfa1312;p=vaadin-framework.git Initial javascript callback support (#6730) --- diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java new file mode 100644 index 0000000000..979a6d22c4 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java @@ -0,0 +1,78 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import java.util.HashSet; +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.ui.AbstractConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; +import com.vaadin.ui.JavascriptManager; + +@Connect(JavascriptManager.class) +public class JavascriptManagerConnector extends AbstractConnector { + private Set currentNames = new HashSet(); + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + Set newNames = getState().getNames(); + + // Current names now only contains orphan callbacks + currentNames.removeAll(newNames); + + for (String name : currentNames) { + removeCallback(name); + } + + currentNames = new HashSet(newNames); + for (String name : newNames) { + addCallback(name); + } + } + + // TODO Ensure we don't overwrite anything (important) in $wnd + private native void addCallback(String name) + /*-{ + var m = this; + $wnd[name] = $entry(function() { + //Must make a copy because arguments is an array-like object (not instanceof Array), causing suboptimal JSON encoding + var args = Array.prototype.slice.call(arguments, 0); + m.@com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavascriptManagerConnector::sendRpc(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); + }); + }-*/; + + // TODO only remove what we actually added + private native void removeCallback(String name) + /*-{ + delete $wnd[name]; + }-*/; + + public void sendRpc(String name, JsArray arguments) { + Object[] parameters = new Object[] { name, new JSONArray(arguments) }; + + /* + * Must invoke manually as the RPC interface can't be used in GWT + * because of the JSONArray parameter + */ + getConnection() + .addMethodInvocationToQueue( + new MethodInvocation( + getConnectorId(), + "com.vaadin.ui.JavascriptManager$JavascriptCallbackRpc", + "call", parameters), true); + } + + @Override + public JavascriptManagerState getState() { + return (JavascriptManagerState) super.getState(); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java new file mode 100644 index 0000000000..77794ffdca --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java @@ -0,0 +1,22 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import java.util.HashSet; +import java.util.Set; + +import com.vaadin.terminal.gwt.client.communication.SharedState; + +public class JavascriptManagerState extends SharedState { + private Set names = new HashSet(); + + public Set getNames() { + return names; + } + + public void setNames(Set names) { + this.names = names; + } +} diff --git a/src/com/vaadin/terminal/gwt/server/JsonCodec.java b/src/com/vaadin/terminal/gwt/server/JsonCodec.java index fb2d289484..b693514a66 100644 --- a/src/com/vaadin/terminal/gwt/server/JsonCodec.java +++ b/src/com/vaadin/terminal/gwt/server/JsonCodec.java @@ -123,6 +123,9 @@ public class JsonCodec implements Serializable { // Try to decode object using fields if (value == JSONObject.NULL) { return null; + } else if (targetType == JSONObject.class + || targetType == JSONArray.class) { + return value; } else { return decodeObject(targetType, (JSONObject) value, application); } diff --git a/src/com/vaadin/ui/JavascriptCallback.java b/src/com/vaadin/ui/JavascriptCallback.java new file mode 100644 index 0000000000..4a34167256 --- /dev/null +++ b/src/com/vaadin/ui/JavascriptCallback.java @@ -0,0 +1,13 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.io.Serializable; + +import com.vaadin.external.json.JSONArray; + +public interface JavascriptCallback extends Serializable { + public void call(JSONArray arguments); +} diff --git a/src/com/vaadin/ui/JavascriptManager.java b/src/com/vaadin/ui/JavascriptManager.java new file mode 100644 index 0000000000..e52962f177 --- /dev/null +++ b/src/com/vaadin/ui/JavascriptManager.java @@ -0,0 +1,53 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.terminal.AbstractExtension; +import com.vaadin.terminal.gwt.client.communication.ServerRpc; +import com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavascriptManagerState; + +public class JavascriptManager extends AbstractExtension { + private Map callbacks = new HashMap(); + + // Can not be defined in client package as this JSONArray is not available + // in GWT + public interface JavascriptCallbackRpc extends ServerRpc { + public void call(String name, JSONArray arguments); + } + + public JavascriptManager() { + registerRpc(new JavascriptCallbackRpc() { + public void call(String name, JSONArray arguments) { + JavascriptCallback callback = callbacks.get(name); + // TODO error handling + callback.call(arguments); + } + }); + } + + @Override + public JavascriptManagerState getState() { + return (JavascriptManagerState) super.getState(); + } + + public void addCallback(String name, JavascriptCallback javascriptCallback) { + callbacks.put(name, javascriptCallback); + if (getState().getNames().add(name)) { + requestRepaint(); + } + } + + public void removeCallback(String name) { + callbacks.remove(name); + if (getState().getNames().remove(name)) { + requestRepaint(); + } + } + +} diff --git a/src/com/vaadin/ui/Root.java b/src/com/vaadin/ui/Root.java index 8792bf1912..9814084cbc 100644 --- a/src/com/vaadin/ui/Root.java +++ b/src/com/vaadin/ui/Root.java @@ -408,6 +408,8 @@ public abstract class Root extends AbstractComponentContainer implements private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( this); + private JavascriptManager javascriptManager; + private RootServerRpc rpc = new RootServerRpc() { public void click(MouseEventDetails mouseDetails) { fireEvent(new ClickEvent(Root.this, mouseDetails)); @@ -1590,4 +1592,14 @@ public abstract class Root extends AbstractComponentContainer implements return dirtyConnectorTracker; } + public JavascriptManager getJavascriptManager() { + if (javascriptManager == null) { + // Create and attach on first use + javascriptManager = new JavascriptManager(); + addExtension(javascriptManager); + } + + return javascriptManager; + } + } diff --git a/tests/testbench/com/vaadin/tests/features/JavascriptManagerTest.html b/tests/testbench/com/vaadin/tests/features/JavascriptManagerTest.html new file mode 100644 index 0000000000..1ec959abce --- /dev/null +++ b/tests/testbench/com/vaadin/tests/features/JavascriptManagerTest.html @@ -0,0 +1,47 @@ + + + + + + +New Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
New Test
open/run/com.vaadin.tests.features.JavascriptManagerTest?restartApplication
assertTextvaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[4]1. Got 4 arguments
assertTextvaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[3]2. Argument 1 as a number: 42
assertTextvaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[2]3. Argument 2 as a string: text
assertTextvaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[1]4. Argument 3.p as a boolean: true
assertTextvaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[0]5. Argument 4 is JSONObject.NULL: true
+ + diff --git a/tests/testbench/com/vaadin/tests/features/JavascriptManagerTest.java b/tests/testbench/com/vaadin/tests/features/JavascriptManagerTest.java new file mode 100644 index 0000000000..18abf9f8b8 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/features/JavascriptManagerTest.java @@ -0,0 +1,51 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.tests.features; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.external.json.JSONObject; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.tests.util.Log; +import com.vaadin.ui.JavascriptCallback; + +public class JavascriptManagerTest extends AbstractTestRoot { + + private Log log = new Log(5); + + @Override + protected void setup(WrappedRequest request) { + addComponent(log); + getJavascriptManager().addCallback("testing", new JavascriptCallback() { + public void call(JSONArray arguments) { + try { + log.log("Got " + arguments.length() + " arguments"); + log.log("Argument 1 as a number: " + arguments.getInt(0)); + log.log("Argument 2 as a string: " + arguments.getString(1)); + log.log("Argument 3.p as a boolean: " + + arguments.getJSONObject(2).getBoolean("p")); + log.log("Argument 4 is JSONObject.NULL: " + + (arguments.get(3) == JSONObject.NULL)); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + }); + executeJavaScript("window.testing(42, 'text', {p: true}, null)"); + } + + @Override + protected String getTestDescription() { + return "Test javascript callback handling by adding a callback and invoking the javascript."; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +}