diff --git a/docs/cookbook/browser_interaction/timer.md b/docs/cookbook/browser_interaction/timer.md index 2e3d7f6d..f5ac8f86 100644 --- a/docs/cookbook/browser_interaction/timer.md +++ b/docs/cookbook/browser_interaction/timer.md @@ -18,6 +18,8 @@ client->follow_up_action( After 2 seconds the browser triggers a backend roundtrip with the event name `REFRESH`, which you handle via `check_on_event` like any other event. +An optional third argument keeps the busy indicator down for the tick — see [Without the Busy Indicator](#without-the-busy-indicator). + ## Periodic Refresh To get a repeating timer, simply re-arm it at the end of each handler: @@ -86,6 +88,21 @@ WHEN client->check_on_event( `FIRE_OPEN_TAB` ). There is one timer at a time. Calling `start_timer` again before the previous one fires replaces it — useful for a debounce, e.g. auto-saving an input field 500 ms after the last keystroke. +## Without the Busy Indicator + +Each tick is a backend roundtrip like any other, so when the backend takes longer than a second, the full-screen busy indicator comes up. For a tick nobody is waiting on — a background poll, or a carousel or ticker that moves on by itself — the overlay then flashes over a screen the user is only reading. Pass `` `X` `` as a third argument to keep it down for that tick: + +```abap +WHEN client->check_on_event( `TICK` ). + " read the new status ... + client->follow_up_action( val = client->cs_event-start_timer + t_arg = VALUE #( ( `TICK` ) ( `5000` ) ( `X` ) ) ). +``` + +The flag belongs to the tick that call arms, so a repeating timer passes it again every time it re-arms. Write it as the string `` `X` ``, not `abap_true`: `t_arg` is a string table, and abaplint's syntax check rejects a `c` value such as `abap_true` as one of its rows. + +Only the overlay goes — the flag does for the tick what [`check_no_busy`](/resources/api#event) does for an `_event( )` wire. The tick is still the one roundtrip in flight: a click that lands while it runs is dropped, as during any roundtrip, and brings the overlay up at once. Leave the flag off for a tick the user is actually waiting for; there the overlay is what tells them the app is working. + ::: warning Each timer tick causes a full backend roundtrip. Use sensible intervals (e.g. 2000 ms or more) to avoid heavy server load. ::: diff --git a/docs/public/api/client-api.json b/docs/public/api/client-api.json index 2594f028..5364ba5b 100644 --- a/docs/public/api/client-api.json +++ b/docs/public/api/client-api.json @@ -550,6 +550,7 @@ "**cs_event-smart_variant_init** - run the initialise( ) handshake sap.ui.comp variant management needs (a controller would call oSmartVariantManagement.initialise( fnCallback, oPersonalizableControl )), t_arg = SmartVariantManagement id, personalizable control id (optional, default: the first control that registered itself): ``client->follow_up_action( val = client->cs_event-smart_variant_init t_arg = VALUE #( ( `pageVariant` ) ) )``. Without it the control keeps no personalizable control, saving a view fails inside sap.ui.fl and stored variants are never loaded. The action waits for that registration, which the smart controls do once their OData metadata has loaded.", "**cs_event-filter_bar_variant_init** - wire a classic sap.ui.comp.filterbar.FilterBar to a SmartVariantManagement, t_arg = SmartVariantManagement id, FilterBar id: ``client->follow_up_action( val = client->cs_event-filter_bar_variant_init t_arg = VALUE #( ( `variant` ) ( `filterbar` ) ) )``. A SmartFilterBar knows its own fields and registers itself (see smart_variant_init above); a classic FilterBar does not, so a list report normally hand-writes the same controller boilerplate - registerFetchData / registerApplyData / registerGetFiltersWithValues, addPersonalizableControl( ) with a PersonalizableInfo, and a change handler per filter field that marks the variant as modified. This action does all of it, so saving, selecting and restoring a variant works without a single line of JavaScript. The restored values reach the backend through the binding of the filter fields, no extra roundtrip needed.", "**cs_event-keyboard_shortcut** - bind a key combination to a named backend event, the declarative equivalent of a sap.ui.core.CommandExecution shortcut, t_arg = combination, event name: ``client->follow_up_action( val = client->cs_event-keyboard_shortcut t_arg = VALUE #( ( `Ctrl+S` ) ( `SAVE` ) ) )``. The combination is spelled like the UI5 one (`Ctrl+S`, `Ctrl+Shift+D`, `F2`; ctrl/shift/alt/meta in any order, cmd/command/option/control accepted as aliases). Pressing it fires the event exactly like a button press and suppresses the browser's own default for the combination. Registering the same combination again rebinds it; an empty event name removes it. The registrations belong to the running app and are dropped when another app takes over. An optional THIRD t_arg SCOPES the shortcut: the scoped registration wins while its scope is OPEN and the unscoped one applies otherwise, which is how a UI5 CommandExecution in a Popover's dependents shadows the page-level one for the same command. A scope is either a view slot (cs_view-popover/popup/nested/nested2/main) or the ID OF A CONTROL that can be open or closed - a Popover/Dialog declared in the view and opened with control_by_id openBy, which never enters a framework slot. A control scope beats a slot scope (it is the more specific statement), then the innermost open slot wins. An empty event name removes the registration of THAT scope only.", + "**cs_event-start_timer** - raise a BACKEND event once after a delay, t_arg = event name, delay in milliseconds: ``client->follow_up_action( val = client->cs_event-start_timer t_arg = VALUE #( ( `TICK` ) ( `5000` ) ) )``. There is one timer per app: arming it again replaces the pending one, and every roundtrip cancels it, so a repeating timer - a poll, a carousel - is re-armed by every response that keeps it going. A tick that meets a roundtrip in flight waits for it instead of being dropped. The tick is a roundtrip like any other and raises the global busy indicator after the usual one-second delay; an optional THIRD t_arg `X` (abap_bool as `X`/``, like every flag in t_arg) keeps it down - the check_no_busy of _event( ) for the tick, so a poll whose backend call takes longer than that no longer flashes the full-screen overlay: ``client->follow_up_action( val = client->cs_event-start_timer t_arg = VALUE #( ( `TICK` ) ( `5000` ) ( `X` ) ) )``. As with check_no_busy only the overlay goes: the tick is still the one roundtrip in flight, and a click that lands meanwhile is dropped by the busy guard and raises the overlay at once, like every dropped click.", "**cs_event-hash_attach_changed** - APP-OWNED hash routing (HashChanger#attachHashChanged), the 1:1 counterpart of a UI5 router's own hash (`#/Page2`) for an app that does NOT use hash_routing, t_arg = a backend event name: ``client->follow_up_action( val = client->cs_event-hash_attach_changed t_arg = VALUE #( ( `HASH_CHANGED` ) ) )``. From then on hash_set( `/Page2` ) writes that value as the whole app hash (a pushed history entry), hash_replace( ) the same without a new entry, and a hash change the app did not write itself - browser Back/Forward, a manual URL edit - fires the registered event; the hash the browser now stands on arrives with that request (and with every other one, a fresh deep-link start included) in get( )-s_config-hash, so the app decides what to show. While registered, the framework leaves the hash entirely alone. Calling it without t_arg unregisters. The registration dies with an app switch - register it in view_display( ), so every render (a draft restore included) re-asserts it. Mutually exclusive with hash_routing (a routed app's hash belongs to the router) and with app_state_set_active (both claim the whole hash).", "**cs_event-hash_back** - the UI5 onNavBack pattern: without t_arg one real step back in the browser history (`window.history.go(-1)` - the step is CONSUMED, and the resulting hash change fires the registered event). With t_arg = a fallback hash it guards the cold deep link the way UI5's recommended onNavBack does: when this page load never pushed an app hash, there is no in-app step to take, so the fallback is written as a REPLACE instead of falling out of the app - and the change fires the registered event, which shows the fallback route: ``client->follow_up_action( val = client->cs_event-hash_back t_arg = VALUE #( ( `/` ) ) )``.", "**cs_event-store_data** - write a structure into the browser's local or session storage, the write half of the invisible z2ui5:Storage control that reads it back. t_arg = the structure holding TYPE (`local`/`session`), PREFIX, KEY and VALUE - handed over as the BINDING of that structure, so the frontend takes the current value: ``client->follow_up_action( val = client->cs_event-store_data t_arg = VALUE #( ( |${ client->_bind( ms_storage ) }| ) ) )``. It works from a view wire and from a handler alike: on a wire UI5 resolves the binding when the view is built, and a follow-up action queued in a handler carries the model PATH, which the frontend resolves when it runs. The ``$`` is what makes the wire form work: a bare ``{/MS_STORAGE}`` travels raw into the handler expression, where UI5 reads it as an object literal and the wire never fires - only ``${ }`` is a binding there. A handler resolves both spellings. An empty VALUE removes the key.", diff --git a/docs/resources/api.md b/docs/resources/api.md index 1e45205e..f985ad46 100644 --- a/docs/resources/api.md +++ b/docs/resources/api.md @@ -205,6 +205,8 @@ Every cs_event-* action also works roundtrip-free when WIRED IN THE VIEW: write **cs_event-keyboard_shortcut** - bind a key combination to a named backend event, the declarative equivalent of a sap.ui.core.CommandExecution shortcut, t_arg = combination, event name: ``client->follow_up_action( val = client->cs_event-keyboard_shortcut t_arg = VALUE #( ( `Ctrl+S` ) ( `SAVE` ) ) )``. The combination is spelled like the UI5 one (`Ctrl+S`, `Ctrl+Shift+D`, `F2`; ctrl/shift/alt/meta in any order, cmd/command/option/control accepted as aliases). Pressing it fires the event exactly like a button press and suppresses the browser's own default for the combination. Registering the same combination again rebinds it; an empty event name removes it. The registrations belong to the running app and are dropped when another app takes over. An optional THIRD t_arg SCOPES the shortcut: the scoped registration wins while its scope is OPEN and the unscoped one applies otherwise, which is how a UI5 CommandExecution in a Popover's dependents shadows the page-level one for the same command. A scope is either a view slot (cs_view-popover/popup/nested/nested2/main) or the ID OF A CONTROL that can be open or closed - a Popover/Dialog declared in the view and opened with control_by_id openBy, which never enters a framework slot. A control scope beats a slot scope (it is the more specific statement), then the innermost open slot wins. An empty event name removes the registration of THAT scope only. +**cs_event-start_timer** - raise a BACKEND event once after a delay, t_arg = event name, delay in milliseconds: ``client->follow_up_action( val = client->cs_event-start_timer t_arg = VALUE #( ( `TICK` ) ( `5000` ) ) )``. There is one timer per app: arming it again replaces the pending one, and every roundtrip cancels it, so a repeating timer - a poll, a carousel - is re-armed by every response that keeps it going. A tick that meets a roundtrip in flight waits for it instead of being dropped. The tick is a roundtrip like any other and raises the global busy indicator after the usual one-second delay; an optional THIRD t_arg `X` (abap_bool as `X`/``, like every flag in t_arg) keeps it down - the check_no_busy of _event( ) for the tick, so a poll whose backend call takes longer than that no longer flashes the full-screen overlay: ``client->follow_up_action( val = client->cs_event-start_timer t_arg = VALUE #( ( `TICK` ) ( `5000` ) ( `X` ) ) )``. As with check_no_busy only the overlay goes: the tick is still the one roundtrip in flight, and a click that lands meanwhile is dropped by the busy guard and raises the overlay at once, like every dropped click. + **cs_event-hash_attach_changed** - APP-OWNED hash routing (HashChanger#attachHashChanged), the 1:1 counterpart of a UI5 router's own hash (`#/Page2`) for an app that does NOT use hash_routing, t_arg = a backend event name: ``client->follow_up_action( val = client->cs_event-hash_attach_changed t_arg = VALUE #( ( `HASH_CHANGED` ) ) )``. From then on hash_set( `/Page2` ) writes that value as the whole app hash (a pushed history entry), hash_replace( ) the same without a new entry, and a hash change the app did not write itself - browser Back/Forward, a manual URL edit - fires the registered event; the hash the browser now stands on arrives with that request (and with every other one, a fresh deep-link start included) in get( )-s_config-hash, so the app decides what to show. While registered, the framework leaves the hash entirely alone. Calling it without t_arg unregisters. The registration dies with an app switch - register it in view_display( ), so every render (a draft restore included) re-asserts it. Mutually exclusive with hash_routing (a routed app's hash belongs to the router) and with app_state_set_active (both claim the whole hash). **cs_event-hash_back** - the UI5 onNavBack pattern: without t_arg one real step back in the browser history (`window.history.go(-1)` - the step is CONSUMED, and the resulting hash change fires the registered event). With t_arg = a fallback hash it guards the cold deep link the way UI5's recommended onNavBack does: when this page load never pushed an app hash, there is no in-app step to take, so the fallback is written as a REPLACE instead of falling out of the app - and the change fires the registered event, which shows the fallback route: ``client->follow_up_action( val = client->cs_event-hash_back t_arg = VALUE #( ( `/` ) ) )``. diff --git a/docs/resources/deprecations.md b/docs/resources/deprecations.md index 12b27dd3..ef01ceab 100644 --- a/docs/resources/deprecations.md +++ b/docs/resources/deprecations.md @@ -442,6 +442,11 @@ The controls still ship and views that use them keep rendering. See [Frontend](/cookbook/event_navigation/frontend) for the full argument list of each event. +Migrating a `Timer` whose `finished` wire kept the busy indicator down with +`s_ctrl-check_no_busy`: `cs_event-start_timer` takes the same flag as its third +argument, `` `X` `` (*next release*) — see +[Timer](/cookbook/browser_interaction/timer#without-the-busy-indicator). + ### `cs_config-title` → `cs_event-set_title` The page title used to be set in the user exit and the tab title while the app