diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index 926ec3fb39..0dcc61b114 100644 --- a/articles/flow/component-internals/element-api/calling-javascript.adoc +++ b/articles/flow/component-internals/element-api/calling-javascript.adoc @@ -17,7 +17,7 @@ The Element API contains methods for executing JavaScript in the browser from th The [methodname]`Element.callJsFunction()` method allows you to run a client-side component function from the server side. The method accepts two parameters: the name of the function to call; and the arguments to pass to the function. -The arguments passed to the function must be a type supported by the communication mechanism. The supported types are `String`, `Boolean`, `Integer`, `Double`, `JsonNode`, `Element`, and `Component`. +The arguments passed to the function must be a type supported by the communication mechanism. The supported types are `String`, `Boolean`, `Integer`, `Double`, `JsonNode`, `Element`, `Component`, and `JsFunction` (see <<#js-function,Passing JavaScript Functions>>). .Calling the `clearSelection()` JavaScript function on the root element from the server side [example] @@ -64,7 +64,7 @@ You can also use the generic [methodname]`Element.executeJs()` method to run Jav The [methodname]`executeJs()` method accepts two parameters: the JavaScript expression to invoke; and the parameters to pass to the expression. The given parameters are available as variables named `$0`, `$1`, and so on. -The arguments passed to the expression must be a type supported by the communication mechanism. The supported types are `String`, `Integer`, `Double`, `Boolean`, `JsonNode`, and `Element`. +The arguments passed to the expression must be a type supported by the communication mechanism. The supported types are `String`, `Integer`, `Double`, `Boolean`, `JsonNode`, `Element`, `Component`, and `JsFunction` (see <<#js-function,Passing JavaScript Functions>>). .Calling `MyModule.complete(true)` on the client side [example] @@ -164,4 +164,81 @@ CompletableFuture> future = getElement() ---- +[[js-function]] +== Passing JavaScript Functions [since:com.vaadin:vaadin@V25.2] + +A [classname]`JsFunction` lets you build a reusable JavaScript function on the server and pass it as a parameter to [methodname]`executeJs()` or [methodname]`callJsFunction()`. The function arrives on the client as a real callable function with its captured values pre-bound, so you don't need to concatenate JavaScript fragments to embed server-side values. + +The first argument to [methodname]`JsFunction.of()` is a JavaScript function body. The remaining arguments are captured values, referenced inside the body as `$0`, `$1`, … using the same naming convention as [methodname]`executeJs()` parameters. + +.Defining a function and invoking it +[example] +==== +[source,java] +---- +JsFunction greet = JsFunction.of( + "return $0 + ' ' + $1;", "Hello", "World"); +getElement().executeJs( + "this.textContent = $0();", greet); +---- +==== + +Captures may be any value supported as an [methodname]`executeJs()` parameter. An attached `Element` arrives as the corresponding DOM node; a detached `Element` arrives as `null`. + +.Capturing an element +[example] +==== +[source,java] +---- +Div target = new Div(); +add(target); + +JsFunction mutate = JsFunction.of( + "$0.textContent = 'updated';", + target.getElement()); +getElement().executeJs("$0();", mutate); +---- +==== + + +=== Declaring Runtime Arguments + +Use [methodname]`withArguments()` to declare named parameters that the function accepts at call time. The body references them by name, and the JavaScript that invokes the function passes them positionally: + +.A function with runtime arguments +[example] +==== +[source,java] +---- +JsFunction format = JsFunction + .of("return prefix + ':' + suffix;") + .withArguments("prefix", "suffix"); +getElement().executeJs( + "this.textContent = $0('alpha', 'beta');", format); +---- +==== + + +=== Controlling `this` + +In JavaScript, the value of `this` inside a function depends on how the function is invoked, not where it is defined. The body of a [classname]`JsFunction` follows this convention – `this` is not bound to the host element automatically. + +When the function is invoked as `$0(...)` from inside an [methodname]`executeJs()` expression, the body's `this` is the global object (or `undefined` in strict mode), not the element on which [methodname]`executeJs()` was called. To set `this` explicitly, invoke the function with [methodname]`Function.prototype.call()`: its first argument becomes `this` inside the body. + +.Calling the function with a specific `this` +[example] +==== +[source,java] +---- +JsFunction setOwnText = JsFunction + .of("this.textContent = msg;") + .withArguments("msg"); +getElement().executeJs( + "$0.call(this, 'host element text');", setOwnText); +---- +==== + +If the body always needs to reference the same element regardless of how the function is called, pass it as a capture instead of relying on `this`. + + [discussion-id]`AB7EDF45-DB22-4560-AF27-FF1DC6944482`