Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/public/api/client-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@
"**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-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. An empty VALUE removes the key.",
"**cs_event-binding_call** - apply a declarative filter or sorter to an aggregation binding, the client-side equivalent of the UI5 controller pattern getBinding('items').filter(...); the model data stays untouched. t_arg = id, aggregation, method, params. Method `filter`: params = path, operator, value1, value2 (empty values clear the filter); method `sort`: params = path, descending, group (abap_bool as `X`/``): ``client->follow_up_action( val = client->cs_event-binding_call t_arg = VALUE #( ( `tab` ) ( `items` ) ( `filter` ) ( `NAME` ) ( `Contains` ) ( `ab` ) ) )``."
],
"parameters": [
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ Every one of them also works roundtrip-free when WIRED IN THE VIEW: write the sa

**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. An empty VALUE removes the key.

**cs_event-binding_call** - apply a declarative filter or sorter to an aggregation binding, the client-side equivalent of the UI5 controller pattern getBinding('items').filter(...); the model data stays untouched. t_arg = id, aggregation, method, params. Method `filter`: params = path, operator, value1, value2 (empty values clear the filter); method `sort`: params = path, descending, group (abap_bool as `X`/``): ``client->follow_up_action( val = client->cs_event-binding_call t_arg = VALUE #( ( `tab` ) ( `items` ) ( `filter` ) ( `NAME` ) ( `Contains` ) ( `ab` ) ) )``.

| Parameter | Type | Default | Description |
Expand Down
70 changes: 62 additions & 8 deletions docs/resources/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ replacement has a sample that proves it:

| What you used AJSON for | What to write instead | Sample |
|---|---|---|
| Drop initial fields so the UI5 default applies (`create_empty_filter`) | `omit_initial`, or `omit_initial_paths` for single columns | [`Z2UI5_CL_SMP_APP_507`](https://github.com/abap2UI5/samples/blob/main/src/00/z2ui5_cl_smp_app_507.clas.abap) |
| Get a model **node** instead of a quoted string — under keys no ABAP component could be named after | `json = abap_true` | [`Z2UI5_CL_SMP_APP_509`](https://github.com/abap2UI5/samples/blob/main/src/00/z2ui5_cl_smp_app_509.clas.abap) |
| Drop initial fields so the UI5 default applies (`create_empty_filter`) | `omit_initial`, or `omit_initial_paths` for single columns | [`Z2UI5_CL_SMP_APP_507`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_507.clas.abap) |
| Get a model **node** instead of a quoted string — under keys no ABAP component could be named after | `json = abap_true` | [`Z2UI5_CL_SMP_APP_509`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_509.clas.abap) |
| Anything else | Shape the value in ABAP before you bind it | — |

```abap
Expand Down Expand Up @@ -378,14 +378,68 @@ still work; treat them as stable-but-closed rather than as something to migrate
away from today. No page of this documentation uses them any more: what the
examples reached them for is SAP standard — `cl_web_http_utility` for base64
(`cl_http_utility=>if_http_utility~encode_x_base64( )` on older releases), and
a failing library raises its own exception instead of being wrapped. JSON is
built and read by hand: compose it as a string in ABAP and bind it with
`_bind( val = ... json = abap_true )` on the way out, and read the one field
you need with `find` / `substring_before` on the way in — the payloads that
reach an app are written by the framework and are flat. There is deliberately
no released parser.
a failing library raises its own exception instead of being wrapped. For the
JSON they were also reached for, see the section below.
:::

### JSON is built and read by hand

**There is deliberately no released JSON parser or writer, and there is no
substitute to reach for.** `/ui2/cl_json` is not released for ABAP Cloud,
`xco_cp_json` is missing on 7.02, and the bundled [ajson](/technical/tools/ajson)
is the framework's model engine — a mirrored copy of an external project, which
the abap2UI5 linter reports as `non-released-api` when an app names it, and it
is right to.

There was a released reader, `z2ui5_cl_ui5_json`, for two weeks in September
2026. It was removed again before it had shipped in any release, so no
installation ever had it and nothing has to be migrated away from it.

**Outbound — a control property that must receive an object.** A
`sap.ui.integration` Card manifest is the case this exists for: its keys
(`sap.app`, `sap.card`) are not valid ABAP field names, so no typed ABAP value
can be that object, and UI5 reads a *string* manifest as a manifest URL. Keep
the JSON in a `string` attribute, compose it in ABAP, and bind it as a node:

```abap
client->_bind( val = mv_card_manifest json = abap_true )
```

A string that does not parse raises rather than shipping broken JSON to the
frontend. This direction is outbound only — the attribute is not read back, so
ABAP stays the single author of it. Where the payload has a URL instead, bind
that; it needs no flag.

**Inbound — an event argument that arrives as JSON.** Write the few lines that
read the field you need. `Z2UI5_CL_SMP_APP_327`
([`json_get_value`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_327.clas.abap),
one field of a flat object) and `Z2UI5_CL_SMP_APP_197`
([`json_get_values`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_197.clas.abap),
one property across an array of objects) are the pattern to copy: find
`"<name>":"` and take what stands up to the next quote.

Two limits worth knowing before you copy it:

- **It is a reader for FLAT payloads.** A control-valued event parameter is
marshaled as an object of the control's `ID` plus its public properties, and
a property whose value is itself an object or an array travels as one — the
frontend passes such a value through rather than flattening it. A `find` walk
answers the flat case; a nested one is a sign to bind the value into the model
instead of parsing it out of an event argument, which is what a two-way bound
attribute does for you with no parsing at all.
- **A payload composed from user input needs escaping on both ends.** The
samples above read what the *framework* wrote. If your own app also writes the
JSON — publishing into an AMC channel, say — then a quote the user typed
reaches the reader as `\"`, and a `substring_before` on the next quote ends
the value early.
`Z2UI5_CL_SMPS_APP_489`
([samples-stack](https://github.com/abap2UI5/samples-stack/blob/main/src/07/z2ui5_cl_smps_app_489.clas.abap))
is that case written out: a writer that escapes and a reader that walks the
value resolving escapes.

An app parsing genuinely arbitrary, nested JSON is doing something this
framework does not hand it a tool for.

### Invisible custom controls

Earlier versions of abap2UI5 needed an invisible helper control in the view for
Expand Down
20 changes: 11 additions & 9 deletions docs/technical/tools/ajson.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ The solution: [ajson](https://github.com/sbcgua/ajson), a solid open-source JSON
- **Developer-Friendly** — Clear API with thorough documentation
- **Active Maintenance** — Responsive issue resolution and ongoing improvement

## Integration
## It is the framework's engine, not an API for your app

ajson plugs directly into the `z2ui5` namespace and installs with abap2UI5 automatically. Use it anywhere in your code:
```abap
DATA(input) = z2ui5_cl_ajson=>parse( json_string ).
ajson is what serializes the model on every roundtrip and what reads the delta back — `z2ui5_cl_ui5_srv_model` is built on it, and so is `_bind( json = abap_true )`. That is where it belongs.

DATA(output) = z2ui5_cl_ajson=>create_empty( )->set(
iv_path = `/name`
iv_val = `value`
)->stringify( ).
```
What ships under `z2ui5_cl_ajson` is a **mirrored copy** of an external project (`src/00/01`), not a contract abap2UI5 owns. It sits outside the released API, it is resynced from upstream automatically (see below), and a resync is free to change it. An app that calls it binds itself to whatever the mirror looks like today — which is why the abap2UI5 linter reports such a call as `non-released-api`, correctly.

::: warning Do not call it from app code
Nothing in an app class should name `z2ui5_cl_ajson`, `z2ui5_cx_ajson_error`, `z2ui5_if_ajson_mapping` or `z2ui5_if_ajson_filter`. There is no released JSON parser to use instead, and that is deliberate — see [JSON is built and read by hand](/resources/deprecations#json-is-built-and-read-by-hand) for what to write in both directions.
:::

Nor is there a substitute to reach for: `/ui2/cl_json` is not released for ABAP Cloud and `xco_cp_json` is missing on 7.02 — the two reasons the framework vendored a library in the first place.

## Automatic Updates

Every ajson update and bug fix flows into abap2UI5 automatically via GitHub Actions and the [mirror-ajson](https://github.com/abap2UI5/mirror-ajson) repository. You always run the latest stable version — no manual steps needed.

That automation is the other half of the argument above: the copy in your system moves without anybody in this project reviewing the diff for app-facing impact, because nothing app-facing is supposed to depend on it.