import com.vaadin.shared.JavaScriptExtensionState; | import com.vaadin.shared.JavaScriptExtensionState; | ||||
import com.vaadin.terminal.gwt.client.ApplicationConnection; | import com.vaadin.terminal.gwt.client.ApplicationConnection; | ||||
import com.vaadin.ui.JavaScriptCallback; | |||||
import com.vaadin.ui.JavaScriptFunction; | |||||
/** | /** | ||||
* Base class for Extensions with all client-side logic implemented using | * Base class for Extensions with all client-side logic implemented using | ||||
* the field, that function is called whenever the contents of the shared state | * the field, that function is called whenever the contents of the shared state | ||||
* is changed.</li> | * is changed.</li> | ||||
* <li>Any field name corresponding to a call to | * <li>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 | |||||
* {@link #addFunction(String, JavaScriptFunction)} on the server will | |||||
* automatically be present as a function that triggers the registered function | |||||
* on the server.</li> | * on the server.</li> | ||||
* <li>Any field name referred to using | * <li>Any field name referred to using | ||||
* {@link #invokeCallback(String, Object...)} on the server will be called if a | |||||
* {@link #callFunction(String, Object...)} on the server will be called if a | |||||
* function has been assigned to the field.</li> | * function has been assigned to the field.</li> | ||||
* </ul> | * </ul> | ||||
* <p> | * <p> | ||||
} | } | ||||
/** | /** | ||||
* Register a {@link JavaScriptCallback} that can be called from the | |||||
* Register a {@link JavaScriptFunction} that can be called from the | |||||
* JavaScript using the provided name. A JavaScript function with the | * JavaScript using the provided name. A JavaScript function with the | ||||
* provided name will be added to the connector wrapper object (initially | * provided name will be added to the connector wrapper object (initially | ||||
* available as <code>this</code>). Calling that JavaScript function will | * available as <code>this</code>). Calling that JavaScript function will | ||||
* cause the call method in the registered {@link JavaScriptCallback} to be | |||||
* cause the call method in the registered {@link JavaScriptFunction} to be | |||||
* invoked with the same arguments. | * invoked with the same arguments. | ||||
* | * | ||||
* @param functionName | * @param functionName | ||||
* the name that should be used for client-side callback | * 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 | |||||
* @param function | |||||
* the {@link JavaScriptFunction} object that will be invoked | |||||
* when the JavaScript function is called | |||||
*/ | */ | ||||
protected void registerCallback(String functionName, | |||||
JavaScriptCallback javaScriptCallback) { | |||||
callbackHelper.registerCallback(functionName, javaScriptCallback); | |||||
protected void addFunction(String functionName, JavaScriptFunction function) { | |||||
callbackHelper.registerCallback(functionName, function); | |||||
} | } | ||||
/** | /** | ||||
* @param arguments | * @param arguments | ||||
* function arguments | * function arguments | ||||
*/ | */ | ||||
protected void invokeCallback(String name, Object... arguments) { | |||||
protected void callFunction(String name, Object... arguments) { | |||||
callbackHelper.invokeCallback(name, arguments); | callbackHelper.invokeCallback(name, arguments); | ||||
} | } | ||||
import com.vaadin.tools.ReflectTools; | import com.vaadin.tools.ReflectTools; | ||||
import com.vaadin.ui.AbstractJavaScriptComponent; | import com.vaadin.ui.AbstractJavaScriptComponent; | ||||
import com.vaadin.ui.JavaScript.JavaScriptCallbackRpc; | import com.vaadin.ui.JavaScript.JavaScriptCallbackRpc; | ||||
import com.vaadin.ui.JavaScriptCallback; | |||||
import com.vaadin.ui.JavaScriptFunction; | |||||
/** | /** | ||||
* Internal helper class used to implement functionality common to | * Internal helper class used to implement functionality common to | ||||
JavaScriptCallbackRpc.class, "call", String.class, JSONArray.class); | JavaScriptCallbackRpc.class, "call", String.class, JSONArray.class); | ||||
private AbstractClientConnector connector; | private AbstractClientConnector connector; | ||||
private Map<String, JavaScriptCallback> callbacks = new HashMap<String, JavaScriptCallback>(); | |||||
private Map<String, JavaScriptFunction> callbacks = new HashMap<String, JavaScriptFunction>(); | |||||
private JavaScriptCallbackRpc javascriptCallbackRpc; | private JavaScriptCallbackRpc javascriptCallbackRpc; | ||||
public JavaScriptCallbackHelper(AbstractClientConnector connector) { | public JavaScriptCallbackHelper(AbstractClientConnector connector) { | ||||
} | } | ||||
public void registerCallback(String functionName, | public void registerCallback(String functionName, | ||||
JavaScriptCallback javaScriptCallback) { | |||||
JavaScriptFunction javaScriptCallback) { | |||||
callbacks.put(functionName, javaScriptCallback); | callbacks.put(functionName, javaScriptCallback); | ||||
JavaScriptConnectorState state = getConnectorState(); | JavaScriptConnectorState state = getConnectorState(); | ||||
if (state.getCallbackNames().add(functionName)) { | if (state.getCallbackNames().add(functionName)) { | ||||
javascriptCallbackRpc = new JavaScriptCallbackRpc() { | javascriptCallbackRpc = new JavaScriptCallbackRpc() { | ||||
@Override | @Override | ||||
public void call(String name, JSONArray arguments) { | public void call(String name, JSONArray arguments) { | ||||
JavaScriptCallback callback = callbacks.get(name); | |||||
JavaScriptFunction callback = callbacks.get(name); | |||||
try { | try { | ||||
callback.call(arguments); | callback.call(arguments); | ||||
} catch (JSONException e) { | } catch (JSONException e) { |
* the field, that function is called whenever the contents of the shared state | * the field, that function is called whenever the contents of the shared state | ||||
* is changed.</li> | * is changed.</li> | ||||
* <li>Any field name corresponding to a call to | * <li>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 | |||||
* {@link #addFunction(String, JavaScriptFunction)} on the server will | |||||
* automatically be present as a function that triggers the registered function | |||||
* on the server.</li> | * on the server.</li> | ||||
* <li>Any field name referred to using | * <li>Any field name referred to using | ||||
* {@link #invokeCallback(String, Object...)} on the server will be called if a | |||||
* {@link #callFunction(String, Object...)} on the server will be called if a | |||||
* function has been assigned to the field.</li> | * function has been assigned to the field.</li> | ||||
* </ul> | * </ul> | ||||
* <p> | * <p> | ||||
} | } | ||||
/** | /** | ||||
* Register a {@link JavaScriptCallback} that can be called from the | |||||
* Register a {@link JavaScriptFunction} that can be called from the | |||||
* JavaScript using the provided name. A JavaScript function with the | * JavaScript using the provided name. A JavaScript function with the | ||||
* provided name will be added to the connector wrapper object (initially | * provided name will be added to the connector wrapper object (initially | ||||
* available as <code>this</code>). Calling that JavaScript function will | * available as <code>this</code>). Calling that JavaScript function will | ||||
* cause the call method in the registered {@link JavaScriptCallback} to be | |||||
* cause the call method in the registered {@link JavaScriptFunction} to be | |||||
* invoked with the same arguments. | * invoked with the same arguments. | ||||
* | * | ||||
* @param functionName | * @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 | |||||
* the name that should be used for client-side function | |||||
* @param function | |||||
* the {@link JavaScriptFunction} object that will be invoked | |||||
* when the JavaScript function is called | |||||
*/ | */ | ||||
protected void registerCallback(String functionName, | |||||
JavaScriptCallback javaScriptCallback) { | |||||
callbackHelper.registerCallback(functionName, javaScriptCallback); | |||||
protected void addFunction(String functionName, JavaScriptFunction function) { | |||||
callbackHelper.registerCallback(functionName, function); | |||||
} | } | ||||
/** | /** | ||||
* @param arguments | * @param arguments | ||||
* function arguments | * function arguments | ||||
*/ | */ | ||||
protected void invokeCallback(String name, Object... arguments) { | |||||
protected void callFunction(String name, Object... arguments) { | |||||
callbackHelper.invokeCallback(name, arguments); | callbackHelper.invokeCallback(name, arguments); | ||||
} | } | ||||
* @since 7.0.0 | * @since 7.0.0 | ||||
*/ | */ | ||||
public class JavaScript extends AbstractExtension { | public class JavaScript extends AbstractExtension { | ||||
private Map<String, JavaScriptCallback> callbacks = new HashMap<String, JavaScriptCallback>(); | |||||
private Map<String, JavaScriptFunction> functions = new HashMap<String, JavaScriptFunction>(); | |||||
// Can not be defined in client package as this JSONArray is not available | // Can not be defined in client package as this JSONArray is not available | ||||
// in GWT | // in GWT | ||||
registerRpc(new JavaScriptCallbackRpc() { | registerRpc(new JavaScriptCallbackRpc() { | ||||
@Override | @Override | ||||
public void call(String name, JSONArray arguments) { | public void call(String name, JSONArray arguments) { | ||||
JavaScriptCallback callback = callbacks.get(name); | |||||
JavaScriptFunction function = functions.get(name); | |||||
// TODO handle situation if name is not registered | // TODO handle situation if name is not registered | ||||
try { | try { | ||||
callback.call(arguments); | |||||
function.call(arguments); | |||||
} catch (JSONException e) { | } catch (JSONException e) { | ||||
throw new IllegalArgumentException(e); | throw new IllegalArgumentException(e); | ||||
} | } | ||||
/** | /** | ||||
* Add a new function to the global JavaScript namespace (i.e. the window | * Add a new function to the global JavaScript namespace (i.e. the window | ||||
* object). The <code>call</code> method in the passed | * object). The <code>call</code> method in the passed | ||||
* {@link JavaScriptCallback} object will be invoked with the same | |||||
* {@link JavaScriptFunction} object will be invoked with the same | |||||
* parameters whenever the JavaScript function is called in the browser. | * parameters whenever the JavaScript function is called in the browser. | ||||
* | * | ||||
* A callback added with the name <code>"myCallback"</code> can thus be | |||||
* A function added with the name <code>"myFunction"</code> can thus be | |||||
* invoked with the following JavaScript code: | * invoked with the following JavaScript code: | ||||
* <code>window.myCallback(argument1, argument2)</code>. | |||||
* <code>window.myFunction(argument1, argument2)</code>. | |||||
* | * | ||||
* If the name parameter contains dots, simple objects are created on demand | * If the name parameter contains dots, simple objects are created on demand | ||||
* to allow calling the function using the same name (e.g. | * to allow calling the function using the same name (e.g. | ||||
* <code>window.myObject.myFunction</code>). | * <code>window.myObject.myFunction</code>). | ||||
* | * | ||||
* @param name | * @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 | |||||
* the name that the function should get in the global JavaScript | |||||
* namespace. | |||||
* @param function | |||||
* the JavaScriptFunction that will be invoked if the JavaScript | |||||
* function is called. | * function is called. | ||||
*/ | */ | ||||
public void addCallback(String name, JavaScriptCallback callback) { | |||||
callbacks.put(name, callback); | |||||
public void addFunction(String name, JavaScriptFunction function) { | |||||
functions.put(name, function); | |||||
if (getState().getNames().add(name)) { | if (getState().getNames().add(name)) { | ||||
requestRepaint(); | requestRepaint(); | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Removes a JavaScripCallback from the browser's global JavaScript | |||||
* Removes a JavaScripFunction from the browser's global JavaScript | |||||
* namespace. | * 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. | |||||
* If the name contains dots and intermediate objects were created by | |||||
* {@link #addFunction(String, JavaScriptFunction)}, these objects will not | |||||
* be removed by this method. | |||||
* | * | ||||
* @param name | * @param name | ||||
* the name of the callback to remove | * the name of the callback to remove | ||||
*/ | */ | ||||
public void removeCallback(String name) { | |||||
callbacks.remove(name); | |||||
public void removeFunction(String name) { | |||||
functions.remove(name); | |||||
if (getState().getNames().remove(name)) { | if (getState().getNames().remove(name)) { | ||||
requestRepaint(); | requestRepaint(); | ||||
} | } |
* the corresponding JavaScript function is called, the {@link #call(JSONArray)} | * the corresponding JavaScript function is called, the {@link #call(JSONArray)} | ||||
* method is invoked. | * method is invoked. | ||||
* | * | ||||
* @see JavaScript#addCallback(String, JavaScriptCallback) | |||||
* @see AbstractJavaScriptComponent#registerCallback(String, JavaScriptCallback) | |||||
* @see AbstractJavaScriptExtension#registerCallback(String, JavaScriptCallback) | |||||
* @see JavaScript#addFunction(String, JavaScriptCallback) | |||||
* @see AbstractJavaScriptComponent#addFunction(String, JavaScriptCallback) | |||||
* @see AbstractJavaScriptExtension#addFunction(String, JavaScriptCallback) | |||||
* | * | ||||
* @author Vaadin Ltd | * @author Vaadin Ltd | ||||
* @version @VERSION@ | * @version @VERSION@ | ||||
* @since 7.0.0 | * @since 7.0.0 | ||||
*/ | */ | ||||
public interface JavaScriptCallback extends Serializable { | |||||
public interface JavaScriptFunction extends Serializable { | |||||
/** | /** | ||||
* Invoked whenever the corresponding JavaScript function is called in the | * Invoked whenever the corresponding JavaScript function is called in the | ||||
* browser. | * browser. |
import com.vaadin.tests.util.Log; | import com.vaadin.tests.util.Log; | ||||
import com.vaadin.ui.AbstractJavaScriptComponent; | import com.vaadin.ui.AbstractJavaScriptComponent; | ||||
import com.vaadin.ui.HasComponents; | import com.vaadin.ui.HasComponents; | ||||
import com.vaadin.ui.JavaScriptCallback; | |||||
import com.vaadin.ui.JavaScriptFunction; | |||||
public class BasicJavaScriptComponent extends AbstractTestRoot { | public class BasicJavaScriptComponent extends AbstractTestRoot { | ||||
log.log("Got RPC message: " + message); | log.log("Got RPC message: " + message); | ||||
} | } | ||||
}); | }); | ||||
registerCallback("messageToServer", new JavaScriptCallback() { | |||||
addFunction("messageToServer", new JavaScriptFunction() { | |||||
@Override | @Override | ||||
public void call(JSONArray arguments) throws JSONException { | public void call(JSONArray arguments) throws JSONException { | ||||
log.log("Got callback message: " + arguments.getString(0)); | log.log("Got callback message: " + arguments.getString(0)); | ||||
} | } | ||||
}); | }); | ||||
registerCallback("reportParentIds", new JavaScriptCallback() { | |||||
addFunction("reportParentIds", new JavaScriptFunction() { | |||||
@Override | @Override | ||||
public void call(JSONArray arguments) throws JSONException { | public void call(JSONArray arguments) throws JSONException { | ||||
JSONArray parentIds = arguments.getJSONArray(0); | JSONArray parentIds = arguments.getJSONArray(0); | ||||
}); | }); | ||||
getRpcProxy(TestRpc.class).sendRpc("RPC message"); | getRpcProxy(TestRpc.class).sendRpc("RPC message"); | ||||
invokeCallback("messageToClient", "Callback message"); | |||||
callFunction("messageToClient", "Callback message"); | |||||
getState() | getState() | ||||
.setMessages( | .setMessages( |
import com.vaadin.tests.components.AbstractTestRoot; | import com.vaadin.tests.components.AbstractTestRoot; | ||||
import com.vaadin.tests.util.Log; | import com.vaadin.tests.util.Log; | ||||
import com.vaadin.ui.JavaScript; | import com.vaadin.ui.JavaScript; | ||||
import com.vaadin.ui.JavaScriptCallback; | |||||
import com.vaadin.ui.JavaScriptFunction; | |||||
public class JavascriptManagerTest extends AbstractTestRoot { | public class JavascriptManagerTest extends AbstractTestRoot { | ||||
protected void setup(WrappedRequest request) { | protected void setup(WrappedRequest request) { | ||||
addComponent(log); | addComponent(log); | ||||
final JavaScript js = JavaScript.getCurrent(); | final JavaScript js = JavaScript.getCurrent(); | ||||
js.addCallback("testing.doTest", new JavaScriptCallback() { | |||||
js.addFunction("testing.doTest", new JavaScriptFunction() { | |||||
@Override | @Override | ||||
public void call(JSONArray arguments) throws JSONException { | public void call(JSONArray arguments) throws JSONException { | ||||
log.log("Got " + arguments.length() + " arguments"); | log.log("Got " + arguments.length() + " arguments"); | ||||
+ arguments.getJSONObject(2).getBoolean("p")); | + arguments.getJSONObject(2).getBoolean("p")); | ||||
log.log("Argument 4 is JSONObject.NULL: " | log.log("Argument 4 is JSONObject.NULL: " | ||||
+ (arguments.get(3) == JSONObject.NULL)); | + (arguments.get(3) == JSONObject.NULL)); | ||||
js.removeCallback("testing.doTest"); | |||||
js.removeFunction("testing.doTest"); | |||||
} | } | ||||
}); | }); | ||||
js.execute("window.testing.doTest(42, 'text', {p: true}, null)"); | js.execute("window.testing.doTest(42, 'text', {p: true}, null)"); |
import com.vaadin.tests.components.AbstractTestRoot; | import com.vaadin.tests.components.AbstractTestRoot; | ||||
import com.vaadin.ui.Button; | import com.vaadin.ui.Button; | ||||
import com.vaadin.ui.Button.ClickEvent; | import com.vaadin.ui.Button.ClickEvent; | ||||
import com.vaadin.ui.JavaScriptCallback; | |||||
import com.vaadin.ui.JavaScriptFunction; | |||||
import com.vaadin.ui.Notification; | import com.vaadin.ui.Notification; | ||||
public class SimpleJavaScriptExtensionTest extends AbstractTestRoot { | public class SimpleJavaScriptExtensionTest extends AbstractTestRoot { | ||||
Notification.show(getState().getPrefix() + message); | Notification.show(getState().getPrefix() + message); | ||||
} | } | ||||
}); | }); | ||||
registerCallback("greetToServer", new JavaScriptCallback() { | |||||
addFunction("greetToServer", new JavaScriptFunction() { | |||||
@Override | @Override | ||||
public void call(JSONArray arguments) throws JSONException { | public void call(JSONArray arguments) throws JSONException { | ||||
Notification.show(getState().getPrefix() | Notification.show(getState().getPrefix() | ||||
} | } | ||||
public void greetCallback(String message) { | public void greetCallback(String message) { | ||||
invokeCallback("greetToClient", message); | |||||
callFunction("greetToClient", message); | |||||
} | } | ||||
} | } | ||||
private void pushCommand(Object... commandAndArguments) { | private void pushCommand(Object... commandAndArguments) { | ||||
// Cast to Object to use Object[] commandAndArguments as the first | // Cast to Object to use Object[] commandAndArguments as the first | ||||
// varargs argument instead of as the full varargs argument array. | // varargs argument instead of as the full varargs argument array. | ||||
invokeCallback("pushCommand", (Object) commandAndArguments); | |||||
callFunction("pushCommand", (Object) commandAndArguments); | |||||
} | } | ||||
protected void extend(Root root) { | protected void extend(Root root) { |
import com.vaadin.external.json.JSONArray; | import com.vaadin.external.json.JSONArray; | ||||
import com.vaadin.external.json.JSONException; | import com.vaadin.external.json.JSONException; | ||||
import com.vaadin.ui.AbstractJavaScriptComponent; | import com.vaadin.ui.AbstractJavaScriptComponent; | ||||
import com.vaadin.ui.JavaScriptCallback; | |||||
import com.vaadin.ui.JavaScriptFunction; | |||||
import com.vaadin.ui.Notification; | import com.vaadin.ui.Notification; | ||||
@JavaScript({ | @JavaScript({ | ||||
+ dataIndex + "]"); | + dataIndex + "]"); | ||||
} | } | ||||
}); | }); | ||||
registerCallback("onPlotClick", new JavaScriptCallback() { | |||||
addFunction("onPlotClick", new JavaScriptFunction() { | |||||
@Override | @Override | ||||
public void call(JSONArray arguments) throws JSONException { | public void call(JSONArray arguments) throws JSONException { | ||||
int seriesIndex = arguments.getInt(0); | int seriesIndex = arguments.getInt(0); | ||||
public void highlight(int seriesIndex, int dataIndex) { | public void highlight(int seriesIndex, int dataIndex) { | ||||
getRpcProxy(FlotHighlightRpc.class).highlight(seriesIndex, dataIndex); | getRpcProxy(FlotHighlightRpc.class).highlight(seriesIndex, dataIndex); | ||||
invokeCallback("highlight", seriesIndex, dataIndex); | |||||
callFunction("highlight", seriesIndex, dataIndex); | |||||
} | } | ||||
@Override | @Override |