From 64a4cca0ab201fa1c257691c99e035723b67f73c Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 19 May 2026 13:14:09 +0000 Subject: [PATCH 1/2] docs: document JsFunction for executeJs Adds a "Passing JavaScript Functions" section to the calling-javascript reference, covering JsFunction.of(), element captures, withArguments(), and how `this` binding works. Also lists JsFunction among the supported executeJs parameter types. --- .../element-api/calling-javascript.adoc | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index 926ec3fb39..cef3c9ba5b 100644 --- a/articles/flow/component-internals/element-api/calling-javascript.adoc +++ b/articles/flow/component-internals/element-api/calling-javascript.adoc @@ -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`, and `JsFunction` (see <<#js-function,Passing JavaScript Functions>>). .Calling `MyModule.complete(true)` on the client side [example] @@ -95,6 +95,81 @@ Always pass arguments using the `$0`, `$1`, ... notation to avoid script injecti If you need to run JavaScript without having access to an element, use the [methodname]`UI.getCurrent().getPage().executeJs()` method. +[[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, including `Element`. An attached element arrives on the client 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 materialized function accepts at call time. The body references them by name, and the caller supplies them positionally after the captured values: + +.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` + +Inside the body, `this` follows normal JavaScript call semantics – it isn't bound to the host element. The caller of the materialized function chooses the receiver: + +.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); +---- +==== + +To use a specific element from within the body regardless of how the function is called, pass it as a capture instead of relying on `this`. + + == Return Values The return value from the JavaScript function called using [methodname]`callJsFunction()`, or the value from a `return` statement in an `executeJs()` expression can be accessed by adding a listener to the [classname]`PendingJavaScriptResult` instance returned from either method. From 2c3626e2e24bc64ab8bf560b98f4a56e3a3fcbb5 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 19 May 2026 18:55:56 +0000 Subject: [PATCH 2/2] docs: address review feedback on JsFunction section - Add JsFunction to callJsFunction supported types - Add Component to executeJs supported types - Drop the arbitrary "including Element" qualifier on captures - Reword the runtime-arguments paragraph so "caller" refers to the invoking JavaScript rather than implying captures are supplied at call time - Expand the "this" subsection for readers unfamiliar with how JavaScript binds this and how Function.prototype.call() works - Move JsFunction section to after Return Values, since Return Values is the more commonly used feature --- .../element-api/calling-javascript.adoc | 152 +++++++++--------- 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/articles/flow/component-internals/element-api/calling-javascript.adoc b/articles/flow/component-internals/element-api/calling-javascript.adoc index cef3c9ba5b..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`, `Element`, and `JsFunction` (see <<#js-function,Passing JavaScript Functions>>). +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] @@ -95,6 +95,75 @@ Always pass arguments using the `$0`, `$1`, ... notation to avoid script injecti If you need to run JavaScript without having access to an element, use the [methodname]`UI.getCurrent().getPage().executeJs()` method. +== Return Values + +The return value from the JavaScript function called using [methodname]`callJsFunction()`, or the value from a `return` statement in an `executeJs()` expression can be accessed by adding a listener to the [classname]`PendingJavaScriptResult` instance returned from either method. + +.Checking for support of Constructable Stylesheets in the browser +[example] +==== +[source,java] +---- +public void checkConstructableStylesheets() { + getElement().executeJs( + "return 'adoptedStyleSheets' in document") + .then(Boolean.class, supported -> { + if (supported) { + System.out.println( + "Feature is supported"); + } else { + System.out.println( + "Feature isn't supported"); + } + }); +} +---- +==== + +If the return value is a JavaScript `Promise`, the client only sends the return value to the server when the `Promise` is resolved. +// TODO What happens if the Promise is rejected? + + +=== Deserializing Generic Types with TypeReference + +The [methodname]`then()` method accepts a [classname]`Class` parameter for simple types, but this doesn't work for generic types like `List` due to Java type erasure. For these cases, use the Jackson [classname]`TypeReference` overloads. + +.Deserializing a JavaScript array into a `List` +[example] +==== +[source,java] +---- +getElement().executeJs("return this.getItems()") + .then(new TypeReference>() {}, + items -> { + // items is List + items.forEach(person -> + System.out.println(person.getName())); + }); +---- +==== + +An error handler can be provided as a second callback. The handler receives the error message from the failed JavaScript execution as a `String`: + +[source,java] +---- +getElement().executeJs("return this.getItems()") + .then(new TypeReference>() {}, + items -> processItems(items), + errorMessage -> handleError(errorMessage)); +---- + +You can also use [methodname]`toCompletableFuture(TypeReference)` to get the result as a [classname]`CompletableFuture`: + +[source,java] +---- +CompletableFuture> future = getElement() + .executeJs("return this.getPersonMap()") + .toCompletableFuture( + new TypeReference>() {}); +---- + + [[js-function]] == Passing JavaScript Functions [since:com.vaadin:vaadin@V25.2] @@ -114,7 +183,7 @@ getElement().executeJs( ---- ==== -Captures may be any value supported as an [methodname]`executeJs()` parameter, including `Element`. An attached element arrives on the client as the corresponding DOM node; a detached element arrives as `null`. +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] @@ -134,7 +203,7 @@ getElement().executeJs("$0();", mutate); === Declaring Runtime Arguments -Use [methodname]`withArguments()` to declare named parameters that the materialized function accepts at call time. The body references them by name, and the caller supplies them positionally after the captured values: +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] @@ -152,7 +221,9 @@ getElement().executeJs( === Controlling `this` -Inside the body, `this` follows normal JavaScript call semantics – it isn't bound to the host element. The caller of the materialized function chooses the receiver: +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] @@ -167,76 +238,7 @@ getElement().executeJs( ---- ==== -To use a specific element from within the body regardless of how the function is called, pass it as a capture instead of relying on `this`. - - -== Return Values - -The return value from the JavaScript function called using [methodname]`callJsFunction()`, or the value from a `return` statement in an `executeJs()` expression can be accessed by adding a listener to the [classname]`PendingJavaScriptResult` instance returned from either method. - -.Checking for support of Constructable Stylesheets in the browser -[example] -==== -[source,java] ----- -public void checkConstructableStylesheets() { - getElement().executeJs( - "return 'adoptedStyleSheets' in document") - .then(Boolean.class, supported -> { - if (supported) { - System.out.println( - "Feature is supported"); - } else { - System.out.println( - "Feature isn't supported"); - } - }); -} ----- -==== - -If the return value is a JavaScript `Promise`, the client only sends the return value to the server when the `Promise` is resolved. -// TODO What happens if the Promise is rejected? - - -=== Deserializing Generic Types with TypeReference - -The [methodname]`then()` method accepts a [classname]`Class` parameter for simple types, but this doesn't work for generic types like `List` due to Java type erasure. For these cases, use the Jackson [classname]`TypeReference` overloads. - -.Deserializing a JavaScript array into a `List` -[example] -==== -[source,java] ----- -getElement().executeJs("return this.getItems()") - .then(new TypeReference>() {}, - items -> { - // items is List - items.forEach(person -> - System.out.println(person.getName())); - }); ----- -==== - -An error handler can be provided as a second callback. The handler receives the error message from the failed JavaScript execution as a `String`: - -[source,java] ----- -getElement().executeJs("return this.getItems()") - .then(new TypeReference>() {}, - items -> processItems(items), - errorMessage -> handleError(errorMessage)); ----- - -You can also use [methodname]`toCompletableFuture(TypeReference)` to get the result as a [classname]`CompletableFuture`: - -[source,java] ----- -CompletableFuture> future = getElement() - .executeJs("return this.getPersonMap()") - .toCompletableFuture( - new TypeReference>() {}); ----- +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`