@@ -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<String> currentNames = new HashSet<String>(); | |||
@Override | |||
public void onStateChanged(StateChangeEvent stateChangeEvent) { | |||
super.onStateChanged(stateChangeEvent); | |||
Set<String> newNames = getState().getNames(); | |||
// Current names now only contains orphan callbacks | |||
currentNames.removeAll(newNames); | |||
for (String name : currentNames) { | |||
removeCallback(name); | |||
} | |||
currentNames = new HashSet<String>(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<JavaScriptObject> 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(); | |||
} | |||
} |
@@ -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<String> names = new HashSet<String>(); | |||
public Set<String> getNames() { | |||
return names; | |||
} | |||
public void setNames(Set<String> names) { | |||
this.names = names; | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} |
@@ -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<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 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(); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1,47 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> | |||
<head profile="http://selenium-ide.openqa.org/profiles/test-case"> | |||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | |||
<link rel="selenium.base" href="http://localhost:8888/" /> | |||
<title>New Test</title> | |||
</head> | |||
<body> | |||
<table cellpadding="1" cellspacing="1" border="1"> | |||
<thead> | |||
<tr><td rowspan="1" colspan="3">New Test</td></tr> | |||
</thead><tbody> | |||
<tr> | |||
<td>open</td> | |||
<td>/run/com.vaadin.tests.features.JavascriptManagerTest?restartApplication</td> | |||
<td></td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>vaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[4]</td> | |||
<td>1. Got 4 arguments</td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>vaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[3]</td> | |||
<td>2. Argument 1 as a number: 42</td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>vaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[2]</td> | |||
<td>3. Argument 2 as a string: text</td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>vaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[1]</td> | |||
<td>4. Argument 3.p as a boolean: true</td> | |||
</tr> | |||
<tr> | |||
<td>assertText</td> | |||
<td>vaadin=runcomvaadintestsfeaturesJavascriptManagerTest::/VVerticalLayout[0]/VVerticalLayout[0]/VVerticalLayout[0]/VLabel[0]</td> | |||
<td>5. Argument 4 is JSONObject.NULL: true</td> | |||
</tr> | |||
</tbody></table> | |||
</body> | |||
</html> |
@@ -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; | |||
} | |||
} |