Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions articles/flow/browser-apis/clipboard-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ The [classname]`Clipboard` API lets server-side Java code copy text, HTML, and i
The Clipboard API requires a secure context (HTTPS), except on `localhost` during development. Browsers also require the [code]`write` call to happen inside a short-lived user gesture (click, key press, …) -- writes triggered outside of a gesture, for example from a background thread or a timer, will fail with a [code]`NotAllowedError`.


[#how-the-binding-works]
== How the Click Binding Works

[methodname]`Clipboard.onClick(component)` does not register a click listener. It _arms_ a clipboard action that the browser runs synchronously inside the click event, while the user gesture is active -- a browser requirement for clipboard writes. The action must therefore be assigned during the same server request that creates the binding.

Chain the action method directly onto [methodname]`onClick(...)`. The [methodname]`write*` and [methodname]`read*` methods return [code]`void`, so there is nothing to keep:

[source,java]
----
Button copy = new Button("Copy");
Clipboard.onClick(copy).writeText("Hello, world!");
----

Assigning the action later -- for example storing the [classname]`ClipboardBinding` and calling [methodname]`writeText()` from a separate [methodname]`addClickListener` callback -- arms the binding one request too late, after the gesture window has closed. A binding left with no action does nothing on the client, so the framework throws an [classname]`IllegalStateException` when the response is built.

The same arming model applies to the <<{articles}/flow/browser-apis/fullscreen-api#,Fullscreen>> and <<{articles}/flow/browser-apis/web-share-api#,Web Share>> APIs, which share the user-gesture requirement.


== Copying Text

[methodname]`Clipboard.onClick(component)` returns a [classname]`ClipboardBinding` -- a fluent surface that attaches a clipboard action to the component's [code]`click` event. The typical "Copy to clipboard" button is one line:
Expand All @@ -28,6 +46,13 @@ Clipboard.onClick(copy).writeText("Hello, world!");

Every subsequent click of the button copies the literal string to the clipboard. The call returns immediately and does not subscribe to the result -- see <<#observing-the-outcome,Observing the Outcome>> if you need to know whether the write succeeded.

[IMPORTANT]
====
A literal string is captured *when the action is armed*, not when the click fires. Building it from a value that changes -- for example concatenating [methodname]`LocalTime.now()` into the text -- therefore copies a stale, build-time snapshot on every click, frozen at the moment the action was armed.

To copy a value that changes, drive it through a component and pass the component (not a string) to [methodname]`writeText()` -- the value is then read on the client at click time, as shown next. Pre-arming has no equivalent for content that must be computed at the exact instant of the click; route such values through a field your server keeps up to date.
====


== Copying a Field Value

Expand Down
29 changes: 29 additions & 0 deletions articles/flow/browser-apis/fullscreen-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ Button fullscreenButton = new Button("Fullscreen");
Fullscreen.onClick(fullscreenButton).enter();
----

[IMPORTANT]
====
[methodname]`onClick(...)` _arms_ the request; it does not register a click listener you fill in later. The request must run inside the browser's gesture window, so it has to be armed up front -- calling [methodname]`enter()` from an [methodname]`addClickListener` callback runs one round-trip too late and the browser rejects it.

Forgetting [methodname]`enter()` altogether -- a binding created with no action -- fails loudly instead: the framework throws an [classname]`IllegalStateException` when the view renders, naming the unarmed binding.

[methodname]`exit()` is different: it needs no gesture and so _is_ called from a normal handler. See <<{articles}/flow/browser-apis/clipboard-api#how-the-binding-works,How the Click Binding Works>> in the Clipboard article for the full explanation of this arm-don't-handle model.
====

To fullscreen a single component instead of the whole page, pass it to [methodname]`enter(Component)`:

[source,java]
Expand All @@ -51,6 +60,26 @@ Button closeButton = new Button("Close", e -> Fullscreen.exit());
The call is a no-op when the page isn't currently fullscreen. The user can also press kbd:[Esc] -- the browser handles that itself, and the state signal updates accordingly.


== Toggling Fullscreen

"Click to enter, click to exit" is a natural request, but a single button can't both _arm_ an enter request for the gesture window and decide at click time to exit instead -- the two needs pull in opposite directions. The canonical pattern keeps them separate: always arm [methodname]`enter()`, let the user leave with kbd:[Esc] or a control wired to [methodname]`Fullscreen.exit()`, and drive the visible affordance from [methodname]`stateSignal()` so the right control shows in each state:

[source,java]
----
Button enter = new Button("Fullscreen");
Fullscreen.onClick(enter).enter(videoPanel);

Button exit = new Button("Exit fullscreen", e -> Fullscreen.exit());

Signal<Boolean> isFullscreen = Fullscreen.stateSignal()
.map(state -> state == FullscreenState.FULLSCREEN);
enter.bindVisible(isFullscreen.map(on -> !on));
exit.bindVisible(isFullscreen);
----

Because [methodname]`exit()` needs no gesture, the exit control is an ordinary click handler -- only the enter side has to be armed. Pressing kbd:[Esc] reaches the same state through [methodname]`stateSignal()`, so the buttons stay in sync however the user leaves fullscreen.


== Observing Fullscreen State

[methodname]`Fullscreen.stateSignal()` returns a read-only signal of the current fullscreen state. Use it to drive UI that depends on whether the page is fullscreen, or to hide controls in browsers that don't support fullscreen at all:
Expand Down
3 changes: 3 additions & 0 deletions articles/flow/browser-apis/web-share-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ WebShare.onClick(shareButton).share(

The trigger component must implement [interfacename]`ClickNotifier`, which covers buttons and most other clickable components.

[IMPORTANT]
[methodname]`onClick(...)` _arms_ the share action; it does not register a click listener you fill in later. The share sheet must open inside the browser's gesture window, so the action has to be armed up front -- calling [methodname]`share()` from an [methodname]`addClickListener` callback runs one round-trip too late and the browser rejects it. Forgetting [methodname]`share()` altogether -- a binding created with no action -- fails loudly instead: the framework throws an [classname]`IllegalStateException` when the view renders. See <<{articles}/flow/browser-apis/clipboard-api#how-the-binding-works,How the Click Binding Works>> in the Clipboard article for the full explanation of this arm-don't-handle model.


== Building Share Content

Expand Down
Loading