diff options
38 files changed, 913 insertions, 560 deletions
diff --git a/WebContent/statictestfiles/jsconnector.js b/WebContent/statictestfiles/jsconnector.js index db8a065e86..d7f697dbf5 100644 --- a/WebContent/statictestfiles/jsconnector.js +++ b/WebContent/statictestfiles/jsconnector.js @@ -1,10 +1,11 @@ -window.com_vaadin_tests_components_javascriptcomponent_BasicJavascriptComponent_ExampleWidget = function() { +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.getRpcProxy().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 df67db8927..1a46300e45 100644 --- a/WebContent/statictestfiles/jsextension.js +++ b/WebContent/statictestfiles/jsextension.js @@ -1,13 +1,24 @@ -window.com_vaadin_tests_features_SimpleJavascriptExtensionTest_SimpleJavascriptExtension = function() { +window.com_vaadin_tests_extensions_SimpleJavaScriptExtensionTest_SimpleJavascriptExtension = function() { + var self = this; var state = this.getState(); - var greetBack = this.getRpcProxyFunction('com.vaadin.tests.features.SimpleJavascriptExtensionTest$SimpleJavascriptExtensionServerRpc', 'greet'); + + //var rpc = this.getRpcProxy("com.vaadin.tests.extensions.SimpleJavaScriptExtensionTest.SimpleJavaScriptExtensionServerRpc"); + var rpc = this.getRpcProxy(); - this.registerRpc("com.vaadin.tests.features.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); } } }); + + 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/AbstractClientConnector.java b/src/com/vaadin/terminal/AbstractClientConnector.java index a2bf4a0be8..ee1a2ab981 100644 --- a/src/com/vaadin/terminal/AbstractClientConnector.java +++ b/src/com/vaadin/terminal/AbstractClientConnector.java @@ -210,7 +210,7 @@ public abstract class AbstractClientConnector implements ClientConnector { public Iterator<ClientConnector> iterator() { CombinedIterator<ClientConnector> iterator = new CombinedIterator<ClientConnector>(); - iterator.addIterator(connector.getExtensionIterator()); + iterator.addIterator(connector.getExtensions().iterator()); if (connector instanceof HasComponents) { HasComponents hasComponents = (HasComponents) connector; @@ -376,30 +376,22 @@ public abstract class AbstractClientConnector implements ClientConnector { return new AllChildrenIterable(connector); } - public Iterator<Extension> getExtensionIterator() { - return Collections.unmodifiableList(extensions).iterator(); + public Collection<Extension> getExtensions() { + return Collections.unmodifiableCollection(extensions); } protected void addExtension(Extension extension) { - addExtensionAtIndex(extension, extensions.size()); - } - - protected void addExtensionAtIndex(Extension extension, int index) { ClientConnector previousParent = extension.getParent(); if (previousParent == this) { - int oldIndex = extensions.indexOf(extension); - if (oldIndex < index) { - index--; - } - extensions.remove(oldIndex); - extensions.add(index, extension); - } else { - if (previousParent != null) { - previousParent.removeExtension(extension); - } - extensions.add(index, extension); - extension.setParent(this); + // Nothing to do, already attached + return; + } else if (previousParent != null) { + throw new IllegalStateException( + "Moving an extension from one parent to another is not supported"); } + + extensions.add(extension); + extension.setParent(this); requestRepaint(); } diff --git a/src/com/vaadin/terminal/AbstractExtension.java b/src/com/vaadin/terminal/AbstractExtension.java index 353db85a18..6753d6f929 100644 --- a/src/com/vaadin/terminal/AbstractExtension.java +++ b/src/com/vaadin/terminal/AbstractExtension.java @@ -8,23 +8,34 @@ import com.vaadin.terminal.gwt.server.ClientConnector; public abstract class AbstractExtension extends AbstractClientConnector implements Extension { + private boolean previouslyAttached = false; protected Class<? extends ClientConnector> getAcceptedParentType() { return ClientConnector.class; } - protected void attachTo(AbstractClientConnector parent) { - parent.addExtension(this); + protected void extend(AbstractClientConnector target) { + target.addExtension(this); + } + + protected void removeFromTarget() { + getParent().removeExtension(this); } @Override public void setParent(ClientConnector parent) { + if (previouslyAttached && parent != null) { + throw new IllegalStateException( + "An extension can not be set to extend a new target after getting detached from the previous."); + } + Class<? extends ClientConnector> acceptedParentType = getAcceptedParentType(); if (parent == null || acceptedParentType.isInstance(parent)) { super.setParent(parent); + previouslyAttached = true; } else { throw new IllegalArgumentException(getClass().getName() - + " can only be attached to parents of type " + + " can only be attached to targets of type " + acceptedParentType.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 new file mode 100644 index 0000000000..a2f8019858 --- /dev/null +++ b/src/com/vaadin/terminal/AbstractJavaScriptExtension.java @@ -0,0 +1,34 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import com.vaadin.terminal.gwt.client.JavaScriptExtensionState; +import com.vaadin.ui.JavaScriptCallback; + +public class AbstractJavaScriptExtension extends AbstractExtension { + private JavaScriptCallbackHelper callbackHelper = new JavaScriptCallbackHelper( + this); + + @Override + protected <T> void registerRpc(T implementation, + java.lang.Class<T> rpcInterfaceType) { + super.registerRpc(implementation, rpcInterfaceType); + callbackHelper.registerRpc(rpcInterfaceType); + } + + protected void registerCallback(String functionName, + JavaScriptCallback javaScriptCallback) { + callbackHelper.registerCallback(functionName, javaScriptCallback); + } + + 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/AbstractJavascriptExtension.java b/src/com/vaadin/terminal/AbstractJavascriptExtension.java deleted file mode 100644 index e741e2af1e..0000000000 --- a/src/com/vaadin/terminal/AbstractJavascriptExtension.java +++ /dev/null @@ -1,20 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal; - -import com.vaadin.ui.JavascriptCallback; - -public class AbstractJavascriptExtension extends AbstractExtension { - private JavascriptRpcHelper rpcHelper = new JavascriptRpcHelper(this); - - protected void registerCallback(String functionName, - JavascriptCallback javascriptCallback) { - rpcHelper.registerCallback(functionName, javascriptCallback); - } - - protected void invokeCallback(String name, Object... arguments) { - rpcHelper.invokeCallback(name, arguments); - } -} diff --git a/src/com/vaadin/terminal/JavaScriptCallbackHelper.java b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java new file mode 100644 index 0000000000..2da64d7b10 --- /dev/null +++ b/src/com/vaadin/terminal/JavaScriptCallbackHelper.java @@ -0,0 +1,101 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal; + +import java.io.Serializable; +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; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; +import com.vaadin.tools.ReflectTools; +import com.vaadin.ui.JavaScript.JavaScriptCallbackRpc; +import com.vaadin.ui.JavaScriptCallback; + +public class JavaScriptCallbackHelper implements Serializable { + + private static final Method CALL_METHOD = ReflectTools.findMethod( + JavaScriptCallbackRpc.class, "call", String.class, JSONArray.class); + private AbstractClientConnector connector; + + private Map<String, JavaScriptCallback> callbacks = new HashMap<String, JavaScriptCallback>(); + private JavaScriptCallbackRpc javascriptCallbackRpc; + + public JavaScriptCallbackHelper(AbstractClientConnector connector) { + this.connector = connector; + } + + 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() { + public void call(String name, JSONArray arguments) { + JavaScriptCallback callback = callbacks.get(name); + try { + callback.call(arguments); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + }; + connector.registerRpc(javascriptCallbackRpc); + } + } + + 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, + new Object[] { name, args }); + connector.requestRepaint(); + } + + public void registerRpc(Class<?> rpcInterfaceType) { + if (rpcInterfaceType == JavaScriptCallbackRpc.class) { + // Ignore + return; + } + Map<String, Set<String>> rpcInterfaces = getConnectorState() + .getRpcInterfaces(); + String interfaceName = rpcInterfaceType.getName(); + if (!rpcInterfaces.containsKey(interfaceName)) { + Set<String> methodNames = new HashSet<String>(); + + for (Method method : rpcInterfaceType.getMethods()) { + methodNames.add(method.getName()); + } + + rpcInterfaces.put(interfaceName, methodNames); + connector.requestRepaint(); + } + } + +} diff --git a/src/com/vaadin/terminal/JavascriptRpcHelper.java b/src/com/vaadin/terminal/JavascriptRpcHelper.java deleted file mode 100644 index b566462833..0000000000 --- a/src/com/vaadin/terminal/JavascriptRpcHelper.java +++ /dev/null @@ -1,61 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.terminal; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import com.vaadin.external.json.JSONArray; -import com.vaadin.external.json.JSONException; -import com.vaadin.tools.ReflectTools; -import com.vaadin.ui.JavascriptCallback; -import com.vaadin.ui.JavascriptManager.JavascriptCallbackRpc; - -public class JavascriptRpcHelper { - - private static final Method CALL_METHOD = ReflectTools.findMethod( - JavascriptCallbackRpc.class, "call", String.class, JSONArray.class); - private AbstractClientConnector connector; - - private Map<String, JavascriptCallback> callbacks = new HashMap<String, JavascriptCallback>(); - private JavascriptCallbackRpc javascriptCallbackRpc; - - public JavascriptRpcHelper(AbstractClientConnector connector) { - this.connector = connector; - } - - public void registerCallback(String functionName, - JavascriptCallback javascriptCallback) { - callbacks.put(functionName, javascriptCallback); - ensureRpc(); - } - - private void ensureRpc() { - if (javascriptCallbackRpc == null) { - javascriptCallbackRpc = new JavascriptCallbackRpc() { - public void call(String name, JSONArray arguments) { - JavascriptCallback callback = callbacks.get(name); - try { - callback.call(arguments); - } catch (JSONException e) { - throw new IllegalArgumentException(e); - } - } - }; - connector.registerRpc(javascriptCallbackRpc); - } - } - - public void invokeCallback(String name, Object... arguments) { - JSONArray args = new JSONArray(Arrays.asList(arguments)); - connector.addMethodInvocationToQueue( - JavascriptCallbackRpc.class.getName(), CALL_METHOD, - new Object[] { name, args }); - connector.requestRepaint(); - } - -} diff --git a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java index 7d11c6245a..f0470c8ee8 100644 --- a/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java +++ b/src/com/vaadin/terminal/gwt/client/ApplicationConnection.java @@ -40,7 +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.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; @@ -50,6 +50,7 @@ import com.vaadin.terminal.gwt.client.communication.SharedState; import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; import com.vaadin.terminal.gwt.client.communication.Type; import com.vaadin.terminal.gwt.client.communication.UidlValue; +import com.vaadin.terminal.gwt.client.extensions.AbstractExtensionConnector; import com.vaadin.terminal.gwt.client.ui.AbstractComponentConnector; import com.vaadin.terminal.gwt.client.ui.VContextMenu; import com.vaadin.terminal.gwt.client.ui.dd.VDragAndDropManager; @@ -1442,8 +1443,8 @@ public class ApplicationConnection { JSONObject stateJson = new JSONObject( states.getJavaScriptObject(connectorId)); - if (connector instanceof HasJavascriptConnectorHelper) { - ((HasJavascriptConnectorHelper) connector) + if (connector instanceof HasJavaScriptConnectorHelper) { + ((HasJavaScriptConnectorHelper) connector) .getJavascriptConnectorHelper() .setNativeState( stateJson.getJavaScriptObject()); @@ -1518,6 +1519,10 @@ public class ApplicationConnection { if (childConnector instanceof ComponentConnector) { newComponents .add((ComponentConnector) childConnector); + } else if (!(childConnector instanceof AbstractExtensionConnector)) { + throw new IllegalStateException( + Util.getConnectorString(childConnector) + + " is not a ComponentConnector nor an AbstractExtensionConnector"); } if (childConnector.getParent() != parentConnector) { // Avoid extra calls to setParent 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..d2a2a5329e --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptConnectorHelper.java @@ -0,0 +1,336 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +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; +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<String> getCallbackNames(); + + public Map<String, Set<String>> getRpcInterfaces(); + } + + private final ServerConnector connector; + private final JavaScriptObject nativeState = JavaScriptObject + .createObject(); + private final JavaScriptObject rpcMap = JavaScriptObject.createObject(); + + private final Map<String, JavaScriptObject> rpcObjects = new HashMap<String, JavaScriptObject>(); + private final Map<String, Set<String>> rpcMethods = new HashMap<String, Set<String>>(); + + 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 : state.getCallbackNames()) { + ensureCallback(JavaScriptConnectorHelper.this, wrapper, + callback); + } + + for (Entry<String, Set<String>> entry : state + .getRpcInterfaces().entrySet()) { + String rpcName = entry.getKey(); + String jsName = getJsInterfaceName(rpcName); + if (!rpcObjects.containsKey(jsName)) { + Set<String> methods = entry.getValue(); + rpcObjects.put(jsName, + createRpcObject(rpcName, methods)); + + // Init all methods for wildcard rpc + for (String method : methods) { + JavaScriptObject wildcardRpcObject = rpcObjects + .get(""); + Set<String> interfaces = rpcMethods.get(method); + if (interfaces == null) { + interfaces = new HashSet<String>(); + rpcMethods.put(method, interfaces); + attachRpcMethod(wildcardRpcObject, null, method); + } + interfaces.add(rpcName); + } + } + } + + // Init after setting up callbacks & rpc + if (!inited) { + init(); + inited = true; + } + + fireNativeStateChange(wrapper); + } + }); + } + + private static String getJsInterfaceName(String rpcName) { + return rpcName.replace('$', '.'); + } + + protected JavaScriptObject createRpcObject(String iface, Set<String> methods) { + JavaScriptObject object = JavaScriptObject.createObject(); + + for (String method : methods) { + attachRpcMethod(object, iface, method); + } + + return object; + } + + private boolean init() { + ApplicationConfiguration conf = connector.getConnection() + .getConfiguration(); + ArrayList<String> attemptedNames = new ArrayList<String>(); + 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<String> 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(), rpcObjects); + } + + 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, + Map<String, JavaScriptObject> rpcObjects) + /*-{ + return { + 'getConnectorId': function() { + return connectorId; + }, + 'getState': function() { + return nativeState; + }, + 'getRpcProxy': $entry(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] = []; + } + registeredRpc[iface].push(rpcHandler); + }, + }; + }-*/; + + 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<JavaScriptObject> 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++) { + parameters[i] = argumentsArray.get(i); + } + connector.getConnection().addMethodInvocationToQueue( + new MethodInvocation(connector.getConnectorId(), iface, method, + parameters), true); + } + + private String findWildcardInterface(String method) { + Set<String> 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<JavaScriptObject> 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); + } + + 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) { + 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 { + JavaScriptObject arguments = parametersJson.getJavaScriptObject(); + invokeJsRpc(rpcMap, iface, method, arguments); + // Also invoke wildcard interface + invokeJsRpc(rpcMap, "", method, arguments); + } + } + + 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) + /*-{ + var targets = rpcMap[interfaceName]; + if (!targets) { + return; + } + for(var i = 0; i < targets.length; i++) { + var target = targets[i]; + 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 new file mode 100644 index 0000000000..e3dafab9bd --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtension.java @@ -0,0 +1,26 @@ +/* +@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.extensions.AbstractExtensionConnector; +import com.vaadin.terminal.gwt.client.ui.Connect; + +@Connect(AbstractJavaScriptExtension.class) +public class JavaScriptExtension extends AbstractExtensionConnector implements + HasJavaScriptConnectorHelper { + private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( + this); + + 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..e7bfbc4bb2 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/JavaScriptExtensionState.java @@ -0,0 +1,36 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +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; +import com.vaadin.terminal.gwt.client.communication.SharedState; + +public class JavaScriptExtensionState extends SharedState implements + JavaScriptConnectorState { + + private Set<String> callbackNames = new HashSet<String>(); + private Map<String, Set<String>> rpcInterfaces = new HashMap<String, Set<String>>(); + + public Set<String> getCallbackNames() { + return callbackNames; + } + + public void setCallbackNames(Set<String> callbackNames) { + this.callbackNames = callbackNames; + } + + public Map<String, Set<String>> getRpcInterfaces() { + return rpcInterfaces; + } + + public void setRpcInterfaces(Map<String, Set<String>> rpcInterfaces) { + this.rpcInterfaces = rpcInterfaces; + } +} diff --git a/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java deleted file mode 100644 index ab0e62222c..0000000000 --- a/src/com/vaadin/terminal/gwt/client/JavascriptConnectorHelper.java +++ /dev/null @@ -1,205 +0,0 @@ -/* -@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<String> attemptedNames = new ArrayList<String>(); - 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<String> 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); - }); - }, - 'getCallback': function(name) { - return $entry(function() { - var args = [name, Array.prototype.slice.call(arguments, 0)]; - var iface = "com.vaadin.ui.JavascriptManager$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] = []; - } - registeredRpc[iface].push(rpcHandler); - }, - }; - }-*/; - - private void fireRpc(String iface, String method, - JsArray<JavaScriptObject> 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) { - if ("com.vaadin.ui.JavascriptManager$JavascriptCallbackRpc" - .equals(invocation.getInterfaceName()) - && "call".equals(invocation.getMethodName())) { - invokeJsRpc(rpcMap, parametersJson.get(0).isString().stringValue(), - null, parametersJson.get(1).isArray().getJavaScriptObject()); - } else { - 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]; - if (methodName === null && typeof target === 'function') { - target.apply($wnd, parameters); - } else { - 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 deleted file mode 100644 index 6c098a52f6..0000000000 --- a/src/com/vaadin/terminal/gwt/client/JavascriptExtension.java +++ /dev/null @@ -1,33 +0,0 @@ -/* -@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 ecbfb0ecc9..3d7e838c62 100644 --- a/src/com/vaadin/terminal/gwt/client/WidgetSet.java +++ b/src/com/vaadin/terminal/gwt/client/WidgetSet.java @@ -5,7 +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.communication.HasJavaScriptConnectorHelper; import com.vaadin.terminal.gwt.client.ui.UnknownComponentConnector; public class WidgetSet { @@ -54,8 +54,8 @@ public class WidgetSet { * let the auto generated code instantiate this type */ ServerConnector connector = widgetMap.instantiate(classType); - if (connector instanceof HasJavascriptConnectorHelper) { - ((HasJavascriptConnectorHelper) connector) + 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..a5191a5fed --- /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/HasJavascriptConnectorHelper.java b/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java deleted file mode 100644 index 74bc75da66..0000000000 --- a/src/com/vaadin/terminal/gwt/client/communication/HasJavascriptConnectorHelper.java +++ /dev/null @@ -1,11 +0,0 @@ -/* -@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/RpcManager.java b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java index e0ffb40125..07d6292ce2 100644 --- a/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java +++ b/src/com/vaadin/terminal/gwt/client/communication/RpcManager.java @@ -89,8 +89,8 @@ public class RpcManager { MethodInvocation invocation = new MethodInvocation(connectorId, interfaceName, methodName); - if (connector instanceof HasJavascriptConnectorHelper) { - ((HasJavascriptConnectorHelper) connector) + if (connector instanceof HasJavaScriptConnectorHelper) { + ((HasJavaScriptConnectorHelper) connector) .getJavascriptConnectorHelper().invokeJsRpc(invocation, parametersJson); } else { diff --git a/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java new file mode 100644 index 0000000000..bcefcf05cb --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/AbstractExtensionConnector.java @@ -0,0 +1,27 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions; + +import com.vaadin.terminal.gwt.client.ServerConnector; +import com.vaadin.terminal.gwt.client.ui.AbstractConnector; + +public abstract class AbstractExtensionConnector extends AbstractConnector { + @Override + public void setParent(ServerConnector parent) { + ServerConnector oldParent = getParent(); + if (oldParent != null && oldParent != parent) { + throw new IllegalStateException( + "An extension can not be moved from one parent to another."); + } + + super.setParent(parent); + + extend(parent); + } + + protected void extend(ServerConnector target) { + // Default does nothing + } +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java new file mode 100644 index 0000000000..f1185586d5 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/ExecuteJavaScriptRpc.java @@ -0,0 +1,11 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.terminal.gwt.client.extensions.javascriptmanager; + +import com.vaadin.terminal.gwt.client.communication.ClientRpc; + +public interface ExecuteJavaScriptRpc extends ClientRpc { + public void executeJavaScript(String script); +} diff --git a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerConnector.java index 979a6d22c4..72bd253ecb 100644 --- a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerConnector.java @@ -12,15 +12,24 @@ 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.extensions.AbstractExtensionConnector; import com.vaadin.terminal.gwt.client.ui.Connect; -import com.vaadin.ui.JavascriptManager; +import com.vaadin.ui.JavaScript; -@Connect(JavascriptManager.class) -public class JavascriptManagerConnector extends AbstractConnector { +@Connect(JavaScript.class) +public class JavaScriptManagerConnector extends AbstractExtensionConnector { private Set<String> currentNames = new HashSet<String>(); @Override + protected void init() { + registerRpc(ExecuteJavaScriptRpc.class, new ExecuteJavaScriptRpc() { + public void executeJavaScript(String Script) { + eval(Script); + } + }); + } + + @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { super.onStateChanged(stateChangeEvent); @@ -46,7 +55,7 @@ public class JavascriptManagerConnector extends AbstractConnector { $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); + m.@com.vaadin.terminal.gwt.client.extensions.javascriptmanager.JavaScriptManagerConnector::sendRpc(Ljava/lang/String;Lcom/google/gwt/core/client/JsArray;)(name, args); }); }-*/; @@ -56,6 +65,13 @@ public class JavascriptManagerConnector extends AbstractConnector { delete $wnd[name]; }-*/; + private static native void eval(String Script) + /*-{ + if(Script) { + $wnd.eval(Script); + } + }-*/; + public void sendRpc(String name, JsArray<JavaScriptObject> arguments) { Object[] parameters = new Object[] { name, new JSONArray(arguments) }; @@ -63,16 +79,14 @@ public class JavascriptManagerConnector extends AbstractConnector { * 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); + getConnection().addMethodInvocationToQueue( + new MethodInvocation(getConnectorId(), + "com.vaadin.ui.JavaScript$JavaScriptCallbackRpc", + "call", parameters), true); } @Override - public JavascriptManagerState getState() { - return (JavascriptManagerState) super.getState(); + 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 index 77794ffdca..fc246aff04 100644 --- a/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavascriptManagerState.java +++ b/src/com/vaadin/terminal/gwt/client/extensions/javascriptmanager/JavaScriptManagerState.java @@ -9,7 +9,7 @@ import java.util.Set; import com.vaadin.terminal.gwt.client.communication.SharedState; -public class JavascriptManagerState extends SharedState { +public class JavaScriptManagerState extends SharedState { private Set<String> names = new HashSet<String>(); public Set<String> getNames() { diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java index 57e65e91c6..47afe95771 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/JavascriptComponentConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentConnector.java @@ -5,16 +5,15 @@ 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; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper; +import com.vaadin.terminal.gwt.client.communication.HasJavaScriptConnectorHelper; +import com.vaadin.ui.AbstractJavaScriptComponent; -@Connect(AbstractJavascriptComponent.class) -public class JavascriptComponentConnector extends AbstractComponentConnector - implements HasJavascriptConnectorHelper { +@Connect(AbstractJavaScriptComponent.class) +public class JavaScriptComponentConnector extends AbstractComponentConnector + implements HasJavaScriptConnectorHelper { - private final JavascriptConnectorHelper helper = new JavascriptConnectorHelper( + private final JavaScriptConnectorHelper helper = new JavaScriptConnectorHelper( this) { @Override protected void showInitProblem( @@ -30,17 +29,6 @@ public class JavascriptComponentConnector extends AbstractComponentConnector } }; - @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) /*-{ @@ -50,11 +38,16 @@ public class JavascriptComponentConnector extends AbstractComponentConnector }-*/; @Override - public JavascriptWidget getWidget() { - return (JavascriptWidget) super.getWidget(); + public JavaScriptWidget getWidget() { + return (JavaScriptWidget) super.getWidget(); } - public JavascriptConnectorHelper getJavascriptConnectorHelper() { + 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..6728f85ec9 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptComponentState.java @@ -0,0 +1,37 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +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; +import com.vaadin.terminal.gwt.client.JavaScriptConnectorHelper.JavaScriptConnectorState; + +public class JavaScriptComponentState extends ComponentState implements + JavaScriptConnectorState { + + private Set<String> callbackNames = new HashSet<String>(); + private Map<String, Set<String>> rpcInterfaces = new HashMap<String, Set<String>>(); + + public Set<String> getCallbackNames() { + return callbackNames; + } + + public void setCallbackNames(Set<String> callbackNames) { + this.callbackNames = callbackNames; + } + + public Map<String, Set<String>> getRpcInterfaces() { + return rpcInterfaces; + } + + public void setRpcInterfaces(Map<String, Set<String>> rpcInterfaces) { + this.rpcInterfaces = rpcInterfaces; + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptWidget.java index 93a4417b1c..e6c3323893 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/JavascriptWidget.java +++ b/src/com/vaadin/terminal/gwt/client/ui/JavaScriptWidget.java @@ -8,13 +8,13 @@ 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() { +public class JavaScriptWidget extends Widget { + public JavaScriptWidget() { setElement(Document.get().createDivElement()); } public void showNoInitFound(ArrayList<String> attemptedNames) { - String message = "Could not initialize JavascriptConnector because no javascript init function was found. Make sure one of these functions are defined: <ul>"; + String message = "Could not initialize JavaScriptConnector because no JavaScript init function was found. Make sure one of these functions are defined: <ul>"; for (String name : attemptedNames) { message += "<li>" + name + "</li>"; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java index 9be41a9623..eebbe971a4 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/RootConnector.java @@ -168,9 +168,6 @@ public class RootConnector extends AbstractComponentContainerConnector getWidget().id, client); } getWidget().actionHandler.updateActionMap(childUidl); - } else if (tag == "execJS") { - String script = childUidl.getStringAttribute("script"); - VRoot.eval(script); } else if (tag == "notifications") { for (final Iterator<?> it = childUidl.getChildIterator(); it .hasNext();) { diff --git a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java index 09ed5c9859..ab562f5216 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java +++ b/src/com/vaadin/terminal/gwt/client/ui/root/VRoot.java @@ -295,21 +295,6 @@ public class VRoot extends SimplePanel implements ResizeHandler, }-*/; /** - * Evaluate the given script in the browser document. - * - * @param script - * Script to be executed. - */ - static native void eval(String script) - /*-{ - try { - if (script == null) return; - $wnd.eval(script); - } catch (e) { - } - }-*/; - - /** * Returns true if the body is NOT generated, i.e if someone else has made * the page that we're running in. Otherwise we're in charge of the whole * page. diff --git a/src/com/vaadin/terminal/gwt/server/ClientConnector.java b/src/com/vaadin/terminal/gwt/server/ClientConnector.java index 359e112738..860627a352 100644 --- a/src/com/vaadin/terminal/gwt/server/ClientConnector.java +++ b/src/com/vaadin/terminal/gwt/server/ClientConnector.java @@ -3,7 +3,7 @@ */ package com.vaadin.terminal.gwt.server; -import java.util.Iterator; +import java.util.Collection; import java.util.List; import com.vaadin.terminal.AbstractClientConnector; @@ -137,7 +137,7 @@ public interface ClientConnector extends Connector, RpcTarget { */ public void detach(); - public Iterator<Extension> getExtensionIterator(); + public Collection<Extension> getExtensions(); public void removeExtension(Extension extension); } diff --git a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java index 101e7f0cb3..d04ecb8380 100644 --- a/src/com/vaadin/terminal/gwt/server/DragAndDropService.java +++ b/src/com/vaadin/terminal/gwt/server/DragAndDropService.java @@ -4,7 +4,8 @@ package com.vaadin.terminal.gwt.server; import java.io.PrintWriter; -import java.util.Iterator; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -290,9 +291,9 @@ public class DragAndDropService implements VariableOwner, ClientConnector { } @Override - public Iterator<Extension> getExtensionIterator() { + public Collection<Extension> getExtensions() { // TODO Auto-generated method stub - return null; + return Collections.emptySet(); } @Override diff --git a/src/com/vaadin/ui/AbstractJavaScriptComponent.java b/src/com/vaadin/ui/AbstractJavaScriptComponent.java new file mode 100644 index 0000000000..343caf4abb --- /dev/null +++ b/src/com/vaadin/ui/AbstractJavaScriptComponent.java @@ -0,0 +1,33 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +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( + this); + + @Override + protected <T> void registerRpc(T implementation, + java.lang.Class<T> rpcInterfaceType) { + super.registerRpc(implementation, rpcInterfaceType); + callbackHelper.registerRpc(rpcInterfaceType); + } + + protected void registerCallback(String functionName, + JavaScriptCallback javascriptCallback) { + callbackHelper.registerCallback(functionName, javascriptCallback); + } + + protected void invokeCallback(String name, Object... arguments) { + callbackHelper.invokeCallback(name, arguments); + } + + @Override + public JavaScriptComponentState getState() { + return (JavaScriptComponentState) super.getState(); + } +} diff --git a/src/com/vaadin/ui/AbstractJavascriptComponent.java b/src/com/vaadin/ui/AbstractJavascriptComponent.java deleted file mode 100644 index 0a26c10239..0000000000 --- a/src/com/vaadin/ui/AbstractJavascriptComponent.java +++ /dev/null @@ -1,19 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ -package com.vaadin.ui; - -import com.vaadin.terminal.JavascriptRpcHelper; - -public class AbstractJavascriptComponent extends AbstractComponent { - private JavascriptRpcHelper rpcHelper = new JavascriptRpcHelper(this); - - protected void registerCallback(String functionName, - JavascriptCallback javascriptCallback) { - rpcHelper.registerCallback(functionName, javascriptCallback); - } - - protected void invokeCallback(String name, Object... arguments) { - rpcHelper.invokeCallback(name, arguments); - } -} diff --git a/src/com/vaadin/ui/JavaScript.java b/src/com/vaadin/ui/JavaScript.java new file mode 100644 index 0000000000..fb1dba2f20 --- /dev/null +++ b/src/com/vaadin/ui/JavaScript.java @@ -0,0 +1,84 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ + +package com.vaadin.ui; + +import java.util.HashMap; +import java.util.Map; + +import com.vaadin.external.json.JSONArray; +import com.vaadin.external.json.JSONException; +import com.vaadin.terminal.AbstractExtension; +import com.vaadin.terminal.Extension; +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; + +public class JavaScript extends AbstractExtension { + private Map<String, JavaScriptCallback> callbacks = new HashMap<String, JavaScriptCallback>(); + + // 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 JavaScript() { + registerRpc(new JavaScriptCallbackRpc() { + public void call(String name, JSONArray arguments) { + JavaScriptCallback callback = callbacks.get(name); + // TODO handle situation if name is not registered + try { + callback.call(arguments); + } catch (JSONException e) { + throw new IllegalArgumentException(e); + } + } + }); + } + + @Override + public JavaScriptManagerState getState() { + return (JavaScriptManagerState) super.getState(); + } + + public void addCallback(String name, JavaScriptCallback callback) { + callbacks.put(name, callback); + if (getState().getNames().add(name)) { + requestRepaint(); + } + } + + public void removeCallback(String name) { + callbacks.remove(name); + if (getState().getNames().remove(name)) { + requestRepaint(); + } + } + + public void execute(String script) { + getRpcProxy(ExecuteJavaScriptRpc.class).executeJavaScript(script); + } + + public static JavaScript getCurrent() { + return Root.getCurrentRoot().getJavaScript(); + } + + private static JavaScript getJavascript(Root root) { + // TODO Add caching to avoid iterating collection every time + // Caching should use weak references to avoid memory leaks -> cache + // should be transient to avoid serialization problems + for (Extension extension : root.getExtensions()) { + if (extension instanceof JavaScript) { + return (JavaScript) extension; + } + } + + // Extend root if it isn't yet done + JavaScript javascript = new JavaScript(); + javascript.extend(root); + return javascript; + } + +} diff --git a/src/com/vaadin/ui/JavascriptCallback.java b/src/com/vaadin/ui/JavaScriptCallback.java index 89700b3faf..e6b16010e4 100644 --- a/src/com/vaadin/ui/JavascriptCallback.java +++ b/src/com/vaadin/ui/JavaScriptCallback.java @@ -9,6 +9,6 @@ import java.io.Serializable; import com.vaadin.external.json.JSONArray; import com.vaadin.external.json.JSONException; -public interface JavascriptCallback extends Serializable { +public interface JavaScriptCallback extends Serializable { public void call(JSONArray arguments) throws JSONException; } diff --git a/src/com/vaadin/ui/JavascriptManager.java b/src/com/vaadin/ui/JavascriptManager.java deleted file mode 100644 index 72295dce2b..0000000000 --- a/src/com/vaadin/ui/JavascriptManager.java +++ /dev/null @@ -1,58 +0,0 @@ -/* -@VaadinApache2LicenseForJavaFiles@ - */ - -package com.vaadin.ui; - -import java.util.HashMap; -import java.util.Map; - -import com.vaadin.external.json.JSONArray; -import com.vaadin.external.json.JSONException; -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<String, JavascriptCallback> callbacks = new HashMap<String, JavascriptCallback>(); - - // 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 handle situation if name is not registered - try { - callback.call(arguments); - } catch (JSONException e) { - throw new IllegalArgumentException(e); - } - } - }); - } - - @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 9814084cbc..60408fe1dc 100644 --- a/src/com/vaadin/ui/Root.java +++ b/src/com/vaadin/ui/Root.java @@ -357,12 +357,6 @@ public abstract class Root extends AbstractComponentContainer implements private List<Notification> notifications; /** - * A list of javascript commands that are waiting to be sent to the client. - * Cleared (set to null) when the commands have been sent. - */ - private List<String> jsExecQueue = null; - - /** * List of windows in this root. */ private final LinkedHashSet<Window> windows = new LinkedHashSet<Window>(); @@ -408,7 +402,7 @@ public abstract class Root extends AbstractComponentContainer implements private DirtyConnectorTracker dirtyConnectorTracker = new DirtyConnectorTracker( this); - private JavascriptManager javascriptManager; + private JavaScript javaScript; private RootServerRpc rpc = new RootServerRpc() { public void click(MouseEventDetails mouseDetails) { @@ -557,16 +551,6 @@ public abstract class Root extends AbstractComponentContainer implements notifications = null; } - // Add executable javascripts if needed - if (jsExecQueue != null) { - for (String script : jsExecQueue) { - target.startTag("execJS"); - target.addAttribute("script", script); - target.endTag("execJS"); - } - jsExecQueue = null; - } - if (scrollIntoView != null) { target.addAttribute("scrollTo", scrollIntoView); scrollIntoView = null; @@ -1000,15 +984,12 @@ public abstract class Root extends AbstractComponentContainer implements * * @param script * JavaScript snippet that will be executed. + * + * @deprecated as of 7.0, use getJavaScript().execute(String) instead */ + @Deprecated public void executeJavaScript(String script) { - if (jsExecQueue == null) { - jsExecQueue = new ArrayList<String>(); - } - - jsExecQueue.add(script); - - requestRepaint(); + getJavaScript().execute(script); } /** @@ -1592,14 +1573,14 @@ public abstract class Root extends AbstractComponentContainer implements return dirtyConnectorTracker; } - public JavascriptManager getJavascriptManager() { - if (javascriptManager == null) { + public JavaScript getJavaScript() { + if (javaScript == null) { // Create and attach on first use - javascriptManager = new JavascriptManager(); - addExtension(javascriptManager); + javaScript = new JavaScript(); + addExtension(javaScript); } - return javascriptManager; + return javaScript; } } diff --git a/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java b/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavaScriptComponent.java index 2240fc246b..6519e4ae74 100644 --- a/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavascriptComponent.java +++ b/tests/testbench/com/vaadin/tests/components/javascriptcomponent/BasicJavaScriptComponent.java @@ -7,21 +7,24 @@ 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.AbstractJavaScriptComponent; +import com.vaadin.ui.JavaScriptCallback; import com.vaadin.ui.Root; @LoadScripts({ "/statictestfiles/jsconnector.js" }) -public class BasicJavascriptComponent extends AbstractTestRoot { +public class BasicJavaScriptComponent extends AbstractTestRoot { public interface ExampleClickRpc extends ServerRpc { public void onClick(String message); } - public static class SpecialState extends ComponentState { + public static class SpecialState extends JavaScriptComponentState { private List<String> data; public List<String> getData() { @@ -33,7 +36,7 @@ public class BasicJavascriptComponent extends AbstractTestRoot { } } - public static class ExampleWidget extends AbstractJavascriptComponent { + public static class ExampleWidget extends AbstractJavaScriptComponent { public ExampleWidget() { registerRpc(new ExampleClickRpc() { public void onClick(String message) { @@ -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/JavascriptManagerTest.java b/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.java index c84d37cd0f..be4675eb2d 100644 --- a/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.java +++ b/tests/testbench/com/vaadin/tests/extensions/JavascriptManagerTest.java @@ -10,7 +10,8 @@ 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; +import com.vaadin.ui.JavaScript; +import com.vaadin.ui.JavaScriptCallback; public class JavascriptManagerTest extends AbstractTestRoot { @@ -19,7 +20,8 @@ public class JavascriptManagerTest extends AbstractTestRoot { @Override protected void setup(WrappedRequest request) { addComponent(log); - getJavascriptManager().addCallback("testing", new JavascriptCallback() { + JavaScript js = JavaScript.getCurrent(); + js.addCallback("testing", new JavaScriptCallback() { public void call(JSONArray arguments) throws JSONException { log.log("Got " + arguments.length() + " arguments"); log.log("Argument 1 as a number: " + arguments.getInt(0)); @@ -30,7 +32,7 @@ public class JavascriptManagerTest extends AbstractTestRoot { + (arguments.get(3) == JSONObject.NULL)); } }); - executeJavaScript("window.testing(42, 'text', {p: true}, null)"); + js.execute("window.testing(42, 'text', {p: true}, null)"); } @Override diff --git a/tests/testbench/com/vaadin/tests/extensions/SimpleJavascriptExtensionTest.java b/tests/testbench/com/vaadin/tests/extensions/SimpleJavaScriptExtensionTest.java index 92c134efb0..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.terminal.AbstractJavascriptExtension; +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 class SimpleJavaScriptExtensionTest extends AbstractTestRoot { - public static class SimpleJavascriptExtensionState extends SharedState { + public static class SimpleJavaScriptExtensionState extends + JavaScriptExtensionState { private String prefix; public void setPrefix(String prefix) { @@ -30,31 +34,37 @@ public class SimpleJavascriptExtensionTest extends AbstractTestRoot { } } - public static interface SimpleJavascriptExtensionClientRpc extends + public static interface SimpleJavaScriptExtensionClientRpc extends ClientRpc { public void greet(String message); } - public static interface SimpleJavascriptExtensionServerRpc extends + public static interface SimpleJavaScriptExtensionServerRpc extends ServerRpc { public void greet(String message); } public static class SimpleJavascriptExtension extends - AbstractJavascriptExtension { + AbstractJavaScriptExtension { public SimpleJavascriptExtension() { - registerRpc(new SimpleJavascriptExtensionServerRpc() { + registerRpc(new SimpleJavaScriptExtensionServerRpc() { public void greet(String message) { Root.getCurrentRoot().showNotification( getState().getPrefix() + message); } }); + registerCallback("greetToServer", new JavaScriptCallback() { + public void call(JSONArray arguments) throws JSONException { + Root.getCurrentRoot().showNotification( + getState().getPrefix() + arguments.getString(0)); + } + }); } @Override - public SimpleJavascriptExtensionState getState() { - return (SimpleJavascriptExtensionState) super.getState(); + public SimpleJavaScriptExtensionState getState() { + return (SimpleJavaScriptExtensionState) super.getState(); } public void setPrefix(String prefix) { @@ -62,10 +72,14 @@ public class SimpleJavascriptExtensionTest extends AbstractTestRoot { requestRepaint(); } - public void greet(String message) { - getRpcProxy(SimpleJavascriptExtensionClientRpc.class) + 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 |