# abap2UI5 — the complete documentation
Every page of https://abap2ui5.github.io/docs, in sidebar order, as one document.
Each chapter is preceded by an HTML comment with its canonical URL.
---
# Introduction
**Build UI5 Apps Purely in ABAP**
abap2UI5 is an open-source framework that brings the simplicity of classic ABAP development to modern UI5 apps. Just as Selection Screens and ALV grids let you build working UIs with only a few lines of ABAP, abap2UI5 brings that same simplicity to modern web apps:
```abap
CLASS zcl_my_app DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
ENDCLASS.
CLASS zcl_my_app IMPLEMENTATION.
METHOD z2ui5_if_app~main.
client->message_box_display( `Hello World` ).
ENDMETHOD.
ENDCLASS.
```
That's it — your first UI5 app is ready. (`client` is the single parameter of `main`, passed in by the framework — explained on the [Hello World](/get_started/hello_world) page.)
## Background
Since launching in 2023, abap2UI5 has grown from a small side project into a community-driven framework used by ABAP developers worldwide. The framework absorbs frontend complexity, so you can focus on business logic with your existing ABAP skills.
→ *See the [Getting Started Guide](/get_started/quickstart) for step-by-step setup*
→ *See [Sample Apps](/get_started/next#sample-apps) to watch abap2UI5 in action*
## Why abap2UI5?
Traditional UI5 development needs JavaScript expertise, frontend deployment, and OData service setup. abap2UI5 cuts out those complexities:
- **Use your existing ABAP skills** — do what you do best; no frontend expertise needed
- **Broad compatibility** — build apps that run on legacy R/3 systems and modern S/4 Cloud environments
- **Extend beyond RAP** — build UIs for cases RAP does not cover, such as free-style screens, custom flows, or non-CDS data
- **Prototype fast** — iterate rapidly on business apps
Each app ships as an [abapGit](https://abapgit.org) project, so installation across systems needs no separate frontend deployment.
## Overview
### Architecture
abap2UI5 takes a "thin frontend" approach — all processing, logic, and data handling stay in the backend. This design simplifies configuration, cuts client-side complexity (no more cache-clearing headaches), and keeps business logic and sensitive data safely on the server.
### Performance
abap2UI5 is fast. The frontend focuses only on UI rendering via the UI5 framework, while the ABAP backend handles all processing. Unlike traditional UI5 apps that need separate OData calls for each view, abap2UI5 embeds data directly in XML views — cutting network roundtrips and speeding up the response.
### Security
abap2UI5 is secure by design. All business logic stays in the ABAP backend. The frontend receives only the data the backend embeds directly in XML views. Unlike traditional UI5 apps that expose OData endpoints, abap2UI5 delivers only what users need — no access to raw services or database queries from external tools.
### System Footprint
The framework has a small system footprint — essentially classes, interfaces, and a single draft table. The core stays minimal; optional add-ons provide extra functionality only when you need it.
### Transparency
All source code lives publicly on GitHub. We discuss features and issues openly, publish technical blog posts that explain key concepts, and ship fixes quickly via abapGit. You'll always understand how the framework works and can confirm its behavior.
## Compatibility
### ABAP Cloud
abap2UI5 uses only released APIs, making it a strong fit for on-stack and side-by-side extensions on ABAP for Cloud. Use ABAP syntax features like CDS, ABAP SQL, and EML inside your apps.
### Clean Core
By relying only on released APIs, abap2UI5 keeps your apps "cloud-ready" and "upgrade-stable," in line with SAP's clean-core principles. Your investment in abap2UI5 apps stays safe through future SAP system upgrades.
### System Support
Works with both ABAP Cloud and Standard ABAP:
- S/4 Public Cloud and BTP ABAP Environment (ABAP for Cloud)
- S/4 Private Cloud or On-Premise (ABAP for Cloud, Standard ABAP)
- R/3 NetWeaver AS ABAP 7.50 or later (Standard ABAP)
For systems on releases before 7.50 (down to 7.02), a separate downported version is available.
## Enterprise Ready
abap2UI5 combines SAP's UI5 framework with ABAP's backend capabilities, shaped for enterprise SAP environments. It runs smoothly across S/4HANA Public/Private Cloud, BTP ABAP Environment, and NetWeaver systems.
### Production Usage
Use abap2UI5 like any other UI5 app or ABAP program in production. Add the framework and your apps to a transport request to ship them.
### Licensing
Technically, abap2UI5 apps are standard UI5 freestyle apps. License them the same way you license other UI5 apps at your organization. abap2UI5 itself is MIT licensed (free for commercial use).
### Launchpad Integration
Embed your apps into:
- Fiori Launchpads on S/4 On-Premise
- Tiles on S/4 Public Cloud
- Build Work Zone Websites on BTP
### Installation
Getting started is easy:
1. Import the project via abapGit
2. Set up an HTTP service for browser communication
3. Start building!
→ *See the [Quickstart Guide](/get_started/quickstart) for full instructions*
## Community
### Support
The community offers support. Open an issue on GitHub or join the abap2UI5 Slack channel to get help.
→ *See the [Support page](/resources/support) for more options*
### Contribution
Contributions are always welcome. Whether you fix bugs, build features, or improve the docs, every contribution helps the project thrive.
→ *See the [Contribution Guide](/resources/contribution) to learn how to get involved*
### Sponsor
Volunteers maintain abap2UI5. If you or your company benefits from the project, please consider sponsoring it.
→ *Read more about [sponsorship opportunities](/resources/sponsor)*
---
# Quickstart
::: tip No system at hand?
Try abap2UI5 first in the
[**live demo**](https://abap2ui5.github.io/web-abap2UI5-build/): the complete
stack — framework, backend and sample apps — downported, transpiled to
JavaScript and running inside your browser tab, against an in-memory database.
No installation, no SAP system, no login. It is rebuilt daily from `main`, so
what you click there is the current framework. Come back here when you want the
same apps on a real system.
:::
## 1. Installation via abapGit
Install [abap2UI5](https://github.com/abap2UI5/abap2UI5) with [abapGit](https://abapgit.org). (New to abapGit? Install it first — see [abapGit](/technical/tools/abapgit); it's the one-time tool used to pull abap2UI5 into your system.)

::: details ABAP Cloud

:::
## 2. Set Up HTTP Handler and Service
Create a package and define an HTTP handler class. Use the **ABAP** tab for Standard ABAP systems (R/3 NetWeaver, S/4 On-Premise / Private Cloud); use the **ABAP Cloud** tab only on BTP ABAP Environment or S/4 Public Cloud:
::: code-group
```abap [ABAP]
CLASS zcl_my_abap2UI5_http_handler DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_http_extension.
ENDCLASS.
CLASS zcl_my_abap2UI5_http_handler IMPLEMENTATION.
METHOD if_http_extension~handle_request.
z2ui5_cl_ui5_http_handler=>run( server ).
ENDMETHOD.
ENDCLASS.
```
```abap [ABAP Cloud]
CLASS zcl_my_abap2UI5_http_handler DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES if_http_service_extension.
ENDCLASS.
CLASS zcl_my_abap2UI5_http_handler IMPLEMENTATION.
METHOD if_http_service_extension~handle_request.
z2ui5_cl_ui5_http_handler=>run( req = request res = response ).
ENDMETHOD.
ENDCLASS.
```
:::
Next, use transaction `SICF` to create an HTTP service and enter your handler class in the service's **Handler List** tab, then activate the node:


::: details ABAP Cloud
For ABAP Cloud environments, follow the [SAP HTTP service tutorial](https://developers.sap.com/tutorials/abap-environment-create-http-service.html).
:::
::: tip **Security**
abap2UI5 talks only to the HTTP service you define, giving you full control over accessibility, authentication, and other security aspects.
:::
## 3. First Launch
Open the HTTP endpoint in your browser — in `SICF`, right-click your service node and choose **Test Service** (the URL looks like `https://:/sap/bc/`). This startup page is also where you will launch your own apps later:
Press `check` to verify your installation, then launch the bundled test app to confirm everything works. That's it — you can now build your own abap2UI5 apps.
## 4. Your First App
Build a class on your system:
```abap
CLASS zcl_my_app DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
ENDCLASS.
CLASS zcl_my_app IMPLEMENTATION.
METHOD z2ui5_if_app~main.
client->message_box_display( `Hello World` ).
ENDMETHOD.
ENDCLASS.
```
Back on the startup page, enter your class name `ZCL_MY_APP` in the input field and launch it — that's it: you've built your first abap2UI5 app.
::: tip **Naming**
Name your own apps in your customer namespace (`Z...`/`Y...`). The `Z2UI5_` prefix is reserved for the framework and its samples.
:::
## Next Steps
[Hello World](/get_started/hello_world) explains what that class actually did.
Once it is more than one class, [Working Off-Stack](/advanced/working_off_stack) is
where the source moves into a git repository with the checks in front of it.
---
# Hello World
Just copy the following class into your system:
```abap
CLASS zcl_app_hello_world DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
ENDCLASS.
CLASS zcl_app_hello_world IMPLEMENTATION.
METHOD z2ui5_if_app~main.
client->message_box_display( `Hello World` ).
ENDMETHOD.
ENDCLASS.
```
Open the abap2UI5 startup page in your browser (the same page as in the [Quickstart](/get_started/quickstart)), enter the class name `ZCL_APP_HELLO_WORLD` in the input field, and launch your app.
::: tip **ABAP Language Versions**
While the HTTP handler has to distinguish between Standard ABAP and ABAP for Cloud, the apps themselves are independent. You're free to choose whether to build your apps with ABAP Cloud compatibility.
:::
### How Apps Work
abap2UI5 follows a thin-frontend model: the browser only renders UI5 views, while all logic, state, and data handling stay in ABAP on the server. Three ideas to keep in mind before writing code:
- **One method, many calls.** The framework calls your app's `main` method on every roundtrip — on the initial start *and* after every user interaction (button press, input change, navigation).
- **State lives in your class.** Public attributes of your app class hold data between roundtrips; abap2UI5 serializes and restores them for you, so you don't manage sessions manually.
- **The `client` object is your only API.** Use it to display views, check which event fired, bind attributes to UI5 controls, and trigger navigation.
Every abap2UI5 app implements the `z2ui5_if_app` interface. It has a single method, `main`, with one parameter: `client` of type `z2ui5_if_client`. (The real interface also declares a few attributes that the framework manages for you — you can ignore them.)
```abap
INTERFACE z2ui5_if_app PUBLIC.
METHODS main
IMPORTING
client TYPE REF TO z2ui5_if_client.
ENDINTERFACE.
```
→ *For a deeper look at the lifecycle and framework internals, see [How It All Works](/technical/how_it_all_works) and [Concept](/technical/concept).*
### View Display
Instead of a message box, let's render a view with some text:
```abap
CLASS zcl_app_hello_world DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
ENDCLASS.
CLASS zcl_app_hello_world IMPLEMENTATION.
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Hello World`
)->tag( `Text`
)->a( n = `text` v = `My Text` ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
```
You are writing a UI5 XML view, one control per call. `z2ui5_cl_ui5_view_builder`
has four verbs and no list of controls to look up — every UI5 control,
property and aggregation is available because the builder never knew any of
them by name:
| | |
| --- | --- |
| `ele( )` | add a control and **descend** into it — for a container |
| `tag( )` | add a control and **stay** — for a leaf |
| `a( )` | set **one** attribute on the control the chain is pointing at |
| `end( )` | ascend to the parent |
The single rule: `a( )` applies to the control the chain currently points at,
so attributes follow their control — and a control gets them *before* its
first child. The root `mvc:View` and its `xmlns` declarations are written by
hand, exactly as in a real UI5 view. A trailing `end( )` can be left out:
`stringify( )` renders from the root wherever the chain stopped.
### Events
Now add a button and react to its press event:
```abap
CLASS zcl_app_hello_world DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
ENDCLASS.
CLASS zcl_app_hello_world IMPLEMENTATION.
METHOD z2ui5_if_app~main.
IF client->check_on_navigated( ).
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Hello World`
)->tag( `Text`
)->a( n = `text` v = `My Text`
)->tag( `Button`
)->a( n = `text` v = `post`
)->a( n = `press` v = client->_event( `POST` ) ).
client->view_display( view->stringify( ) ).
ELSEIF client->check_on_event( `POST` ).
client->message_box_display( `Hello World!` ).
ENDIF.
ENDMETHOD.
ENDCLASS.
```
As introduced above, the framework calls `main` on every roundtrip. The diagram shows both phases — the initial load and a later user event:
```text
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Browser │──────>│ main() │──────>│ Browser │
│ (Start) │ HTTP │ init │ HTTP │ (View) │
└──────────┘ └──────────┘ └────┬─────┘
│ user clicks
┌──────────┐ ┌──────────┐ ┌────┴─────┐
│ Browser │<──────│ main() │<──────│ Browser │
│ (Update) │ HTTP │ event │ HTTP │ (Event) │
└──────────┘ └──────────┘ └──────────┘
```
Use the lifecycle checks to tell these phases apart:
- `client->check_on_init( )` — first call when the app starts
- `client->check_on_event( )` — user triggered an event (e.g. a button press)
Each `check_*` method returns `abap_true` only for its own phase, so the `IF`/`ELSEIF` chain acts as a dispatcher.
### Data Flow
Finally, add a public attribute and bind it to an input field to send data back to the server. The attribute must be in the `PUBLIC SECTION` — the framework accesses it dynamically and silently ignores private or protected attributes (full rules on the [Binding](/cookbook/model/binding) page):
```abap
CLASS zcl_app_hello_world DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA name TYPE string.
ENDCLASS.
CLASS zcl_app_hello_world IMPLEMENTATION.
METHOD z2ui5_if_app~main.
IF client->check_on_navigated( ).
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Hello World`
)->tag( `Text`
)->a( n = `text` v = `My Text`
)->tag( `Input`
)->a( n = `value` v = client->_bind( name )
)->tag( `Button`
)->a( n = `text` v = `post`
)->a( n = `press` v = client->_event( `POST` ) ).
client->view_display( view->stringify( ) ).
ELSEIF client->check_on_event( `POST` ).
client->message_box_display( |Your name is { name }.| ).
ENDIF.
ENDMETHOD.
ENDCLASS.
```
That's all you need. Set a breakpoint to watch the communication and data updates in action, then try changing the view, events, and data flow.
### Jump into the Code
Press `Ctrl+F12` in any running app to open the **Developer Tools** — tabs for the app's source code, the rendered view XML, the model data, the request/response pair and the error log:

## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Basics I — Hello World, the Smallest App | [`Z2UI5_CL_SMP_APP_493`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_493.clas.abap) |
---
# Full Example
This tutorial walks through a complete app that follows a typical ABAP flow: a small selection screen, reading data from the database, showing the result in a table, opening a popup to edit a row, and posting the changes back. It ties together everything from the [Hello World](/get_started/hello_world) page and shows how the pieces fit into a real screen.
The example uses sales-order-like data but keeps the SELECTs and updates as plain ABAP so you can adapt them to your own tables. Drop the class into your system and launch it the same way as the Hello World app.
## What You Will Build
1. **Selection screen** — date range and a customer filter.
2. **Read data** — fetch matching orders into an internal table on button press.
3. **Result table** — show the orders, with one row editable via popup.
4. **Popup** — open a dialog that lets the user change the delivery date.
5. **Post** — confirm in the popup, write the change back, refresh the table.
## Tutorial
### Step 1 — State: Types and Attributes
The whole app is one class. Public attributes hold everything the framework needs to remember between roundtrips: the selection criteria, the order table, and the row currently being edited. abap2UI5 serializes and restores them automatically — no session handling on your side.
```abap
CLASS zcl_app_full_example DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
TYPES:
BEGIN OF ty_s_order,
order_id TYPE string,
customer TYPE string,
order_date TYPE string,
delivery_date TYPE string,
END OF ty_s_order.
DATA:
BEGIN OF s_search,
date_from TYPE string,
date_to TYPE string,
customer TYPE string,
END OF s_search.
DATA t_orders TYPE STANDARD TABLE OF ty_s_order WITH EMPTY KEY.
DATA s_edit TYPE ty_s_order.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
METHODS on_event.
METHODS view_display.
METHODS popup_edit_display.
METHODS data_read.
METHODS data_update.
PRIVATE SECTION.
ENDCLASS.
```
The `main` method is a pure dispatcher — the same pattern as in [Hello World](/get_started/hello_world), just with the branches extracted into methods. We stash `client` in a protected attribute first, so the handler methods below can use it without passing it around:
```abap
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_navigated( ).
view_display( ).
ELSEIF client->check_on_event( ).
on_event( ).
ENDIF.
ENDMETHOD.
```
### Step 2 — The View: Selection Screen and Table
`view_display` builds the entire screen: a form with the selection criteria on top and the result table below. It is called exactly once, on the first roundtrip — every later interaction only updates data on this view.
Two things are new compared to Hello World: `shell( )` wraps the page in the standard UI5 app frame, and the `navbuttonpress` / `shownavbutton` parameters add a back button when the app was called from another app (details under [Navigation](/cookbook/event_navigation/navigation)). Further down, `get_parent( )` moves one level back up in the builder tree, so the next `column( )` becomes a sibling instead of a child — see [View → Definition](/cookbook/view/definition).
The two date pickers and the customer input are bound with `_bind`, so whatever the user types travels back to the ABAP attributes automatically. The table is bound with `_bind` too; since its cells are display-only, nothing syncs back:
```abap
METHOD view_display.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form` ).
DATA(page) = view->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Full Example`
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( )
)->a( n = `showNavButton` b = client->check_app_prev_stack( ) ).
page->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Order Selection`
)->a( n = `editable` v = `true`
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Order Date From`
)->tag( `DatePicker`
)->a( n = `value` v = client->_bind( s_search-date_from )
)->a( n = `valueFormat` v = `yyyy-MM-dd`
)->tag( `Label`
)->a( n = `text` v = `Order Date To`
)->tag( `DatePicker`
)->a( n = `value` v = client->_bind( s_search-date_to )
)->a( n = `valueFormat` v = `yyyy-MM-dd`
)->tag( `Label`
)->a( n = `text` v = `Customer`
)->tag( `Input`
)->a( n = `value` v = client->_bind( s_search-customer )
)->tag( `Button`
)->a( n = `text` v = `Read Orders`
)->a( n = `press` v = client->_event( `READ` ) ).
DATA(tab) = page->ele( `Table`
)->a( n = `items` v = client->_bind( t_orders ) ).
tab->ele( `columns`
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Order`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Customer`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Order Date`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Delivery Date`
)->end(
)->ele( `Column`
)->a( n = `width` v = `10%` ).
tab->ele( `items`
)->ele( `ColumnListItem`
)->ele( `cells`
)->tag( `Text`
)->a( n = `text` v = `{ORDER_ID}`
)->tag( `Text`
)->a( n = `text` v = `{CUSTOMER}`
)->tag( `Text`
)->a( n = `text` v = `{ORDER_DATE}`
)->tag( `Text`
)->a( n = `text` v = `{DELIVERY_DATE}`
)->tag( `Button`
)->a( n = `icon` v = `sap-icon://edit`
)->a( n = `tooltip` v = `Edit delivery date`
)->a( n = `press` v = client->_event( val = `EDIT`
t_arg = VALUE #( ( `${ORDER_ID}` ) ) ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
```
Two details worth a second look:
- **Cell bindings** like `{ORDER_ID}` are plain UI5 binding paths relative to the table row — the framework maps the ABAP field names for you.
- **The edit button** sends the row's key along with the event: ``t_arg = VALUE #( ( `${ORDER_ID}` ) )``. The `${...}` syntax is resolved *per row* in the browser, so the backend later knows exactly which order was clicked.
- **The fifth column** has no header text and a fixed `10%` width — it only holds the edit button defined in the row cells.
### Step 3 — Reading Data
When the user presses **Read Orders**, the `READ` event arrives in `on_event`. Reading is plain ABAP — in a real system this is a `SELECT` on your own tables; the tutorial fakes it with demo data and applies the selection criteria the same way a `WHERE` clause would:
```abap
METHOD data_read.
" demo data — in your system, replace this with a SELECT, e.g.:
" SELECT order_id, customer, order_date, delivery_date
" FROM zsd_order
" WHERE order_date BETWEEN @s_search-date_from AND @s_search-date_to
" INTO TABLE @t_orders.
t_orders = VALUE #(
( order_id = `1000` customer = `Ace Manufacturing` order_date = `2026-05-04` delivery_date = `2026-07-15` )
( order_id = `1001` customer = `Bright Retail` order_date = `2026-05-12` delivery_date = `2026-07-20` )
( order_id = `1002` customer = `Ace Manufacturing` order_date = `2026-06-01` delivery_date = `2026-08-01` )
( order_id = `1003` customer = `Corner Logistics` order_date = `2026-06-18` delivery_date = `2026-08-10` ) ).
IF s_search-date_from IS NOT INITIAL.
DELETE t_orders WHERE order_date < s_search-date_from.
ENDIF.
IF s_search-date_to IS NOT INITIAL.
DELETE t_orders WHERE order_date > s_search-date_to.
ENDIF.
IF s_search-customer IS NOT INITIAL.
" NS = `contains no string` — remove rows whose customer does not contain the filter text
DELETE t_orders WHERE customer NS s_search-customer.
ENDIF.
ENDMETHOD.
```
Back in `on_event`, note what is **not** there: no second `view_display( )`.
The view already exists in the browser and only the data changed, so the
framework sends the new model by itself — every roundtrip that changed
something bound pushes it. There is nothing to call:
```abap
WHEN `READ`.
data_read( ).
```
::: tip You may see `client->view_model_update( )` in older code
It still compiles, and it still does nothing: the method is deliberately
empty, because the model push is queued by the framework rather than asked
for. Leaving it in is harmless; writing it in new code teaches a step that
does not exist.
:::
### Step 4 — The Edit Popup
Pressing the edit icon fires the `EDIT` event, and `client->get_event_arg( )` returns the `ORDER_ID` we attached to the button in Step 2. The handler copies the matching row into `s_edit` and opens the dialog:
```abap
WHEN `EDIT`.
s_edit = VALUE #( t_orders[ order_id = client->get_event_arg( ) ] OPTIONAL ).
popup_edit_display( ).
```
A popup is built exactly like the main view — same builder, same verbs. The only differences are the root element (`core:FragmentDefinition` instead of `mvc:View`) and that it is handed to `popup_display( )`. The main view stays untouched in the background:
```abap
METHOD popup_edit_display.
DATA(popup) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `FragmentDefinition` ns = `core`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:core` v = `sap.ui.core`
)->a( n = `xmlns:form` v = `sap.ui.layout.form` ).
DATA(dialog) = popup->ele( `Dialog`
)->a( n = `title` v = |Order { s_edit-order_id } - { s_edit-customer }| ).
dialog->ele( n = `SimpleForm` ns = `form`
)->a( n = `editable` v = `true`
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Delivery Date`
)->tag( `DatePicker`
)->a( n = `value` v = client->_bind( s_edit-delivery_date )
)->a( n = `valueFormat` v = `yyyy-MM-dd` ).
dialog->ele( `buttons`
)->tag( `Button`
)->a( n = `text` v = `Cancel`
)->a( n = `press` v = client->_event( `CANCEL` )
)->tag( `Button`
)->a( n = `text` v = `Post`
)->a( n = `press` v = client->_event( `POST` )
)->a( n = `type` v = `Emphasized` ).
client->popup_display( popup->stringify( ) ).
ENDMETHOD.
```
The date picker in the dialog binds to `s_edit-delivery_date` with `_bind` — when the user picks a new date, the attribute is already updated by the time the next event reaches your ABAP code.
### Step 5 — Posting the Change
**Post** writes the change back, closes the popup, and refreshes the table — again without rebuilding the view. **Cancel** just closes the popup:
```abap
WHEN `POST`.
data_update( ).
client->popup_destroy( ).
client->message_toast_display( |Delivery date of order { s_edit-order_id } updated.| ).
WHEN `CANCEL`.
client->popup_destroy( ).
```
Updating is plain ABAP once more — the tutorial changes the internal table, a real app runs an `UPDATE`:
```abap
METHOD data_update.
" in your system, persist the change with an UPDATE, e.g.:
" UPDATE zsd_order SET delivery_date = @s_edit-delivery_date
" WHERE order_id = @s_edit-order_id.
t_orders[ order_id = s_edit-order_id ]-delivery_date = s_edit-delivery_date.
ENDMETHOD.
```
### The Complete Class
Copy this into your system and launch it like the Hello World app:
::: details Full source code
```abap
CLASS zcl_app_full_example DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
TYPES:
BEGIN OF ty_s_order,
order_id TYPE string,
customer TYPE string,
order_date TYPE string,
delivery_date TYPE string,
END OF ty_s_order.
DATA:
BEGIN OF s_search,
date_from TYPE string,
date_to TYPE string,
customer TYPE string,
END OF s_search.
DATA t_orders TYPE STANDARD TABLE OF ty_s_order WITH EMPTY KEY.
DATA s_edit TYPE ty_s_order.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
METHODS on_event.
METHODS view_display.
METHODS popup_edit_display.
METHODS data_read.
METHODS data_update.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_app_full_example IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_navigated( ).
view_display( ).
ELSEIF client->check_on_event( ).
on_event( ).
ENDIF.
ENDMETHOD.
METHOD on_event.
" client->get( )-event holds the name passed to _event( );
" get_event_arg( ) returns the extra argument attached via t_arg
CASE client->get( )-event.
WHEN `READ`.
data_read( ).
WHEN `EDIT`.
s_edit = VALUE #( t_orders[ order_id = client->get_event_arg( ) ] OPTIONAL ).
popup_edit_display( ).
WHEN `POST`.
data_update( ).
client->popup_destroy( ).
client->message_toast_display( |Delivery date of order { s_edit-order_id } updated.| ).
WHEN `CANCEL`.
client->popup_destroy( ).
ENDCASE.
ENDMETHOD.
METHOD view_display.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form` ).
DATA(page) = view->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Full Example`
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( )
)->a( n = `showNavButton` b = client->check_app_prev_stack( ) ).
page->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Order Selection`
)->a( n = `editable` v = `true`
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Order Date From`
)->tag( `DatePicker`
)->a( n = `value` v = client->_bind( s_search-date_from )
)->a( n = `valueFormat` v = `yyyy-MM-dd`
)->tag( `Label`
)->a( n = `text` v = `Order Date To`
)->tag( `DatePicker`
)->a( n = `value` v = client->_bind( s_search-date_to )
)->a( n = `valueFormat` v = `yyyy-MM-dd`
)->tag( `Label`
)->a( n = `text` v = `Customer`
)->tag( `Input`
)->a( n = `value` v = client->_bind( s_search-customer )
)->tag( `Button`
)->a( n = `text` v = `Read Orders`
)->a( n = `press` v = client->_event( `READ` ) ).
DATA(tab) = page->ele( `Table`
)->a( n = `items` v = client->_bind( t_orders ) ).
tab->ele( `columns`
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Order`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Customer`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Order Date`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Delivery Date`
)->end(
)->ele( `Column`
)->a( n = `width` v = `10%` ).
tab->ele( `items`
)->ele( `ColumnListItem`
)->ele( `cells`
)->tag( `Text`
)->a( n = `text` v = `{ORDER_ID}`
)->tag( `Text`
)->a( n = `text` v = `{CUSTOMER}`
)->tag( `Text`
)->a( n = `text` v = `{ORDER_DATE}`
)->tag( `Text`
)->a( n = `text` v = `{DELIVERY_DATE}`
)->tag( `Button`
)->a( n = `icon` v = `sap-icon://edit`
)->a( n = `tooltip` v = `Edit delivery date`
)->a( n = `press` v = client->_event( val = `EDIT`
t_arg = VALUE #( ( `${ORDER_ID}` ) ) ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
METHOD popup_edit_display.
DATA(popup) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `FragmentDefinition` ns = `core`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:core` v = `sap.ui.core`
)->a( n = `xmlns:form` v = `sap.ui.layout.form` ).
DATA(dialog) = popup->ele( `Dialog`
)->a( n = `title` v = |Order { s_edit-order_id } - { s_edit-customer }| ).
dialog->ele( n = `SimpleForm` ns = `form`
)->a( n = `editable` v = `true`
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Delivery Date`
)->tag( `DatePicker`
)->a( n = `value` v = client->_bind( s_edit-delivery_date )
)->a( n = `valueFormat` v = `yyyy-MM-dd` ).
dialog->ele( `buttons`
)->tag( `Button`
)->a( n = `text` v = `Cancel`
)->a( n = `press` v = client->_event( `CANCEL` )
)->tag( `Button`
)->a( n = `text` v = `Post`
)->a( n = `press` v = client->_event( `POST` )
)->a( n = `type` v = `Emphasized` ).
client->popup_display( popup->stringify( ) ).
ENDMETHOD.
METHOD data_read.
" demo data — in your system, replace this with a SELECT, e.g.:
" SELECT order_id, customer, order_date, delivery_date
" FROM zsd_order
" WHERE order_date BETWEEN @s_search-date_from AND @s_search-date_to
" INTO TABLE @t_orders.
t_orders = VALUE #(
( order_id = `1000` customer = `Ace Manufacturing` order_date = `2026-05-04` delivery_date = `2026-07-15` )
( order_id = `1001` customer = `Bright Retail` order_date = `2026-05-12` delivery_date = `2026-07-20` )
( order_id = `1002` customer = `Ace Manufacturing` order_date = `2026-06-01` delivery_date = `2026-08-01` )
( order_id = `1003` customer = `Corner Logistics` order_date = `2026-06-18` delivery_date = `2026-08-10` ) ).
IF s_search-date_from IS NOT INITIAL.
DELETE t_orders WHERE order_date < s_search-date_from.
ENDIF.
IF s_search-date_to IS NOT INITIAL.
DELETE t_orders WHERE order_date > s_search-date_to.
ENDIF.
IF s_search-customer IS NOT INITIAL.
" NS = `contains no string` — remove rows whose customer does not contain the filter text
DELETE t_orders WHERE customer NS s_search-customer.
ENDIF.
ENDMETHOD.
METHOD data_update.
" in your system, persist the change with an UPDATE, e.g.:
" UPDATE zsd_order SET delivery_date = @s_edit-delivery_date
" WHERE order_id = @s_edit-order_id.
t_orders[ order_id = s_edit-order_id ]-delivery_date = s_edit-delivery_date.
ENDMETHOD.
ENDCLASS.
```
:::
## What to Take Away
- One controller class, one `main` method, all state in public attributes — that is the whole app
- The view is rebuilt only when the structure changes. Edits, saves, and popup open/close do not need a fresh `view_display( )`
- Popups use the same builder as the view — a `core:FragmentDefinition` root instead of `mvc:View` — displayed via `popup_display` / `popup_destroy` while the main view stays in place
- Reading and writing the database is plain ABAP — abap2UI5 does not abstract that layer, which is what makes it easy to plug into existing code
From here, look at the [Cookbook](/cookbook/event_navigation/life_cycle) for value helps, navigation between apps, message handling, and other patterns you will need next.
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Full Example with sap.ui.table | [`Z2UI5_CL_SMP_APP_070`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_070.clas.abap) |
| Editable Cells, Add and Delete Rows | [`Z2UI5_CL_SMP_APP_011`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_011.clas.abap) |
---
# Tooling
Everything on this page is optional — abap2UI5 is one ABAP class and needs no
tools at all. What these three add is the loop that ABAP does not give you by
itself: catching a broken view **before** it reaches a system, and seeing the
app without leaving the editor.
They are independent of each other. Take the first one and stop, or take all
three. Building with an AI assistant? That whole side of the tooling — the
indexes, the agent conventions, the MCP server — is collected on
[Building with AI](/get_started/ai).
## Start a project from the template
[**abap2UI5/app-template**](https://github.com/abap2UI5/app-template) — press
*Use this template* on GitHub and you have an app repository that is already
set up: one working app class, both gates configured, and a CI workflow that
runs them on every push.
The alternative is creating a class by hand, which works fine — the template
only saves you from assembling the checks below yourself, and from finding out
half a year later that they were never running.
```
Use this template → clone → npm ci → npm run check
```
[Working Off-Stack](/advanced/working_off_stack) walks through it — what is in the
repository, the rename that makes it yours, and the way into your system.
## Check the view without a system
[**abap2UI5/linter**](https://github.com/abap2UI5/linter) — the view your app
builds only exists at runtime, so no UI5 tooling can see it and the ABAP
compiler has no opinion about it. This one reconstructs the view from your
builder chain and judges the two together:
- controls, properties, aggregations and enum values that UI5 does not have,
or does not have **yet** on the release you target (the `@since` floor —
1.71 by default, which is what most systems serve),
- bindings that point at nothing, events nothing handles, deprecated controls,
- and then it loads every view in a headless browser, which is the only way to
find a view that does not merely render wrongly but fails to load at all.
```sh
npx @abap2ui5/linter src
```
No SAP system, no install beyond npm. It also ships as a GitHub Action, and
the [app-template](https://github.com/abap2UI5/app-template) has it wired into
CI already. The [linter page](/advanced/linter) has the rest: the two
gates, `--fix`, and the baseline for switching it on over a codebase that
already exists.
## Run the app from the editor
[**abap2UI5/vscode-extension**](https://github.com/abap2UI5/vscode-extension) —
press `F9` on an app class and it starts in a preview beside your code,
against your real system. Plus completion and hover for the whole UI5 API
inside the builder chain, the linter running as you type, a click-a-control →
jump-to-the-line inspector, and a traffic log of every roundtrip.
Install it from the VS Code Marketplace or Open VSX; it needs the
[ABAP remote filesystem](https://marketplace.visualstudio.com/items?itemName=murbani.vscode-abap-remote-fs)
extension for the system connection.
## Where the samples live
Three repositories, in the order they build on each other:
| | |
| --- | --- |
| [samples](https://github.com/abap2UI5/samples) | the fundamentals — binding, events, popups, navigation, and complete little apps |
| [samples-controls](https://github.com/abap2UI5/samples-controls) | the UI5 demo kit rebuilt in abap2UI5, one app per official sample |
| [samples-stack](https://github.com/abap2UI5/samples-stack) | abap2UI5 together with OData, RAP, WebSockets and the Fiori Launchpad |
All three install with abapGit and carry an overview app that lists everything
they contain.
---
# Building with AI
An AI assistant writing abap2UI5 starts at a disadvantage nothing about your
project causes. Almost every abap2UI5 example on the public web builds its view
with `z2ui5_cl_xml_view`, the frozen predecessor of
`z2ui5_cl_ui5_view_builder` — so that is what a model writes when asked for an
app, confidently, and in an API that is no longer the one to use.
Everything below is a way of telling it otherwise, in rising order of effort.
## Paste the essentials
The zero-setup version, for any assistant with web access: paste this ahead of
your task. It corrects the four things a model most reliably gets wrong about
abap2UI5:
```text
Before writing any abap2UI5 code, read https://abap2ui5.github.io/docs/llms.txt
and follow it to the pages you need.
Four things that override whatever you remember about abap2UI5:
1. An app is ONE ABAP class implementing z2ui5_if_app. Everything enters main( ),
which dispatches on client->check_on_navigated( ) (the display branch, true on
first start too), client->check_on_event( `X` ) and - for one-time setup only -
client->check_on_init( ).
2. Build the view with z2ui5_cl_ui5_view_builder and its verbs ele / tag / a / end /
stringify. z2ui5_cl_xml_view is the FROZEN predecessor - it is what most examples
online show, and it is not what to write.
3. Bind with client->_bind( ). It is bidirectional; only what the user edited comes back.
4. Every roundtrip is a fresh ABAP session. Nothing survives on the server except
the app class itself, which is serialized.
Before building something from scratch, check whether it exists: the sample
catalogue lists every app with the words to search it by, at
https://github.com/abap2UI5/samples/blob/main/SAMPLES.md
When you are done, check the result with the abap2UI5-linter
(npx abap2ui5lint) - it reads the view your ABAP builds and needs no SAP system.
```
## Point it at the right index
Two files describe this project to a machine, and they answer different
questions:
| | |
| --- | --- |
| [`abap2ui5.github.io/docs/llms.txt`](https://abap2ui5.github.io/docs/llms.txt) | the map of the **prose** — every chapter of this site with one line of what it covers, and [`llms-full.txt`](https://abap2ui5.github.io/docs/llms-full.txt) for all of it in one fetch |
| [`github.com/abap2UI5/abap2UI5/llms.txt`](https://github.com/abap2UI5/abap2UI5/blob/main/llms.txt) | the map of the **code** — the interface files to read instead of guessing at a signature, and the guide for building apps that ships with the framework |
Both are short and both are free to give an assistant that has web access. It
is the cheapest correction available: an agent that has read either one does
not reach for the frozen builder.
## Put the conventions in the repository
An index tells an agent what abap2UI5 is. `AGENTS.md` tells it what *your
project* is — and it is read automatically, by every session, without anybody
remembering to paste anything.
The [app-template](/advanced/working_off_stack) ships one written for
app-building: the class shape, the lifecycle, the view builder, binding,
events, and the gates to run before calling the work done. It also ships a
`.claude/settings.json` allowlist so an agent can run `npm run check` itself
instead of stopping to ask.
## Give it the gates
An agent that cannot check its own work will hand you an app that does not
render. The two gates of the template need no SAP system, which means an agent
can run them on its own:
```sh
npm run check
```
The [abap2UI5 linter](/advanced/linter) half is the one that matters
here: it reconstructs the view from the builder chain and reports the names UI5
does not have, the bindings that point at nothing — and a class still built on
the frozen builder.
## Give it the loop
The [**MCP server**](/advanced/mcp_server) turns the checks into a development
loop, still without a system. It works with any MCP client — Claude Code,
Cursor, VS Code:
```sh
claude mcp add abap2ui5 -- npx --yes @abap2ui5/mcp-server
```
The tools an agent then has:
| | |
| --- | --- |
| `examples` | search the three sample catalogues — *has somebody already built a value help, a tree, navigation between two apps?* Answers with a class to read, never with a snippet to trust |
| `capabilities` | whether abap2UI5 can express a UI5 feature at all, from the verified capability map |
| `validate_view` | the linter's gates, in seconds, against your project's own config |
| `deploy_app` | write the class into a local sandbox and compile it |
| `build_backend` / `run_app` | transpile the framework and the app to Node, boot it headless, and hand back the errors **and a screenshot** |
| `pitfalls` | the defects a green run still does not catch — abapGit import, activation, the oldest UI5 release |
Set-up is levelled: validating views needs one small checkout and a minute;
the screenshot loop needs a browser and a first build measured in tens of
minutes. Stop where the value stops for you — the
[MCP Server page](/advanced/mcp_server) has the three levels, every tool and
the loop they are meant to be used in.
## From the editor
The [VS Code extension](/advanced/vscode)
registers that same MCP server for every client in the window — Copilot agent
mode, Claude Code, anything else speaking MCP — so an agent working in your
editor has the loop without any separate configuration. Point
`abap2ui5.mcp.reposRoot` at the folder holding the checkouts and the extension
passes the paths through.
It adds a second server of its own for the half that one deliberately does not
have: your configured **systems**. An agent can list them, search app classes
over ADT and get the app rendered on the real system as a screenshot — while
every credential prompt stays an ordinary VS Code dialog the agent never sees.
## Next Steps
- [Working Off-Stack](/advanced/working_off_stack) — the repository all of this
assumes
- [Tooling](/get_started/tooling) — the human side of the same loop: the
template, the linter, and the VS Code extension
---
# What's Next?
You've installed abap2UI5 and built your first app. From here, pick the direction that fits your goals.
## Sample Apps
With hundreds of samples, the [samples repository](https://github.com/abap2UI5/samples) is the fastest way to learn abap2UI5. Browse tables, lists, trees, and other UI5 controls — copy and paste snippets to speed up your own work:
Looking for one in particular? The [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md) lists every app on one page with the words you would search it by, so `Ctrl+F` for `f4`, `tree` or `nav_app_call` lands on the class that does it. The [Cookbook](/cookbook/overview) links the same apps from the page that explains the pattern.

_No system at hand? The [live demo](https://abap2ui5.github.io/web-abap2UI5-build/) runs
these samples in the browser — the whole abap2UI5 stack, backend included,
transpiled to JavaScript and rebuilt daily from `main`. Nothing to install._
::: tip Contribution
The samples evolve all the time. Have one to share? Open a PR so others can learn from it.
:::
## Tooling
Optional, and worth the ten minutes: a project [template](/advanced/working_off_stack) with the checks already wired up, a [linter](/advanced/linter) that finds broken views without a system, an [extension](/advanced/vscode) that runs your app on `F9` next to the code, and an [MCP server](/advanced/mcp_server) that lets an AI agent build and *look at* the app. See [Tooling](/get_started/tooling), and [Building with AI](/get_started/ai) if an assistant writes some of it.
## Development
Build views, handle events, share data, and work with tables. The [Cookbook](/cookbook/overview) walks through the patterns you need for everyday work — start with the [Life Cycle](/cookbook/event_navigation/life_cycle) page.
## Configuration
Before going live, set up security, performance tuning, Launchpad integration, and more. Start with the [Configuration guide](/configuration/setup).
## Add-ons
Extend your apps with community-built add-ons for layout handling, charts, table maintenance, and more. See the [Add-ons page](/advanced/addons).
## Real-World Use
See ready-to-use apps, real-world scenarios, and companies already running on abap2UI5 on the [Who Uses abap2UI5](/resources/who_uses) page.
---
# Overview
The Cookbook collects task-oriented recipes for everyday abap2UI5 development. Each section focuses on one area of the framework and shows the patterns you reach for most often. Use this page as a map — pick the topic that matches the problem in front of you and jump straight in.
## Sections
### [Cheat Sheet](/cookbook/cheat_sheet)
The rules that decide whether an app works or fails in a hard-to-debug way, condensed to one page with links to the full recipes.
### [View](/cookbook/view/definition)
Build the XML view your app sends to the browser. Covers the basic view definition, deprecated controls to stay away from, nesting views inside other views, and XML templating for repeating structures.
### [Model](/cookbook/model/binding)
Share data between ABAP and the frontend. Explains data binding, expressions and formatters, tables and trees, the device model, and how to deal with the model size limit.
### [Event, Navigation](/cookbook/event_navigation/life_cycle)
Understand how a request flows through your app. Covers the lifecycle, backend and frontend events, actions, navigation between apps, and exception handling.
### [Popup, Popover](/cookbook/popup_popover/popup)
Overlay parts of the view with dialogs and popovers — custom popups, popovers anchored to a control, and the built-in dialogs the framework ships out of the box.
### [Translation, Messages](/cookbook/translation_messages/message)
Communicate with the user. Show message toasts and message boxes, write to the application log, and translate text with `i18n`.
### [Browser Interaction](/cookbook/browser_interaction/title)
Reach into the browser from ABAP — set the tab title, control focus and scrolling, run timers, access the clipboard, work with the URL, and handle the soft keyboard on mobile.
### [Device Capabilities](/cookbook/device_capabilities/info)
Read device info and use native hardware: camera, geolocation, barcode scanning, audio, and file upload/download including PDF and spreadsheet generation.
### [State, Connectivity](/cookbook/expert_more/lock)
Manage session and app state across requests and talk to the outside — locking, statefulness, WebSocket, logout, OData, and sharing app state via URL.
### [More Topics](/cookbook/eml_cds_sql/rap)
EML/CDS/SQL integration with RAP, recurring patterns and helpers, troubleshooting, and a list of obsolete features kept for reference.
## How to Use This Cookbook
- Each recipe stands on its own — read only the section you need.
- Code snippets are copy-paste ready. Drop them into a class that implements `z2ui5_if_app`.
- Most pages end with a **Working Samples** table: complete apps that use what the page describes. Each is a single class you can import and run.
- To search all of them instead — *"is there a sample for X?"* — read the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md) and `Ctrl+F` for what you are after. Every entry carries the words a newcomer would type, not only its title. Once the repository is installed, the same list is the overview app `Z2UI5_CL_SMP_APP_000`, with a search box.
- For the API surface (`z2ui5_if_client`, `z2ui5_cl_ui5_view_builder`, …), read the source in the [main repository](https://github.com/abap2UI5/abap2UI5).
→ New to abap2UI5? Start with the [Getting Started Guide](/get_started/quickstart).
---
# Cheat Sheet
A one-page recap of the rules that decide whether an abap2UI5 app works or misbehaves in a way that is hard to debug. Each row links to the recipe that explains it in full — read this page as a checklist, not as an introduction.
| Rule | Why it matters |
|---|---|
| Implement `z2ui5_if_app` and put everything in the single `main` method | It is the only entry point the framework calls — on the initial load *and* on every user interaction → [Life Cycle](/cookbook/event_navigation/life_cycle) |
| Dispatch with one `IF` / `ELSEIF` chain over `check_on_init( )`, `check_on_navigated( )` and `check_on_event( )` | Each check answers for its own phase only; separate `IF` blocks let two branches run in the same roundtrip → [Life Cycle](/cookbook/event_navigation/life_cycle) |
| Always call `view_display( )` in the `check_on_navigated( )` branch | After a called app returns via `nav_app_leave( )`, the browser still shows *its* view — without a re-display the user is left on a stale or blank screen → [Navigation](/cookbook/event_navigation/navigation) |
| Declare every attribute you bind in the `PUBLIC SECTION` | Binding works via dynamic `ASSIGN`; `PROTECTED` and `PRIVATE` attributes are silently ignored → [Binding](/cookbook/model/binding) |
| Keep state in public attributes, not in local variables | Between two events the controller is serialized to the client and back — locals, `DATA(...)` declarations, open cursors and locks do not survive → [Statefulness](/cookbook/expert_more/statefulness) |
| Respect the UI5 aggregation rules even though the builder does not enforce them | The builder lets you nest anything inside anything; UI5 does not, and the mismatch surfaces as broken rendering rather than a syntax error → [Definition](/cookbook/view/definition) |
| Never use a deprecated UI5 control | It renders today and vanishes on the next UI5 upgrade → [Deprecated Controls](/cookbook/view/deprecated_controls) |
| Check the built-in popups before building a custom dialog | Roughly twenty ready-made dialogs ship with the framework — confirm, select, file up/download, ranges, PDF, … → [Built-In](/cookbook/popup_popover/built_in) |
| Use backtick string literals (`` ` ``) | Project-wide convention in the framework, the samples and this documentation; keeps ABAP string handling consistent |
::: warning An ABAP flag passed as `v` does not reach the view as a boolean
`a( )` takes **either** `v` — any string expression — **or** `b`, an ABAP boolean. Only `b` converts, and it is the form to use whenever the value comes out of ABAP:
```abap
)->tag( `Button`
)->a( n = `text` v = `Save`
)->a( n = `enabled` b = abap_false ) " → enabled="false"
```
Through `v` the flag is written verbatim: `abap_true` arrives in the view as `enabled="X"` and `abap_false` as an empty value. Neither is the `true` / `false` UI5 expects, and neither is a syntax error — the view renders, with the control in the wrong state.
A **literal** is a string and belongs in `v`, unquoted by any flag variable:
```abap
)->a( n = `enabled` v = `false` )
```
Any expression that yields a flag works in `b`, so there is no reason to convert by hand: `` )->a( n = `visible` b = xsdbool( lines( mt_item ) > 0 ) ) ``.
:::
## Next Steps
- [Overview](/cookbook/overview) — the full map of cookbook topics
- [Common Failures](/cookbook/troubleshooting/common_failures) — symptoms and their usual causes
---
# Definition
abap2UI5 uses [SAP UI5](https://sapui5.hana.ondemand.com) on the frontend without modification. Whatever your ABAP code sends to the browser is a **standard UI5 XML view** — the same XML you would write in any UI5 freestyle project.
The consequence: **everything in the UI5 SDK works in abap2UI5 1:1 when you write the XML directly**. Any control, any property, any namespace from the [UI5 Demo Kit](https://sapui5.hana.ondemand.com/sdk) is available. Copy the XML, paste it into your ABAP class, and it renders.
### Sending a View
The simplest case: build an XML string and ship it to the client.
```abap
METHOD z2ui5_if_app~main.
client->view_display(
|| &
| | &
| | &
| | &
| | &
| | &
|| ).
ENDMETHOD.
```
Swap `` for any other control from the SDK; the framework doesn't care.
### The View Builder
Writing raw XML by hand quickly turns cumbersome. abap2UI5 ships
`z2ui5_cl_ui5_view_builder`, which produces the same XML by method chaining —
one call per control, so the shape of the chain is the shape of the view.
`stringify( )` renders the tree into the XML string the framework sends to the
frontend:
```abap
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:core` v = `sap.ui.core`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `My title`
)->tag( `Text`
)->a( n = `text` v = `My text` ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
```
Both snippets produce the exact same view. Use whichever you prefer — raw
strings are fine for a handful of lines, the builder scales better for real
apps.
Four verbs, and no catalogue of controls behind them:
| | |
| --- | --- |
| `ele( )` | add a control and **descend** into it — for a container |
| `tag( )` | add a control and **stay** — for a leaf |
| `a( )` | set **one** attribute on the control the chain is pointing at |
| `end( )` | ascend to the parent |
There is exactly one rule: `a( )` applies to the control the chain is
**pointing at** — the child just added by `ele( )` or `tag( )` — so an
attribute always follows its own control, and a control has to receive its
attributes *before* its first child. `ele( )` descends and is closed by an
`end( )`; `tag( )` stays, so a run of leaves needs none. The final `end( )`s
may be left out entirely: `stringify( )` always renders from the root, no
matter where the chain stopped.
The root `mvc:View` and its `xmlns` declarations are written by hand, exactly
as in a real UI5 view — the builder does not invent them for you.
Because the builder knows no control names, **it can express every control,
property and aggregation UI5 has**, including those released after this page
was written. Whatever name and namespace you pass is written into the XML
verbatim, in the SDK's own camelCase. An aggregation is an element like any
other and carries its parent's namespace — `` under a Page is
``ele( n = `content` ns = `m` )``, a default-namespace `` inside an
`sap.ui.table.Table` is ``ele( `columns` )``.
For an ABAP boolean pass `b` instead of `v`; it renders `true`/`false`, so a
flag reaches the view without a conversion of its own:
```abap
)->a( n = `editable` b = mv_edit_mode
)->a( n = `visible` b = xsdbool( lines( mt_item ) > 0 ) )
```
Tips for working with views:
- The [VS Code extension](/advanced/vscode) gives
the chain completion and hover for the whole UI5 API, and checks the view
while you type.
- The [abap2UI5 linter](/advanced/linter) rebuilds the view
from your chain and reports unknown controls, properties, enum values and
`@since` violations — no SAP system involved.
- See the [samples repository](/get_started/next#sample-apps) for ready-made
examples to copy and adapt.
::: warning Respect the UI5 Control Aggregation Rules
The builder is intentionally permissive — it lets you nest **any** control
inside **any** other control, because it never knew what either of them is.
UI5 itself is not permissive. Every UI5 control defines specific aggregations
(e.g. `sap.m.Page` has `content`, `headerContent`, `footer`) and each
aggregation accepts only certain child control types (often a particular
interface or base class).
Combining controls in a way that violates these rules can lead to broken
rendering, missing controls, layout glitches, runtime errors in the browser
console, or subtle bugs that only show up on certain devices or themes.
**Always check the [UI5 SDK](https://sapui5.hana.ondemand.com) for each
control** to confirm:
- which aggregations it exposes,
- which child types those aggregations accept, and
- which parent controls are valid for the control you want to use.
The ABAP compiler cannot catch these mistakes — they are pure UI5 concerns.
The [abap2UI5 linter](/advanced/linter) catches a large part
of them before you deploy, and the rest have to be verified against the SDK.
:::
### Where to Look for Controls
Because UI5 XML is used 1:1, **the UI5 documentation is your reference** for anything visual:
- [UI5 Demo Kit](https://sapui5.hana.ondemand.com/sdk) — interactive samples for every control
- [UI5 Control API](https://sapui5.hana.ondemand.com/sdk/#/api) — properties, aggregations, events
Find a control you like in the UI5 docs, copy its XML, paste it into `view_display( )` — done. abap2UI5 has no separate control catalog to learn.
One thing the SDK will not warn you about while you copy: the control may be deprecated. Because the XML is passed through 1:1, a deprecated control renders exactly like any other — until UI5 removes it. See [Deprecated Controls](/cookbook/view/deprecated_controls) for the cases that come up most often.
### Choosing a Control
The UI5 SDK is large. The table below covers the choices that come up in almost every abap2UI5 app — use it as a starting point before diving into the SDK.
| Need | Use | Notes |
| --------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
| Tabular data, columns, sorting | `sap.m.Table` | Responsive, supports growing/p13n. Default choice for business data. |
| Flat list with icons/avatars | `sap.m.List` with `StandardListItem` | Lighter than `Table` when columns are not needed. |
| Hierarchical data (parent/child) | `sap.m.Tree` or `sap.ui.table.TreeTable` | `Tree` is responsive; `TreeTable` shows fixed columns. |
| Form with labels + inputs | `sap.ui.layout.form.SimpleForm` | Use this 90% of the time — auto-layouts labels and fields responsively. |
| Form with custom grid layout | `sap.ui.layout.form.Form` | When `SimpleForm` is not flexible enough. |
| App page with title and content | `sap.m.Page` | The standard container. Wrap in `sap.m.Shell` for the SAP frame. |
| Page with collapsible header | `sap.f.DynamicPage` | For object pages and analytics screens. |
| Page with action toolbar | `sap.f.semantic.SemanticPage` | Adds semantic actions (edit, delete, share) in the footer. |
| Vertical / horizontal stack | `sap.m.VBox` / `sap.m.HBox` | Quick layout without a form. |
| Tabs | `sap.m.IconTabBar` | Use `IconTabFilter` for each tab. |
| Single-select dropdown | `sap.m.Select` (≤ 20 items) / `sap.m.ComboBox` | `ComboBox` allows typing and filtering. |
| Multi-select dropdown | `sap.m.MultiComboBox` | Pills appear inside the field. |
| Date / time input | `sap.m.DatePicker` / `sap.m.TimePicker` / `sap.m.DateTimePicker` | Needs a formatter — see [Binding → Data-Type Mapping](/cookbook/model/binding#data-type-mapping). |
| Status indicator | `sap.m.ObjectStatus` | Colored text + icon for state. |
| Modal dialog | `sap.m.Dialog` (inside a `core:FragmentDefinition`) | See [Popup](/cookbook/popup_popover/popup). |
When two controls fit, prefer the simpler one: `Table` over `TreeTable`, `SimpleForm` over `Form`, `Select` over `ComboBox`. Switch to the richer variant only when a concrete requirement justifies it.
### Next Steps
This produces a static view. The next section walks through binding and sharing data between the view and the app logic.
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Basics I — Hello World, the Smallest App | [`Z2UI5_CL_SMP_APP_493`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_493.clas.abap) |
| Ship Your Own CSS with the View | [`Z2UI5_CL_SMP_APP_050`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_050.clas.abap) |
| FlexBox Layouts with Custom Classes | [`Z2UI5_CL_SMP_APP_255`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_255.clas.abap) |
---
# Deprecated Controls
abap2UI5 sends plain UI5 XML to the browser, so **every** control the UI5 runtime knows will render — including the ones SAP has deprecated. Nothing in the framework stops you from using them.
That makes deprecation a question you have to answer yourself while building the view. A deprecated control still works today, but it receives no new features, may behave inconsistently with newer themes, and disappears without replacement once SAP removes it — which has already happened (the Belize themes were removed in 1.136).
The authoritative, always-current list is the **[UI5 deprecation index](https://ui5.sap.com/#/api/deprecated)**. Check it whenever you pick a control you have not used before. The sections below collect the cases that come up most often in abap2UI5 apps.
## Whole Libraries to Avoid
Do not use *any* control from these libraries — they are deprecated in their entirety:
| Library | Deprecated since | Use instead |
|---|---|---|
| `sap.ui.commons.*` — Accordion, Button, CheckBox, ComboBox, DatePicker, Dialog, FileUploader, Label, Link, Menu, Panel, RadioButton, SearchField, Slider, TextArea, TextField, TextView, ToggleButton, Toolbar, Tree, Form, SimpleForm, AbsoluteLayout, BorderLayout, MatrixLayout, HorizontalLayout, VerticalLayout, … (entire library) | 1.38 | `sap.m` + `sap.ui.layout` |
| `sap.viz.ui5.*` legacy charts — Bar, Bubble, Bullet, Column, Combination, Donut, Heatmap, Line, Pie, Scatter, StackedColumn, Treemap, Waterfall, … | 1.32 | `sap.viz.ui5.controls.VizFrame` |
`sap.ui.commons` is the one to watch out for: several control names exist in both `sap.ui.commons` and `sap.m` (`Button`, `Label`, `Dialog`, `Panel`, …). Copying XML from an old tutorial or an older SAP sample often drags the deprecated namespace along with it.
## Individual Deprecated Controls
| Control | Deprecated since | Use instead |
|---|---|---|
| `sap.m.MultiEditField` | 1.120 | — |
| `sap.f.Avatar` | 1.73 | `sap.m.Avatar` |
| `sap.ui.core.XMLComposite` | 1.88 | Custom controls |
| `sap.ui.core.mvc.HTMLView` | 1.108 | `XMLView` |
| `sap.ui.core.mvc.JSONView` | 1.120 | `XMLView` |
| `sap.ui.core.mvc.JSView` | 1.90 | Typed views |
| `sap.ui.core.mvc.TemplateView` | 1.56 | `XMLView` |
| `sap.ui.core.tmpl.TemplateControl` | 1.56 | — |
| `sap.ui.table.ColumnHeader` | 1.120 | `sap.ui.table.Column` |
| `sap.ui.table.TableHelper` | 1.118 | — |
| `sap.f.routing.Router` / `Target` / `TargetHandler` / `Targets` | 1.56 | `sap.m.routing.*` (async) |
| `sap.tnt.IToolHeader` (interface) | 1.135 | Any control as `ToolPage` header |
## Deprecated Enums and Types
These show up as *property values* rather than as controls, which makes them easy to miss — the view renders, the value is simply ignored or falls back to a default:
- `sap.m.ValueCSSColor`, `DateTimeInputType` (use `DatePicker` / `TimePicker`), `ListHeaderDesign`, `ListMode.SingleSelect` (1.143 → `SingleSelectLeft`), `FrameType.TwoThirds` / `Auto`, the misspelled `PlacementType.*Prefered*` variants
- `sap.f.AvatarShape` / `AvatarSize` / `AvatarType` / `AvatarColor` / `AvatarImageFitType` / `IllustratedMessageType` / `IllustratedMessageSize` / `DynamicPageTitleArea` — use the `sap.m.*` equivalents
- `sap.ui.layout.BlockBackgroundType.Mixed`, `form.GridElementCells`, `SimpleFormLayout.ResponsiveLayout`, `SimpleFormLayout.GridLayout`, `cssgrid.CSSGridGapShortHand`, `GridHelper`
- `sap.ui.table.NavigationMode`, `SortOrder` (use `sap.ui.core.SortOrder`), `VisibleRowCountMode` (use the `rowMode` aggregation), `TreeAutoExpandMode`, `ResetAllMode`
- `sap.ui.core.MessageType` (use `module:sap/ui/core/message/MessageType`)
- `sap.ui.unified.ContentSwitcherAnimation` (1.147 — concept discarded)
## Other Deprecated Items
- Analysis Path Framework (APF) — deprecated 1.140
- `sap.m.PDFViewer.sourceValidationFailed()` — deprecated 1.141
- The declarative `data-sap-ui-type` attribute — deprecated 1.120, use XML views
- Belize, Blue Crystal, and Blue Crystal HCB themes — **removed** in 1.136, use Horizon → [Theme](/configuration/setup/theme)
::: warning Avatar — mind the namespace
Write `Avatar` with no `ns`, so the element resolves to `sap.m.Avatar` through
the view's default `xmlns`:
```abap
)->tag( `Avatar`
)->a( n = `src` v = `sap-icon://person-placeholder` " → = sap.m.Avatar
```
**Never write `ns = `f``** — that produces ``, the deprecated
`sap.f.Avatar`.
`AvatarGroup` and `AvatarGroupItem` are the other way round: those controls do
still live in `sap.f`, so they need `ns = `f`` and the `sap.f` namespace
declared on the view.
:::
### Next Steps
- [Definition](/cookbook/view/definition) — how the XML view is built and where to look controls up
- [Cheat Sheet](/cookbook/cheat_sheet) — the rules that matter most, on one page
---
# Nested Views
A **nested view** in abap2UI5 is a separate XML view fragment that you inject into a *placeholder* inside another view. The main view stays on screen; only the nested fragment is rendered (and later re-rendered or refreshed) independently. This is the standard pattern for master-detail screens, side panels, tab content, and anywhere you want one part of the UI to update without rebuilding the whole page.
If you know SAPUI5's [nested views](https://sapui5.hana.ondemand.com/sdk/#/topic/df8c9c3d6f2a4d728ba7d6f4cb6c6d35) (``), the goal is the same — split the UI into independently managed pieces. In abap2UI5 the wiring is done from ABAP at runtime: instead of referencing a static view file, you build the nested view's XML in ABAP and tell the client to plug it into a named slot.
### The Basic Pattern
Two ingredients are needed:
1. **An anchor in the main view** — any control with an `id`. The nested view will be inserted *into* this control.
2. **A nested view + a `nest_view_display` call** — builds the fragment and ships it to the named anchor.
```abap
" 1) Main view with an anchor
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Main View`
)->a( n = `id` v = `test` " <-- the anchor id
)->ele( `content`
)->tag( `Button`
)->a( n = `text` v = `Re-render only the nested view`
)->a( n = `press` v = client->_event( `NEST` ) ).
" 2) Nested view, built like any other view
DATA(nested) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->a( n = `title` v = `Nested View`
)->tag( `Input`
)->a( n = `value` v = client->_bind( mv_input_nest )
)->tag( `Button`
)->a( n = `text` v = `event`
)->a( n = `press` v = client->_event( `TEST` ) ).
IF client->check_on_navigated( ).
client->view_display( view->stringify( ) ).
ENDIF.
CASE client->get( )-event.
WHEN `NEST`.
client->nest_view_display(
val = nested->stringify( )
id = `test` " target the anchor
method_insert = `addContent` ). " UI5 mutator on that control
ENDCASE.
```
What happens at runtime: `view_display` paints the main view; the page with `id="test"` sits on screen. When the user clicks the button, `nest_view_display` ships the nested XML to the client, which calls `addContent( ... )` on the control with that id. The nested fragment appears inside the page — without re-rendering the page itself.
The full pattern (re-render everything vs. main only vs. nested only) is in `Z2UI5_CL_SMP_APP_065`.
### `nest_view_display` Parameters
| Parameter | Meaning |
| ---------------- | -------------------------------------------------------------------------------------------------- |
| `val` | The nested view's XML, produced by `stringify( )`. |
| `id` | The id of the anchor control in the main view. |
| `method_insert` | UI5 mutator method called on the anchor to add the nested view (e.g. `addContent`). |
| `method_destroy` | Optional. UI5 mutator method that removes the previous nested content before inserting the new one. |
`method_insert` and `method_destroy` are plain UI5 control methods — pick whichever the anchor exposes. The choice depends on the anchor's aggregation:
| Anchor control | Typical `method_insert` | Typical `method_destroy` |
| ----------------------- | --------------------------- | ------------------------------- |
| `Page`, `VBox`, generic | `addContent` | `removeAllContent` |
| `FlexibleColumnLayout` | `addMidColumnPage` | `removeAllMidColumnPages` |
| `FlexibleColumnLayout` | `addEndColumnPage` | `removeAllEndColumnPages` |
| `FlexibleColumnLayout` | `addBeginColumnPage` | `removeAllBeginColumnPages` |
Always pass `method_destroy` when the nested view is going to be replaced over the lifetime of the app; otherwise consecutive calls stack new fragments on top of the old ones.
### Independent Re-rendering
The whole point of nested views is to re-render only what changed. Four calls cover the common needs:
| Call | What it does |
| --------------------------------- | --------------------------------------------------------------------------------------------- |
| `client->view_display( ... )` | Replaces the main view's XML. The anchor is recreated, so any nested content is lost too. |
| `client->nest_view_display( ... )`| Replaces only the nested view. The main view stays on screen. |
| `client->view_model_update( )` | Pushes current ABAP data values into **all already-rendered views**. No re-render. |
| `client->nest_view_destroy( )` | Removes the nested view from the frontend without touching the main view. |
The main view has the matching `client->view_destroy( )`; the second nested slot has `nest2_view_display( )` and `nest2_view_destroy( )`.
A rule of thumb:
- **Layout changed** (different controls, new columns, new sections) → `view_display` / `nest_view_display`.
- **Only the data changed** (a flag flipped, a row added to a bound table) → `view_model_update`.
`Z2UI5_CL_SMP_APP_065` shows the difference between the three options in a single screen with one button per call.
### Master-Detail with `FlexibleColumnLayout`
The most common real-world use: a master list on the left, detail content on the right. `sap.f.FlexibleColumnLayout` is the standard container; abap2UI5 nests the detail view into its middle column.
```abap
" Master view — built once
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:f` v = `sap.f`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Master Detail`
)->ele( n = `FlexibleColumnLayout` ns = `f`
)->a( n = `layout` v = client->_bind( mv_layout )
)->a( n = `id` v = `test` " anchor
)->ele( n = `beginColumnPages` ns = `f`
)->ele( `List`
)->a( n = `items` v = client->_bind( t_tab )
)->a( n = `selectionChange` v = client->_event( `SELCHANGE` )
)->ele( `items`
)->tag( `StandardListItem`
)->a( n = `title` v = `{TITLE}`
)->a( n = `selected` v = `{SELECTED}` ).
client->view_display( view->stringify( ) ).
```
When the user picks a row, a detail view is rendered into the middle column:
```abap
METHOD view_display_detail.
DATA(nested) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:t` v = `sap.ui.table`
)->ele( `Page`
)->a( n = `title` v = `Nested View`
)->ele( n = `Table` ns = `t`
)->a( n = `rows` v = client->_bind( t_tab2 ) ).
" ...columns, toolbar, row actions...
client->nest_view_display(
val = nested->stringify( )
id = `test`
method_insert = `addMidColumnPage`
method_destroy = `removeAllMidColumnPages` ).
ENDMETHOD.
```
The layout is bound editable (`mv_layout`), so events like *full-screen mode* or *close detail* simply update `mv_layout` and call `view_model_update`. The FCL transitions itself; no view is rebuilt.
End-to-end samples:
- `Z2UI5_CL_SMP_APP_097` — list master, `sap.ui.table.Table` in the detail with sort/filter/row actions.
- `Z2UI5_CL_DEMO_APP_085` — full master-detail with an `ObjectPageLayout` as the nested detail, including search, sort, and the FCL fullscreen toggle.
### Refreshing After Data Changes
All bound data lives in a **single client-side model**, regardless of which view a binding was built in — `client->_bind( ... )` always writes to that one root model. One call therefore refreshes everything: after the ABAP data changes, `client->view_model_update( )` pushes the new values into every rendered view — main, nested, second nested.
```abap
DELETE t_tab2 WHERE title = ls_arg-title.
client->view_model_update( ). " push the new data into all rendered views
```
::: tip `nest_view_model_update` and the `view` parameter of `_bind` are obsolete
Earlier releases kept a separate model per view: bindings were tagged with `view = client->cs_view-...` and each view had its own refresh call (`nest_view_model_update( )`, `nest2_view_model_update( )`). That separation is gone — there is now one root model. The old per-view refresh methods still exist as compatibility aliases and behave like `view_model_update( )`, and the `view` parameter of `_bind` / `_bind_edit` is an inert no-op. In new code, omit the parameter and use `view_model_update( )` only.
:::
### Two Levels of Nesting
The middle column can itself host another nested view in the end column — useful for master / detail / detail-of-detail flows. abap2UI5 exposes a second method for this level:
```abap
METHOD view_display_detail_detail.
DATA(nested) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->a( n = `title` v = `Nested View`
)->tag( `Text`
)->a( n = `text` v = client->_bind( mv_title ) ).
client->nest2_view_display(
val = nested->stringify( )
id = `test`
method_insert = `addEndColumnPage`
method_destroy = `removeAllEndColumnPages` ).
ENDMETHOD.
```
`nest2_view_display` works exactly like `nest_view_display` but targets the second level — typically the FCL's *end* column. `Z2UI5_CL_SMP_APP_098` walks through all three columns: a list selects a row, a row-action navigates to the end column, the layout switches to `ThreeColumnsEndExpanded`.
### When to Use Nested Views (and When Not To)
| Situation | Approach |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Different visual sections that update at different rates | Nested views — re-render each piece on its own |
| Master-detail, FCL columns, drill-down navigation | Nested views — the canonical use case |
| A side panel that toggles open/closed but keeps the page intact | Nested views |
| Building one view from helper methods (still rendered as one) | Plain ABAP composition — pass nodes between methods, no `nest_view_display` needed |
| One full screen replacing another | `view_display` with the new view (or `nav_app_call` for a separate app) |
Plain composition is the right starting point: keep helper methods that take a parent node and add children to it. Reach for nested views once the UI has clear sub-areas that need to update independently — otherwise you pay for ceremony you don't use.
### Tips
- The anchor id must be unique in the main view. The framework calls `byId` on the rendered view to find it; duplicate ids break the lookup.
- Always provide `method_destroy` when a nested slot will be replaced more than once. Forgetting it causes nested fragments to accumulate.
- Build the nested view in its own method (e.g. `view_display_detail`) and call it both from the initial render and from event handlers. Two call sites, one definition.
- If a nested view does not pick up a data change, you probably need `view_model_update( )`; if a control simply isn't there, you need `nest_view_display( )` again.
- For very large apps, look at `Z2UI5_CL_SMP_APP_104`, which loads each detail screen from a separate `z2ui5_if_app` class and renders it into the nested slot. It is an advanced pattern — start with the simpler form first.
See `Z2UI5_CL_SMP_APP_065`, `Z2UI5_CL_DEMO_APP_085`, `Z2UI5_CL_SMP_APP_097`, `Z2UI5_CL_SMP_APP_098`, and `Z2UI5_CL_SMP_APP_104` for runnable examples covering every variation above.
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Basic Example (nest_view_display) | [`Z2UI5_CL_SMP_APP_065`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_065.clas.abap) |
| Embed Another App's View | [`Z2UI5_CL_SMP_APP_104`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_104.clas.abap) |
| Master-Detail with FlexibleColumnLayout | [`Z2UI5_CL_SMP_APP_097`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_097.clas.abap) |
| Three Columns with FlexibleColumnLayout | [`Z2UI5_CL_SMP_APP_098`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_098.clas.abap) |
| Dynamic Content in a Nested View | [`Z2UI5_CL_SMP_APP_176`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_176.clas.abap) |
---
# XML Templating
XML Templating is a **UI5 preprocessor feature**, not an abap2UI5 invention. The UI5 runtime understands a small set of instructions in the `template` XML namespace — `template:repeat`, `template:if`, `template:then`, `template:else`, `template:with` — and expands them into plain XML *before* the control tree is created. abap2UI5 exposes these instructions through the fluent builder so you can drive the expansion from ABAP data.
See the official UI5 references for the underlying mechanics:
[XML Templating](https://sapui5.hana.ondemand.com/sdk/#/topic/5ee619fc1370463ea674ee04b65ed83b),
[`template:repeat`](https://sapui5.hana.ondemand.com/sdk/#/topic/512e545ba66f4214ba0de1eb56f319e1),
[`template:if`](https://sapui5.hana.ondemand.com/sdk/#/topic/fc185952184c48618ef46306a1517f8c).
### How It Works
Templating happens **once**, at view instantiation, against a JSON model. The preprocessor walks the XML, evaluates each `template:` instruction against that model, and replaces the instruction with the resulting XML. After that the control tree is built from the expanded XML and normal data binding takes over.
This timing has two consequences:
- **Expansion is build-time.** A `template:repeat` over an internal table produces a fixed number of controls; the expanded XML is what UI5 renders.
- **Data changes do not re-template.** If the data driving the template changes, the existing expansion stays as is. To pick up the change you must rebuild the view (`view_display`) or the templated fragment (`nest_view_display`) — see [Re-rendering](#re-rendering) below.
abap2UI5 wires up the templating model for you. Every variable you bind with `client->_bind( ... )` is reachable inside templates via the `template>` model prefix:
| ABAP binding | Path inside templates |
| ---------------------------------- | --------------------------- |
| `client->_bind( mt_layout )` | `{template>/MT_LAYOUT}` |
| `client->_bind( mv_flag )` | `{template>/MV_FLAG}` |
The `template>` model is the templating engine's view of the data — distinct from the default model used by runtime bindings like `{MT_DATA}`.
### `template:repeat` — Loops
`template:repeat` clones its children once per row of the bound list. Use it when the **structure** of the view (e.g. which columns a table has) depends on data:
```abap
mt_layout = VALUE #( ( fname = `NAME` merge = `false` visible = `true` )
( fname = `DATE` merge = `false` visible = `true` )
( fname = `AGE` merge = `false` visible = `false` ) ).
client->_bind( mt_layout ).
view->ele( `Table`
)->a( n = `items` v = client->_bind( mt_data )
)->ele( `columns`
)->ele( n = `repeat` ns = `template`
)->a( n = `list` v = `{template>/MT_LAYOUT}`
)->a( n = `var` v = `L0`
)->ele( `Column`
)->a( n = `mergeDuplicates` v = `{L0>MERGE}`
)->a( n = `visible` v = `{L0>VISIBLE}`
)->tag( `Text`
)->a( n = `text` v = `{L0>FNAME}`
)->end(
)->end(
)->end(
)->ele( `items`
)->ele( `ColumnListItem`
)->ele( `cells`
)->ele( n = `repeat` ns = `template`
)->a( n = `list` v = `{template>/MT_LAYOUT}`
)->a( n = `var` v = `L1`
)->tag( `ObjectIdentifier`
)->a( n = `text` v = `{= '{' + ${L1>FNAME} + '}' }` ).
```
Notes on the snippet:
- `list` is the binding path that drives the loop; `var` is the alias used inside the loop body (here `L0` for the column headers, `L1` for the cells). `template` is an element namespace like any other, so the builder needs nothing beyond `ns = template` on the element — the prefix itself is declared once on the view's root.
- Inside the loop, `{L0>FNAME}` is a templating-time read — it ends up as the literal string `NAME`/`DATE`/`AGE` in the expanded XML.
- `{= '{' + ${L1>FNAME} + '}' }` is an [expression binding](https://sapui5.hana.ondemand.com/sdk/#/topic/daf6852a04b44d118963968a1239d2c0) that **constructs another binding string at templating time**. With `L1>FNAME = NAME` it expands to `text="{NAME}"`, which becomes a normal runtime binding against the row of `mt_data`. This is the standard pattern for templated tables: outer loop builds the columns, inner loop builds the cells, expression binding wires each cell to the right field of the row.
- `template_repeat` accepts the same optional attributes as the UI5 instruction (`startIndex`, `length`) plus list-binding extras like sorters and filters.
The full sample is `Z2UI5_CL_SMP_APP_173`.
### `template:if` / `template:then` / `template:else` — Conditionals
`template:if` evaluates an expression against the templating model and keeps or drops its children accordingly. With a `template:then` / `template:else` pair you get a two-branch switch:
```abap
client->_bind( mv_flag ).
view->ele( n = `if` ns = `template`
)->a( n = `test` v = `{template>/MV_FLAG}`
)->ele( n = `then` ns = `template`
)->tag( n = `Icon` ns = `core`
)->a( n = `src` v = `sap-icon://accept`
)->a( n = `color` v = `green`
)->end(
)->ele( n = `else` ns = `template`
)->tag( n = `Icon` ns = `core`
)->a( n = `src` v = `sap-icon://decline`
)->a( n = `color` v = `red` ).
```
The test argument follows the same rules as in UI5: any binding expression is fine, and the string `"false"` is treated as boolean `false` (a UI5 convenience). For richer conditions use expression binding, e.g. `` `{= ${template>/MV_COUNT} > 0 }` ``.
`template:elseif` is also supported by UI5, and needs nothing extra here: it is the same `ele` call with a different name, `ns = template` and a `test` attribute. That is the point of the builder — every templating instruction UI5 has is reachable the moment UI5 has it, without waiting for a method.
### Re-rendering
Because templating runs once, the view must be rebuilt for changes in the templating model to take effect. There are two strategies:
**Rebuild the whole view** — simplest, works for most apps. After the user toggles a flag, render the view again:
```abap
CASE client->get( )-event.
WHEN `CHANGE_FLAG`.
view_display( ). " builds and ships the view again
ENDCASE.
```
This is the pattern used in `Z2UI5_CL_SMP_APP_173`: the switch fires `CHANGE_FLAG`, `view_display` runs, the new value of `mv_flag` flows through `template:if`, and the icon swaps.
**Rebuild only the templated fragment** — keeps a stable shell on screen and replaces a nested piece. `Z2UI5_CL_SMP_APP_176` separates the main view from the templated table:
```abap
" Main view: built once, stays on screen
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Main View`
)->a( n = `id` v = `test` ). " ...
client->view_display( view->stringify( ) ).
" Nested templated view: inserted into the main view by id.
" The template namespace has to be declared on the nested view's root.
DATA(nested) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:template` v = `http://schemas.sap.com/sapui5/extension/sap.ui.core.template/1`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `Nested View`
)->ele( `Table`
)->a( n = `items` v = client->_bind( mt_data )
)->ele( `columns`
)->ele( n = `repeat` ns = `template`
)->a( n = `list` v = `{template>/MT_LAYOUT}`
)->a( n = `var` v = `L0`
)->ele( `Column` ). " ...
client->nest_view_display( val = nested->stringify( )
id = `test`
method_insert = `addContent` ).
```
`nest_view_display` targets a control in the existing view by id (`test`) and appends/replaces the nested view there. To refresh the templated piece on a data change, call `nest_view_display` again from the event handler — the main view is left untouched.
### Templating vs ABAP-side Composition
The two demo apps both build *dynamic* views, but with different mechanics. Pick based on what the dynamic part actually is:
| Situation | Approach |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Whether a control exists at all, decided once per render | ABAP `IF` around the builder call — simpler, no `template:` namespace involved |
| Number of controls comes from an internal table, computed once | ABAP `LOOP` over the table, calling the builder for each row |
| Reusable XML fragment that UI5 itself should expand against metadata | `template:repeat` / `template:if` — keeps the templating logic in the view layer |
| Cells of a table where the column set itself is data-driven | `template:repeat` — UI5 expands columns and cell bindings in one pass, as in app 173 |
Plain ABAP control flow covers most cases and is easier to debug. Reach for `template:` when you want the expansion to live in the view (closer to standard UI5 patterns) or when you are mapping a metadata-style structure onto controls.
### Tips
- Always bind the data that drives a template (`client->_bind`) **before** the builder call that references it. The binding registers the path that `{template>/...}` resolves against.
- Inside `template:repeat`, prefer `{var>FIELD}` over deeper paths — it keeps the body readable and lets you nest repeats with distinct `var` names (`L0`, `L1`, ...) without collisions.
- Use expression binding (`{= ... }`) when the value you need is a binding string itself. Templating-time expressions can read `${var>...}` and concatenate strings, which is how dynamic cell bindings are assembled.
- If a templated control does not update after a data change, you forgot to rebuild — call `view_display` or `nest_view_display` again.
See `Z2UI5_CL_SMP_APP_173` for `template:repeat` + `template:if` in a single view and `Z2UI5_CL_SMP_APP_176` for the stable-shell / templated-nested-view pattern.
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Build Columns Dynamically (template:repeat) | [`Z2UI5_CL_SMP_APP_173`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_173.clas.abap) |
| Dynamic Content in a Nested View | [`Z2UI5_CL_SMP_APP_176`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_176.clas.abap) |
---
# Binding
In abap2UI5 you share data between your ABAP code and the UI5 frontend with `client->_bind( )`. There is only one binding, and it works **in both directions**: when the value is changed in an editable control, the framework writes it back to your ABAP attribute before the next event handler runs. Only the paths the user actually edited are transported back (a delta), so read-only data costs nothing on the way back.
### Displaying Data
Bind an attribute to a display-only control (e.g. `text`) — nothing is editable there, so nothing syncs back:
```abap
CLASS zcl_app_hello_world DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA name TYPE string.
ENDCLASS.
CLASS zcl_app_hello_world IMPLEMENTATION.
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Hello World`
)->tag( `Text`
)->a( n = `text` v = `My Text`
)->tag( `Text`
)->a( n = `text` v = client->_bind( name ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
```
This method works with tables, trees, and other nested data structures — see [Tables](/cookbook/model/tables) and [Trees](/cookbook/model/trees).
### Editing Data
Bind an attribute to an editable control (e.g. `input`). After an event, the framework has already synced the user's changes back to your ABAP attribute:
```abap
CLASS zcl_app_hello_world DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA name TYPE string.
ENDCLASS.
CLASS zcl_app_hello_world IMPLEMENTATION.
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Hello World`
)->tag( `Text`
)->a( n = `text` v = `Enter your name`
)->tag( `Input`
)->a( n = `value` v = client->_bind( name )
)->tag( `Button`
)->a( n = `text` v = `post`
)->a( n = `press` v = client->_event( `POST` ) ).
client->view_display( view->stringify( ) ).
CASE client->get( )-event.
WHEN `POST`.
client->message_box_display( |Your name is { name }.| ).
RETURN.
ENDCASE.
ENDMETHOD.
ENDCLASS.
```
::: tip `_bind_edit` is obsolete
Earlier releases split binding into a display-only `_bind` and a writable `_bind_edit`. Both now write to the same model and behave identically, so `_bind_edit` is only an obsolete alias of `_bind` — prefer `_bind( )`. You will still find `_bind_edit` in older examples.
:::
::: warning **Bound Attributes Must Be Public**
`_bind( )` accesses your class attributes from outside the controller via dynamic ASSIGN. This only works for attributes in the `PUBLIC SECTION` — `PROTECTED` and `PRIVATE` attributes are not visible to the framework and silently fail to bind: the value never reaches the frontend and edits never sync back. There is no compile-time or runtime error.
Always declare bound data in `PUBLIC SECTION`. This resembles the PAI/PBO logic, where data lived in global variables. See also [Life Cycle → Lifecycle Pitfalls](/cookbook/event_navigation/life_cycle#lifecycle-pitfalls).
:::
### Binding to Structures
When the bound attribute is a structure, refer to a field directly with the `-` component selector. The framework generates one model path per field — the structure name and the component name, in upper case, joined by `/`:
```abap
TYPES: BEGIN OF ts_order,
customer TYPE string,
material TYPE string,
quantity TYPE i,
END OF ts_order.
DATA edit_row TYPE ts_order.
...
)->tag( `Input`
)->a( n = `value` v = client->_bind( edit_row-customer ) " resolves to {/EDIT_ROW/CUSTOMER}
)->tag( `Input`
)->a( n = `value` v = client->_bind( edit_row-material )
)->tag( `Input`
)->a( n = `value` v = client->_bind( edit_row-quantity )
```
Nested structures follow the same rule recursively (`edit_row-address-city` → `/EDIT_ROW/ADDRESS/CITY`). Internal tables of structures use one row context per item — see [Tables](/cookbook/model/tables).
### Data-Type Mapping
ABAP and UI5 do not share a type system. When ABAP values cross to the frontend they are serialized to JSON and then read by UI5 controls. The table below is the reference for how each ABAP type travels on the wire and which UI5 binding it pairs with. For the controls that need an explicit `type:` or formatter, the linked sections in [Formatter](/cookbook/model/formatter) show the full binding-string pattern.
| ABAP type | On the wire | Typical UI5 use | Notes |
| -------------------- | ------------------ | ------------------------------------------------------ | -------------------------------------------------------------------- |
| `string`, `c LENGTH n` | JSON string | `Input`, `Text` | Works without a formatter. |
| `i`, `int8`, `b`, `s` | JSON number | `Input type="Number"`, `Text` | Returned as string from inputs; cast back if you need an integer. |
| `p LENGTH n DECIMALS m`, `decfloat16`, `decfloat34` | JSON string | `Input`, `Text` + `sap.ui.model.type.Float`/`Currency` | Sent as a string to preserve precision. Locale formatting needs an explicit type — see [Currency](/cookbook/model/formatter#currency). |
| `f` (binary float) | JSON number | `Input`, `Text` + `sap.ui.model.type.Float` | Binary float — prefer `p` or `decfloat34` for monetary values to avoid rounding drift. |
| `n LENGTH n` | JSON string of digits | `Input` + `sap.ui.model.odata.type.String` with `isDigitSequence: true` | Without the constraint, leading zeros render literally — see [Digit Sequence](/cookbook/model/formatter#digit-sequence). |
| `d` | 8-char string `YYYYMMDD` | `DatePicker` + `sap.ui.model.type.Date` | Not an ISO date — a formatter is required for explicit locale or pattern control. See [Date](/cookbook/model/formatter#date). |
| `t` | 6-char string `HHMMSS` | `TimePicker` + `sap.ui.model.type.Time` | Same pattern as `d` — see [Time](/cookbook/model/formatter#time). |
| `abap_bool` (`X`/` `) | JSON string `"X"` / `""` | `CheckBox` with expression binding or ABAP-side conversion | UI5's `CheckBox` expects `true`/`false`, not `"X"` — see [Boolean](/cookbook/model/formatter#boolean). |
| `timestamp`, `timestampl`, `utclong` | JSON string (packed digits for `timestamp`/`timestampl`; ISO-like for `utclong`) | `DateTimePicker` + ABAP-side conversion or custom formatter | No built-in UI5 type reads them directly. Split into `d` + `t` or convert to a `yyyyMMddHHmmss` string — see [Timestamp](/cookbook/model/formatter#timestamp). |
| `xstring` | binary — must be base64-encoded in ABAP before binding | `Image`, `FileUploader`, `pdf_viewer` | The framework does not auto-encode. Convert with `cl_web_http_utility=>encode_x_base64( )` (or `cl_http_utility=>if_http_utility~encode_x_base64( )` on older releases) — see [PDF](/cookbook/device_capabilities/pdf) and [Upload / Download](/cookbook/device_capabilities/upload_download). |
| structure | JSON object | Bind individual fields with `struct-field` | One model path per field — see [Binding to Structures](#binding-to-structures). |
| internal table | JSON array | `Table`, `List`, `Tree` | One row context per item — see [Tables](/cookbook/model/tables) and [Trees](/cookbook/model/trees). |
When a value looks wrong, the fix is almost always a UI5-side `type` (e.g. `sap.ui.model.type.Date`, `sap.ui.model.type.Float`) or an abap2UI5 [Formatter](/cookbook/model/formatter). The shape is always the same — build a JSON binding string with `parts` and `type`, using `path = abap_true` on `_bind` to inject the raw model path:
```abap
)->tag( `Input`
)->a( n = `value` v = |\{ parts: [ `{ client->_bind( val = amount path = abap_true ) }`,
`{ client->_bind( val = currency path = abap_true ) }` ],
type: 'sap.ui.model.type.Currency' \}|
```
See [Formatter](/cookbook/model/formatter) for the full example with `formatOptions`, `constraints`, and read-only display variants.
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Basics II — Data Binding: Input and Button | [`Z2UI5_CL_SMP_APP_494`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_494.clas.abap) |
| Types for Integer, Decimal, Date and Time | [`Z2UI5_CL_SMP_APP_047`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_047.clas.abap) |
| Structure Fields and INCLUDEs | [`Z2UI5_CL_SMP_APP_166`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_166.clas.abap) |
| Single Table Cell (tab_index) | [`Z2UI5_CL_SMP_APP_144`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_144.clas.abap) |
| Dynamic Table Typed at Runtime (RTTI) | [`Z2UI5_CL_SMP_APP_061`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_061.clas.abap) |
---
# Expression Binding
Expression Binding lets you compute values directly in XML views with JavaScript-like expressions. This is especially handy in abap2UI5, since it cuts server roundtrips by moving calculations, logical conditions, and string operations to the frontend.
The syntax `{= ... }` marks a UI5 expression binding. Inside the expression, you can use JavaScript operators (like `===` for strict equality or `Math.max`) and reference model properties with `$` followed by a binding path. Note: `===` is the JavaScript strict equality operator (not an ABAP operator) — UI5 needs it because these expressions evaluate in the browser.
### Calculate the Maximum Value on the Frontend
The inputs use a UI5 type binding (`{ type: ..., path: "..." }`) for integer validation. The third input uses an expression binding (`{= ... }`) to compute the maximum of both values directly in the browser. What the ABAP string concatenation produces at runtime:
| ABAP code | UI5 binding result |
|---|---|
| `client->_bind( val = input31 path = abap_true )` | `/INPUT31` (raw path for type binding) |
| `client->_bind( input31 )` | `{/INPUT31}` (full binding for expression) |
| `` `{= Math.max($` && client->_bind( input31 ) && `, $` && client->_bind( input32 ) && `) }` `` | `{= Math.max(${/INPUT31}, ${/INPUT32}) }` |
```abap
CLASS z2ui5_cl_demo_app_max_val DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA input31 TYPE i.
DATA input32 TYPE i.
ENDCLASS.
CLASS z2ui5_cl_demo_app_max_val IMPLEMENTATION.
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Shell`
)->ele( `Page`
)->tag( `Label`
)->a( n = `text` v = `max value of the first two inputs`
" UI5 type binding — validates integer input
" resolves to: { type: "sap.ui.model.type.Integer", path: "/INPUT31" }
)->tag( `Input`
)->a( n = `value` v = `{ type : "sap.ui.model.type.Integer",` &&
` path:"` && client->_bind( val = input31
path = abap_true ) && `" }`
)->tag( `Input`
)->a( n = `value` v = `{ type : "sap.ui.model.type.Integer",` && |\n| &&
` path:"` && client->_bind( val = input32
path = abap_true ) && `" }`
" Expression binding — computed in the browser
" resolves to: {= Math.max(${/INPUT31}, ${/INPUT32}) }
)->tag( `Input`
)->a( n = `value` v = `{= Math.max($` && client->_bind( input31 ) && `, $` && client->_bind( input32 ) && `) }`
)->a( n = `enabled` b = abap_false ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
```
### Conditionally Set Input Field Editability
The `enabled` property uses an expression binding that resolves to `{= 500===${/QUANTITY} }` — the product field becomes editable only when the quantity equals 500 exactly. Note that `===` is the JavaScript strict equality operator.
```abap
CLASS z2ui5_cl_demo_editable DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA quantity TYPE i.
DATA product TYPE string.
ENDCLASS.
CLASS z2ui5_cl_demo_editable IMPLEMENTATION.
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Shell`
)->ele( `Page`
)->tag( `Label`
)->a( n = `text` v = `only enabled when the quantity equals 500`
)->tag( `Input`
)->a( n = `value` v = `{ type : "sap.ui.model.type.Integer",` &&
` path:"` && client->_bind( val = quantity
path = abap_true ) && `" }`
" enabled resolves to: {= 500===${/QUANTITY} }
)->tag( `Input`
)->a( n = `value` v = client->_bind( product )
)->a( n = `enabled` v = `{= 500===$` && client->_bind( quantity ) && ` }` ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
```
For all options, see the sample class `Z2UI5_CL_SMP_APP_027` or the [UI5 docs on expression binding](https://sapui5.hana.ondemand.com/sdk/#/topic/daf6852a04b44d118963968a1239d2c0).
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Expression Binding, Types and Composite Parts | [`Z2UI5_CL_SMP_APP_027`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_027.clas.abap) |
---
# Formatter
You can format values like currencies, numerics, dates, or booleans directly on the frontend with UI5 type formatters.
UI5 formatter types use a special JSON-based binding syntax with these key elements:
- **`parts: [...]`** — lists the model paths used as input (e.g., amount + currency)
- **`type: '...'`** — the UI5 formatter type (e.g., `sap.ui.model.type.Currency`)
- **`formatOptions: {...}`** — optional settings that control the output format
- **`constraints: {...}`** — optional constraints applied when user input is parsed back
Note on ABAP syntax: inside string templates (`|...|`), escape the curly braces as `\{` and `\}`, because `{ }` normally denotes an embedded ABAP expression.
The `path = abap_true` parameter on `_bind` returns only the raw model path rather than the full binding expression, so you can embed it inside the `parts` array or a single-path `path:` entry. The path is e.g. `/AMOUNT`.
For example, this ABAP code:
```abap
|\{ parts: [`{ client->_bind( val = amount path = abap_true ) }`], type: 'sap.ui.model.type.Currency' \}|
```
produces this UI5 binding string at runtime:
```text
{ parts: ["/AMOUNT"], type: 'sap.ui.model.type.Currency' }
```
The sections below show the binding-string pattern for each ABAP type that needs a formatter. Each pattern is the minimum that makes the value display and parse correctly — for runnable apps with full `formatOptions`, `constraints`, and read-only variants, see the [samples repository](https://github.com/abap2UI5/samples).
## Currency
ABAP `p LENGTH n DECIMALS m` + a `c LENGTH 3` currency code (a plain `string` also works, as in the worked example below) → UI5 `Currency` formatter. Two `parts` entries; the type combines them into a locale-aware amount string:
```abap
)->tag( `Input`
)->a( n = `value` v = |\{ parts: [ `{ client->_bind( val = amount path = abap_true ) }`,
`{ client->_bind( val = currency path = abap_true ) }` ],
type: 'sap.ui.model.type.Currency' \}|
```
Common `formatOptions`:
- `showMeasure: false` — hides the currency symbol
- `showNumber: false` — hides the amount, shows only the symbol
- `preserveDecimals: false` — trims trailing zeros
- `currencyCode: false` — hides the ISO code
- `style: 'short'` / `'long'` — compact (`123M`) or full-text (`123 million US dollars`) notation
The [Full Worked Example](#full-worked-example) below demonstrates each of these variants in a single app.
## Digit Sequence
ABAP `n LENGTH n` is sent as a digit string, leading zeros included. Without a type the zeros render literally. Use the OData `String` type with `isDigitSequence: true`:
```abap
)->tag( `Text`
)->a( n = `text` v = |\{ path: `{ client->_bind( val = numeric path = abap_true ) }`,
type: 'sap.ui.model.odata.type.String',
constraints: \{ isDigitSequence: true \} \}|
```
This strips the leading zeros for display and re-pads them on write-back.
## Date
ABAP `d` is an 8-character string `YYYYMMDD`. `DatePicker` accepts it directly via `client->_bind( mv_date )` for the default case. For explicit locale or pattern control, use `sap.ui.model.type.Date` with a `source` pattern that matches the wire format:
```abap
)->tag( `DatePicker`
)->a( n = `value` v = |\{ path: `{ client->_bind( val = mv_date path = abap_true ) }`,
type: 'sap.ui.model.type.Date',
formatOptions: \{ pattern: 'yyyy-MM-dd',
source: \{ pattern: 'yyyyMMdd' \} \} \}|
```
`source.pattern` is the wire format (ABAP side); the outer `pattern` is what the user sees.
## Time
ABAP `t` is a 6-character string `HHMMSS`. Same pattern as Date, with `sap.ui.model.type.Time`:
```abap
)->tag( `TimePicker`
)->a( n = `value` v = |\{ path: `{ client->_bind( val = mv_time path = abap_true ) }`,
type: 'sap.ui.model.type.Time',
formatOptions: \{ pattern: 'HH:mm:ss',
source: \{ pattern: 'HHmmss' \} \} \}|
```
## Boolean
ABAP `abap_bool` is `"X"` or `""`. UI5's `CheckBox` expects `true` / `false`. Two practical bridges:
**Expression binding** — compare the bound value to `'X'` inline. Read-only:
```abap
)->tag( `CheckBox`
)->a( n = `selected` v = `{= $` && client->_bind( mv_flag ) && ` === 'X' }`
```
This resolves to `{= ${/MV_FLAG} === 'X' }`. Note that expression bindings cannot write back — checking the box will not flip the ABAP attribute.
**ABAP-side conversion** — keep a parallel `string`-typed attribute (`'true'` / `'false'`) to bind against, and translate before/after each event:
```abap
DATA flag_bool TYPE abap_bool.
DATA flag_str TYPE string. " 'true' / 'false' for the checkbox
" before view_display:
flag_str = COND #( WHEN flag_bool = abap_true THEN 'true' ELSE 'false' ).
" after the event:
flag_bool = COND #( WHEN flag_str = 'true' THEN abap_true ELSE abap_false ).
```
Then a `CheckBox` whose `selected` attribute is `client->_bind( flag_str )` works both directions. More boilerplate in the controller, simpler view.
A custom JS formatter wired through `sap.ui.model.SimpleType` is the third option — see the [samples repository](https://github.com/abap2UI5/samples).
## Timestamp
`timestamp` and `timestampl` are packed numbers with no built-in UI5 type that reads them directly. Two practical approaches:
**Split in ABAP** — break the timestamp into separate `d` and `t` fields before sending, bind each with the [Date](#date) / [Time](#time) formatter above, recombine after the event. Simplest when the UI shows date and time as separate fields anyway.
**Send as string with a source pattern** — convert to a string in `yyyyMMddHHmmss` format on the ABAP side, then bind with `sap.ui.model.type.DateTime`:
```abap
)->tag( `DateTimePicker`
)->a( n = `value` v = |\{ path: `{ client->_bind( val = mv_ts_string path = abap_true ) }`,
type: 'sap.ui.model.type.DateTime',
formatOptions: \{ pattern: 'yyyy-MM-dd HH:mm:ss',
source: \{ pattern: 'yyyyMMddHHmmss' \} \} \}|
```
Conversion happens in ABAP (`WRITE timestamp TO ts_string …` or a helper); the framework moves the string verbatim.
A custom JS formatter is the third option when neither fits.
## Full Worked Example
The class below combines the Currency and Digit Sequence patterns in one app and demonstrates every `formatOptions` variant listed under [Currency](#currency):
```abap
CLASS z2ui5_cl_smp_app_067 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA amount TYPE p LENGTH 14 DECIMALS 3.
DATA currency TYPE string.
DATA numeric TYPE n LENGTH 12.
DATA check_initialized TYPE abap_bool.
ENDCLASS.
CLASS z2ui5_cl_smp_app_067 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
IF check_initialized = abap_false.
check_initialized = abap_true.
numeric = `000000000012`.
amount = `123456789.123`.
currency = `USD`.
ENDIF.
CASE client->get( )-event.
WHEN |BACK|.
client->nav_app_leave( client->get_app( client->get( )-s_draft-id_prev_app_stack ) ).
WHEN |BUTTON|.
" the roundtrip is the point of this button: the edited values travel
" to the server and come back, and every formatter above renders them
" again from the model
client->message_toast_display( |Amount { amount }, currency { currency }| ).
ENDCASE.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:form` v = `sap.ui.layout.form`
)->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Currency Format`
)->a( n = `navButtonPress` v = client->_event( |BACK| )
)->a( n = `showNavButton` b = xsdbool( client->get( )-s_draft-id_prev_app_stack IS NOT INITIAL )
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Currency`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Title`
)->a( n = `text` v = `Input`
)->tag( `Label`
)->a( n = `text` v = `Documentation`
)->tag( `Link`
)->a( n = `text` v = `https://sdk.openui5.org/api/sap.ui.model.type.Currency`
)->a( n = `href` v = `https://sdk.openui5.org/api/sap.ui.model.type.Currency`
)->tag( `Label`
)->a( n = `text` v = `One field`
)->tag( `Input`
)->a( n = `value` v = |\{ parts: [ `{ client->_bind( val = amount
path = abap_true ) }`, `{ client->_bind( val = currency
path = abap_true ) }`], type: 'sap.ui.model.type.Currency' \}|
)->tag( `Label`
)->a( n = `text` v = `Two fields`
)->tag( `Input`
)->a( n = `value` v = |\{ parts: [ `{ client->_bind( val = amount
path = abap_true ) }`, `{ client->_bind( val = currency
path = abap_true ) }`], type: 'sap.ui.model.type.Currency', formatOptions: \{showMeasure: false\} \}|
)->tag( `Label`
)->a( n = `text` v = `Two fields`
)->tag( `Input`
)->a( n = `value` v = |\{ parts: [ `{ client->_bind( val = amount
path = abap_true ) }`, `{ client->_bind( val = currency
path = abap_true ) }`], type: 'sap.ui.model.type.Currency', formatOptions: \{showNumber: false\} \}|
)->tag( `Label`
)->a( n = `text` v = `Default`
)->tag( `Text`
)->a( n = `text` v = |\{ parts: [ `{ client->_bind( val = amount
path = abap_true ) }`, `{ client->_bind( val = currency
path = abap_true ) }`], type: 'sap.ui.model.type.Currency' \}|
)->tag( `Label`
)->a( n = `text` v = `preserveDecimals:false`
)->tag( `Text`
)->a( n = `text` v = |\{ parts: [ `{ client->_bind( val = amount
path = abap_true ) }`, `{ client->_bind( val = currency
path = abap_true ) }`], type: 'sap.ui.model.type.Currency', formatOptions: \{ preserveDecimals : false \} \}|
)->tag( `Label`
)->a( n = `text` v = `currencyCode:false`
)->tag( `Text`
)->a( n = `text` v = |\{ parts: [ `{ client->_bind( val = amount
path = abap_true ) }`, `{ client->_bind( val = currency
path = abap_true ) }`], type: 'sap.ui.model.type.Currency', formatOptions: \{ currencyCode : false \} \}|
)->tag( `Label`
)->a( n = `text` v = `style:'short'`
)->tag( `Text`
)->a( n = `text` v = |\{ parts: [ `{ client->_bind( val = amount
path = abap_true ) }`, `{ client->_bind( val = currency
path = abap_true ) }`], type: 'sap.ui.model.type.Currency', formatOptions: \{ style : 'short' \} \}|
)->tag( `Label`
)->a( n = `text` v = `style:'long'`
)->tag( `Text`
)->a( n = `text` v = |\{ parts: [ `{ client->_bind( val = amount
path = abap_true ) }`, `{ client->_bind( val = currency
path = abap_true ) }`], type: 'sap.ui.model.type.Currency', formatOptions: \{ style : 'long' \} \}|
)->tag( `Label`
)->a( n = `text` v = `event`
)->tag( `Button`
)->a( n = `text` v = `send`
)->a( n = `press` v = client->_event( `BUTTON` )
)->end(
)->end(
" Remove leading zeros from a numeric string with OData type formatting.
" isDigitSequence: true tells the formatter to treat the value as a digit
" sequence — resolves to: { path: "/NUMERIC",
" type: 'sap.ui.model.odata.type.String',
" constraints: { isDigitSequence: true } }
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `No Zeros`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Title`
)->a( n = `text` v = `Input`
)->tag( `Label`
)->a( n = `text` v = `Documentation`
)->tag( `Link`
)->a( n = `text` v = `https://sdk.openui5.org/api/sap.ui.model.odata.type.String%23methods/formatValue`
)->a( n = `href` v = `https://sdk.openui5.org/api/sap.ui.model.odata.type.String%23methods/formatValue`
)->tag( `Label`
)->a( n = `text` v = `Numeric`
)->tag( `Input`
)->a( n = `value` v = client->_bind( val = numeric )
)->tag( `Label`
)->a( n = `text` v = `Without leading Zeros`
)->tag( `Text`
)->a( n = `text` v = |\{ path : `{ client->_bind( val = numeric
path = abap_true ) }`, type : 'sap.ui.model.odata.type.String', constraints : \{ isDigitSequence : true \} \}| ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
```
For a full runnable copy, see the sample implementation in class `Z2UI5_CL_SMP_APP_067` in the [samples repository](https://github.com/abap2UI5/samples).
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Currency Amounts (sap.ui.model.type.Currency) | [`Z2UI5_CL_SMP_APP_067`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_067.clas.abap) |
| ABAP Date and Time Strings (DATS/TIMS) | [`Z2UI5_CL_SMP_APP_450`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_450.clas.abap) |
| Date Object for the DatePicker | [`Z2UI5_CL_SMP_APP_457`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_457.clas.abap) |
| Date Objects for the PlanningCalendar | [`Z2UI5_CL_SMP_APP_456`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_456.clas.abap) |
| Inline Icons in a Text | [`Z2UI5_CL_SMP_APP_466`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_466.clas.abap) |
| When Not to Use One: Compute in ABAP | [`Z2UI5_CL_SMP_APP_453`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_453.clas.abap) |
---
# Tables
This section walks through rendering tabular and nested data in views.
### Basic Table
The example below binds a simple table to a UI5 control:
```abap
CLASS z2ui5_cl_sample_tab DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
TYPES:
BEGIN OF ty_row,
count TYPE i,
value TYPE string,
descr TYPE string,
END OF ty_row.
DATA mt_itab TYPE STANDARD TABLE OF ty_row WITH EMPTY KEY.
ENDCLASS.
CLASS z2ui5_cl_sample_tab IMPLEMENTATION.
METHOD z2ui5_if_app~main.
IF client->check_on_navigated( ).
DO 100 TIMES.
INSERT VALUE #(
count = sy-index
value = `red`
descr = `this is a description` ) INTO TABLE mt_itab.
ENDDO.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->ele( `Table`
)->a( n = `items` v = client->_bind( mt_itab )
)->ele( `columns`
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Count`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Value`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Description`
)->end(
)->end(
)->ele( `items`
)->ele( `ColumnListItem`
)->ele( `cells`
)->tag( `Text`
)->a( n = `text` v = `{COUNT}`
)->tag( `Text`
)->a( n = `text` v = `{VALUE}`
)->tag( `Text`
)->a( n = `text` v = `{DESCR}` ).
client->view_display( view->stringify( ) ).
ENDIF.
ENDMETHOD.
ENDCLASS.
```
### Editable
To make a table editable, use editable cell controls (e.g. `input`) — the binding is the same `_bind`:
```abap
METHOD z2ui5_if_app~main.
IF client->check_on_navigated( ).
DO 100 TIMES.
INSERT VALUE #(
count = sy-index
value = `red`
descr = `this is a description` ) INTO TABLE mt_itab.
ENDDO.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->ele( `Table`
)->a( n = `items` v = client->_bind( mt_itab )
)->ele( `columns`
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Count`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Value`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Description`
)->end(
)->end(
)->ele( `items`
)->ele( `ColumnListItem`
)->ele( `cells`
)->tag( `Input`
)->a( n = `value` v = `{COUNT}`
)->tag( `Input`
)->a( n = `value` v = `{VALUE}`
)->tag( `Input`
)->a( n = `value` v = `{DESCR}` ).
client->view_display( view->stringify( ) ).
ENDIF.
ENDMETHOD.
```
### Nested Structures
You can also bind nested structures — use `structure/component` as the binding path:
```abap
CLASS z2ui5_cl_sample_nested_structures DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
TYPES:
BEGIN OF ty_s_tab,
product TYPE string,
BEGIN OF s_details,
create_date TYPE string,
create_by TYPE string,
END OF s_details,
END OF ty_s_tab.
DATA mt_itab TYPE STANDARD TABLE OF ty_s_tab WITH EMPTY KEY.
ENDCLASS.
CLASS z2ui5_cl_sample_nested_structures IMPLEMENTATION.
METHOD z2ui5_if_app~main.
mt_itab = VALUE #(
( product = `table` s_details = VALUE #( create_date = `01.01.2023` create_by = `Peter` ) )
( product = `chair` s_details = VALUE #( create_date = `25.10.2022` create_by = `Frank` ) )
( product = `sofa` s_details = VALUE #( create_date = `12.03.2024` create_by = `George` ) ) ).
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Table`
)->a( n = `items` v = client->_bind( mt_itab )
)->ele( `columns`
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Product`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Created at`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `By`
)->end(
)->end(
)->ele( `items`
)->ele( `ColumnListItem`
)->ele( `cells`
)->tag( `Text`
)->a( n = `text` v = `{PRODUCT}`
)->tag( `Text`
" abap2ui5lint-disable-next-line unknown-binding-path -- linter defect, fixed in @abap2ui5/linter 0.2.0: a nested BEGIN OF inside a row type was dropped. Delete this line with the pin bump; the path is correct
)->a( n = `text` v = `{S_DETAILS/CREATE_DATE}`
)->tag( `Text`
" abap2ui5lint-disable-next-line unknown-binding-path -- same, and it goes with the same pin bump
)->a( n = `text` v = `{S_DETAILS/CREATE_BY}` ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
```
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Editable Cells, Add and Delete Rows | [`Z2UI5_CL_SMP_APP_011`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_011.clas.abap) |
| Selection Modes: Single and Multi Select | [`Z2UI5_CL_SMP_APP_019`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_019.clas.abap) |
| Large Table with Growing and ScrollContainer | [`Z2UI5_CL_SMP_APP_006`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_006.clas.abap) |
| Filter Rows in the Backend | [`Z2UI5_CL_SMP_APP_045`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_045.clas.abap) |
| Search in the Backend (SearchField) | [`Z2UI5_CL_SMP_APP_053`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_053.clas.abap) |
| Live Search with Parallel Requests | [`Z2UI5_CL_SMP_APP_059`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_059.clas.abap) |
| Full Example with sap.ui.table | [`Z2UI5_CL_SMP_APP_070`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_070.clas.abap) |
| Events on Cell Level | [`Z2UI5_CL_SMP_APP_160`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_160.clas.abap) |
| Keep Column Filters on Refresh (C) | [`Z2UI5_CL_SMP_APP_143`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_143.clas.abap) |
| Drag and Drop Rows (A) | [`Z2UI5_CL_SMP_APP_459`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_459.clas.abap) |
---
# Trees
For hierarchical data, abap2UI5 uses nested ABAP structures to represent tree levels. Each level holds a table of child nodes, which UI5 traverses to build the expandable tree control.
### Tree
Define a type hierarchy where each node contains a child table of the next level:
```abap
CLASS z2ui5_cl_sample_tree DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
TYPES:
BEGIN OF ty_prodh_node_level3,
is_selected TYPE abap_bool,
text TYPE string,
prodh TYPE string,
END OF ty_prodh_node_level3,
BEGIN OF ty_prodh_node_level2,
is_selected TYPE abap_bool,
text TYPE string,
prodh TYPE string,
nodes TYPE STANDARD TABLE OF ty_prodh_node_level3 WITH DEFAULT KEY,
END OF ty_prodh_node_level2,
BEGIN OF ty_prodh_node_level1,
is_selected TYPE abap_bool,
text TYPE string,
prodh TYPE string,
nodes TYPE STANDARD TABLE OF ty_prodh_node_level2 WITH DEFAULT KEY,
END OF ty_prodh_node_level1,
ty_prodh_nodes TYPE STANDARD TABLE OF ty_prodh_node_level1 WITH DEFAULT KEY.
DATA prodh_nodes TYPE ty_prodh_nodes.
ENDCLASS.
CLASS z2ui5_cl_sample_tree IMPLEMENTATION.
METHOD z2ui5_if_app~main.
prodh_nodes = VALUE #( (
text = `Machines`
prodh = `00100`
nodes = VALUE #( (
text = `Pumps`
prodh = `0010000100`
nodes = VALUE #( (
text = `Pump 001`
prodh = `001000010000000100` ) (
text = `Pump 002`
prodh = `001000010000000105` ) )
) ) ) (
text = `Paints`
prodh = `00110`
nodes = VALUE #( (
text = `Gloss paints`
prodh = `0011000105`
nodes = VALUE #( (
text = `Paint 001`
prodh = `001100010500000100` ) (
text = `Paint 002`
prodh = `001100010500000105` )
) ) ) ) ).
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->ele( `Tree`
)->a( n = `items` v = client->_bind( prodh_nodes )
)->ele( `items`
)->tag( `StandardTreeItem`
)->a( n = `selected` v = `{IS_SELECTED}`
)->a( n = `title` v = `{TEXT}` ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
```
The child table field (`nodes` in the example above) is the key: UI5 follows that field name to locate sub-items at each level. The name must match across all levels but can be anything you choose.
Note that the example binds `IS_SELECTED` to an editable control so the user's selection is synced back to ABAP. A display-only tree needs no editable cells.
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Nested ABAP Table in a sap.m.Tree | [`Z2UI5_CL_SMP_APP_460`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_460.clas.abap) |
| Drag and Drop Nodes (A,C) | [`Z2UI5_CL_SMP_APP_461`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_461.clas.abap) |
| Inside a Dialog (C) | [`Z2UI5_CL_SMP_APP_462`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_462.clas.abap) |
| Editable Nodes with CustomTreeItem (C) | [`Z2UI5_CL_SMP_APP_463`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_463.clas.abap) |
---
# Device Model
abap2UI5 offers two ways to access device information: directly in the view via the UI5 device model (frontend), or in ABAP logic via `client->get( )-s_device` (backend).
### Frontend
By default, the device model binds to the view under the name `device`. Use standard UI5 binding syntax to show device properties directly — no backend roundtrip needed:
```abap
)->tag( `Input`
)->a( n = `description` v = `device model - resize - width`
)->a( n = `value` v = `{device>/resize/width}` )
```
For all parameters, see the [UI5 docs](https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui.Device).
### Backend
When you need device information in your ABAP logic (e.g., to adapt behavior based on the browser or screen size), read it from `client->get( )-s_device` — no custom control, no extra event needed. The value is shipped with every roundtrip:
```abap
DATA(device) = client->get( )-s_device.
DATA(system) = device-system. " e.g. `desktop`, `phone`, `tablet`
DATA(orientation) = device-orientation. " `landscape` | `portrait`
DATA(browser) = device-browser-name. " e.g. `chrome`
DATA(brw_version) = device-browser-version.
DATA(os) = device-os-name. " e.g. `win`, `mac`, `ios`, `android`
DATA(os_version) = device-os-version.
DATA(width) = device-resize-width. " current viewport, in px
DATA(height) = device-resize-height.
DATA(touch) = device-support-touch.
DATA(pointer) = device-support-pointer.
DATA(retina) = device-support-retina.
```
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Device Model: Phone, Tablet, Desktop (A) | [`Z2UI5_CL_SMP_APP_445`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_445.clas.abap) |
---
# Size Limit
Every UI5 JSON model has a built-in upper limit on the number of items it will expose to a list binding. By default this limit is `100`. If you bind a `ComboBox`, `Table` or any other aggregation to a table that contains more than 100 entries, only the first 100 are rendered — the rest are silently dropped. This is a UI5 design decision, documented under [`sap.ui.model.Model#setSizeLimit`](https://sapui5.hana.ondemand.com/sdk/#/api/sap.ui.model.Model%23methods/setSizeLimit).
abap2UI5 exposes this setting through the built-in client event `SET_SIZE_LIMIT`, so you can raise (or reset) the limit per view directly from ABAP.
### Set the Limit
Trigger the event from your controller with `client->follow_up_action`. The first argument is the new limit, the second is the view key — use the constants `client->cs_view-main`, `-nested`, `-nested2`, `-popup`, or `-popover` to stay type-safe (their underlying values are `MAIN`, `NEST`, `NEST2`, `POPUP`, `POPOVER`):
```abap
client->follow_up_action(
val = z2ui5_if_client=>cs_event-set_size_limit
t_arg = VALUE #(
( `1000` )
( client->cs_view-main ) ) ).
```
After this call, the model bound to the main view accepts up to 1000 entries per binding. The setting is remembered across roundtrips — once raised, it stays in effect until you reset it or leave the app.
### Reset the Limit
To restore the default of `100`, omit the limit argument and pass only the view key:
```abap
client->follow_up_action(
val = z2ui5_if_client=>cs_event-set_size_limit
t_arg = VALUE #( ( client->cs_view-main ) ) ).
```
### Complete Example
The snippet below shows a `ComboBox` filled with 105 entries. Without raising the size limit, the dropdown would stop at item 100. A small form lets the user adjust the limit and the number of entries at runtime:
```abap
CLASS z2ui5_cl_sample_size_limit DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
TYPES:
BEGIN OF ty_s_combo,
key TYPE string,
text TYPE string,
END OF ty_s_combo.
DATA t_combo TYPE STANDARD TABLE OF ty_s_combo WITH EMPTY KEY.
DATA mv_size_limit TYPE i VALUE 100.
DATA mv_combo_number TYPE i VALUE 105.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS z2ui5_cl_sample_size_limit IMPLEMENTATION.
METHOD z2ui5_if_app~main.
CASE client->get( )-event.
WHEN `UPDATE_LIMIT`.
client->follow_up_action(
val = z2ui5_if_client=>cs_event-set_size_limit
t_arg = VALUE #( ( CONV #( mv_size_limit ) ) ( client->cs_view-main ) ) ).
client->message_toast_display( `Size limit updated` ).
RETURN.
WHEN `UPDATE_MODEL`.
t_combo = VALUE #( ).
DO mv_combo_number TIMES.
INSERT VALUE #( key = sy-index text = sy-index ) INTO TABLE t_combo.
ENDDO.
RETURN.
ENDCASE.
DO mv_combo_number TIMES.
INSERT VALUE #( key = sy-index text = sy-index ) INTO TABLE t_combo.
ENDDO.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->a( n = `xmlns:core` v = `sap.ui.core`
)->a( n = `xmlns:form` v = `sap.ui.layout.form`
)->ele( `Page`
)->a( n = `title` v = `Size Limit Demo`
)->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `Settings`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `setSizeLimit`
)->tag( `Input`
)->a( n = `value` v = client->_bind( mv_size_limit )
)->tag( `Button`
)->a( n = `text` v = `update size limit`
)->a( n = `press` v = client->_event( `UPDATE_LIMIT` )
)->tag( `Label`
)->a( n = `text` v = `Number of Entries`
)->tag( `Input`
)->a( n = `value` v = client->_bind( mv_combo_number )
)->tag( `Button`
)->a( n = `text` v = `update number of entries`
)->a( n = `press` v = client->_event( `UPDATE_MODEL` )
)->tag( `Label`
)->a( n = `text` v = `ComboBox`
)->ele( `ComboBox`
)->a( n = `items` v = client->_bind( t_combo )
)->tag( n = `Item` ns = `core`
)->a( n = `key` v = `{KEY}`
)->a( n = `text` v = `{TEXT}` ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
ENDCLASS.
```
### Other Views
The same call applies to nested views, popups and popovers — just swap the view key:
```abap
" Popup
client->follow_up_action(
val = z2ui5_if_client=>cs_event-set_size_limit
t_arg = VALUE #( ( `500` ) ( client->cs_view-popup ) ) ).
" Popover
client->follow_up_action(
val = z2ui5_if_client=>cs_event-set_size_limit
t_arg = VALUE #( ( `500` ) ( client->cs_view-popover ) ) ).
" Nested view
client->follow_up_action(
val = z2ui5_if_client=>cs_event-set_size_limit
t_arg = VALUE #( ( `500` ) ( client->cs_view-nested ) ) ).
```
::: tip **When to raise it**
Raise the limit only as high as you actually need. Large bindings increase memory consumption on the frontend and slow down rendering. For very large datasets, prefer a server-side pattern (OData with `growing`, paging, filtering) instead of pushing everything into the model.
:::
For a runnable sample, see `Z2UI5_CL_SMP_APP_071` in the [samples repository](https://github.com/abap2UI5/samples).
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Model setSizeLimit for Large Tables (A) | [`Z2UI5_CL_SMP_APP_071`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_071.clas.abap) |
---
# Life Cycle
Every request enters the `main` method. `CASE abap_true` dispatches between initialization, navigation returns, and user events using `` client->check_on_init( ) ``, `` client->check_on_event( `EVENT_NAME` ) ``, and `` client->check_on_navigated( ) ``. Each branch either does the work inline (tiny apps) or calls a named handler method (typical apps) — the **structure is always the same**.
```abap
CLASS z2ui5_cl_demo_app_001 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
" State — public so binding works (see Lifecycle Pitfalls below)
DATA value TYPE string.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
METHODS render_main.
METHODS on_post.
ENDCLASS.
CLASS z2ui5_cl_demo_app_001 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
CASE abap_true.
WHEN client->check_on_init( ).
render_main( ).
WHEN client->check_on_event( `POST` ).
on_post( ).
WHEN client->check_on_navigated( ).
" optional: refresh state when returning from another app
ENDCASE.
ENDMETHOD.
ENDCLASS.
```
Whether you dispatch with `CASE abap_true` (as above) or with an equivalent `IF` / `ELSEIF` chain (as the [Hello World](/get_started/hello_world) and [Full Example](/get_started/full_example) tutorials do) is a matter of taste — the structure is what counts. Three things make this the recommended shape:
1. **Store `client` on `me->client`** so handler methods can use it without passing it around.
2. **Dispatch by event name** — `` check_on_event( `POST` ) `` rather than a generic `` CASE client->get( )-event `` with a second dispatch level. Each event gets its own `WHEN`. (With many events or extracted handler methods, the `CASE client->get( )-event` form is a fine alternative — the [Full Example](/get_started/full_example) uses it.)
3. **One render method per view** — call it from any `WHEN` that should rebuild the screen (`check_on_init`, after a search, after a navigation return). Event handlers that only mutate state and reuse the existing view (a button press inside a popup, a toast) skip it — see [The View Is Only Sent When You Call `view_display`](#the-view-is-only-sent-when-you-call-view-display) below.
For a tiny app with one or two events, inline the view and the handler directly in the `WHEN` branches and skip the handler methods entirely. [Hello World](/get_started/hello_world) shows this variant; [Full Example](/get_started/full_example) shows the full version with multiple handler methods, a popup, and persistence. Both follow the same pattern — only the amount of code inside each branch differs.
## Lifecycle Pitfalls
A few details of the request lifecycle are easy to miss and produce bugs that look like framework issues but are actually pattern mistakes. These are not enforced by the compiler and not reported at runtime.
### Bound Attributes Must Be Public
Anything passed to `client->_bind( )` must live in `PUBLIC SECTION` — the framework binds via dynamic ASSIGN and silently ignores `PROTECTED`/`PRIVATE` attributes. Helper variables that never appear in a `_bind( )` call can stay private. Details and rationale on [Binding → Bound Attributes Must Be Public](/cookbook/model/binding).
### The View Is Only Sent When You Call `view_display`
abap2UI5 does not re-render the view automatically. After an event, if you do **not** call `client->view_display( ... )` again, the frontend keeps the previous view tree and only the model data is updated from the serialized state. This is the common case — most event handlers should mutate state and return, leaving the view alone.
Call `view_display( )` again only when the **structure** of the view needs to change: different controls, different bindings, a new dialog, navigation to a different screen. Rebuilding and re-sending the view on every event is wasteful and can cause visible flicker, lost scroll position, and lost focus.
### Returning From a Sub-App Hits `check_on_navigated`, Not `check_on_init`
`check_on_init( )` is `abap_true` **exactly once** — on the very first call of an app instance. It does *not* fire again when control comes back to the app after a `nav_app_call( )` (a popup or a fullscreen sub-app) is closed with `nav_app_leave( )`. That return is signalled by `check_on_navigated( )`.
This trips up apps that build their view only under `check_on_init`:
```abap
" WRONG — screen is blank after returning from the sub-app
CASE abap_true.
WHEN client->check_on_init( ).
render_main( ). " runs only the first time
WHEN client->check_on_event( `OPEN_POPUP` ).
client->nav_app_call( ... ).
ENDCASE.
```
When the sub-app is left, `main` runs again, but neither `check_on_init` nor any event matches, so `view_display( )` is never called and the user is left looking at a stale or empty screen. Render from the navigation-return branch as well — `check_on_navigated( )` fires both on the first display *and* on every return, so reacting to it alone is usually enough:
```abap
" CORRECT — the view is rebuilt on first display and on every return
CASE abap_true.
WHEN client->check_on_navigated( ).
render_main( ).
WHEN client->check_on_event( `OPEN_POPUP` ).
client->nav_app_call( ... ).
ENDCASE.
```
Reserve `check_on_init` for one-time setup that must *not* repeat on return (loading initial data, setting defaults). As a rule of thumb: anything needed to **show the screen** belongs in a branch that also fires on navigation return.
### `check_on_event` Fires Once Per Roundtrip
Every HTTP request carries at most one event. `check_on_event( )` returns `abap_true` exactly once per call to `main`, for that single event. If the user clicks two buttons in quick succession, the framework dispatches them as two independent `main` invocations — they are never batched into one request.
Two consequences follow:
- **Do not assume event ordering inside one `main`.** You cannot look at "the previous event" from within an event handler; the previous event ran in a separate request and the work process has been released since.
- **State across events lives in public class attributes.** Between two events, abap2UI5 serializes the controller to the client and deserializes it on the next request. Anything stored in public attributes (and in serializable types) survives; local variables, open cursors, and acquired locks do not. For sessions that need surviving server-side resources, see [Statefulness](/cookbook/expert_more/statefulness).
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Basics III — Lifecycle: Init, Event, Navigated | [`Z2UI5_CL_SMP_APP_495`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_495.clas.abap) |
| Basics IV — Events, Views and Roundtrips | [`Z2UI5_CL_SMP_APP_004`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_004.clas.abap) |
---
# Backend
UI5 control properties can both display data and fire events. To run backend logic when an event fires, use the `client->_event` method.
## Basic
As an example, we use the `press` property of a button. To fire events to the backend, assign the result of `client->_event( 'MY_EVENT_NAME' )` to the matching UI5 control property. The backend can then read the event details with `client->get( )-event`.
```abap
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->tag( `Button`
)->a( n = `text` v = `post`
)->a( n = `press` v = client->_event( `BUTTON_POST` ) ).
client->view_display( view->stringify( ) ).
CASE client->get( )-event.
WHEN `BUTTON_POST`.
client->message_box_display( `The button was pressed` ).
ENDCASE.
ENDMETHOD.
```
If the backend needs more details about the event, use the `t_arg` parameter to add extra info. Three prefixes are available:
- **`$source`** — the UI5 control that fired the event (e.g., `${$source>/text}` returns the button text)
- **`$parameters`** — the event parameters defined by the UI5 control (e.g., `${$parameters>/id}` returns the element ID)
- **`$event`** — the UI5 event object itself (e.g., `$event>sId` returns the event type like `press`). Note: unlike the other two prefixes, `$event` is written without the `${...}` wrapper and without a leading `/` — see the [Event](#event) section below.
For details, see the [UI5 docs on event handler arguments](https://openui5.hana.ondemand.com/#/topic/b0fb4de7364f4bcbb053a99aa645affe) and sample `Z2UI5_CL_SMP_APP_167`.
## Source
Send properties of the event source control to the backend. The syntax `${$source>/text}` reads the `text` property of the UI5 control that fired the event — here, the button itself, returning the button's label (`post`):
```abap
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->tag( `Button`
)->a( n = `text` v = `post`
)->a( n = `press` v = client->_event(
val = `BUTTON_POST`
" reads the button text → result: "post"
t_arg = VALUE #( ( `${$source>/text}` ) ) ) ).
client->view_display( view->stringify( ) ).
CASE client->get( )-event.
WHEN `BUTTON_POST`.
client->message_box_display( |The button text is { client->get_event_arg( ) }| ).
ENDCASE.
ENDMETHOD.
```
## Parameters
Read parameters of the event. The syntax `${$parameters>/id}` reads the `id` parameter out of the event's parameter map — UI5 builds a qualified ID like `mainView--button_id`:
```abap
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->tag( `Button`
)->a( n = `id` v = `button_id`
)->a( n = `text` v = `post`
)->a( n = `press` v = client->_event(
val = `BUTTON_POST`
" reads the event parameter 'id' → result: "mainView--button_id"
t_arg = VALUE #( ( `${$parameters>/id}` ) ) ) ).
client->view_display( view->stringify( ) ).
CASE client->get( )-event.
WHEN `BUTTON_POST`.
client->message_box_display( |The button id is { client->get_event_arg( ) }| ).
ENDCASE.
ENDMETHOD.
```
## Event
Read specific properties of the event object. The syntax `$event>sId` reads the `sId` attribute of the UI5 event — here it returns the event type name (`press`). Note: there's no `${...}` wrapper because `$event` directly references the event object:
```abap
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->tag( `Button`
)->a( n = `text` v = `post`
)->a( n = `press` v = client->_event(
val = `BUTTON_POST`
" reads an event-object attribute → result: "press"
t_arg = VALUE #( ( `$event>sId` ) ) ) ).
client->view_display( view->stringify( ) ).
CASE client->get( )-event.
WHEN `BUTTON_POST`.
client->message_box_display( |The event id is { client->get_event_arg( ) }| ).
ENDCASE.
ENDMETHOD.
```
::: warning
You can read any object attribute, but use only public and released attributes to avoid compatibility issues with future UI5 versions.
:::
## Model Properties
Read model properties bound to the event:
```abap
CLASS z2ui5_cl_app_hello_world DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
DATA name TYPE string.
ENDCLASS.
CLASS z2ui5_cl_app_hello_world IMPLEMENTATION.
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->tag( `Input`
)->a( n = `value` v = client->_bind( name )
)->tag( `Button`
)->a( n = `text` v = `post`
)->a( n = `press` v = client->_event(
val = `BUTTON_POST`
t_arg = VALUE #( ( `$` && client->_bind( name ) ) ) ) ).
client->view_display( view->stringify( ) ).
CASE client->get( )-event.
WHEN `BUTTON_POST`.
client->message_box_display( |The name is { client->get_event_arg( ) }| ).
ENDCASE.
ENDMETHOD.
ENDCLASS.
```
::: tip
This is just a demo. Reading `name` directly would be easier — the framework updates it automatically.
:::
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Extra Arguments with t_arg | [`Z2UI5_CL_SMP_APP_167`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_167.clas.abap) |
| Control Objects in t_arg (FacetFilter) | [`Z2UI5_CL_SMP_APP_197`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_197.clas.abap) |
---
# Frontend
If you don't want to handle the event in the backend, fire actions directly on the frontend. The difference between the two event styles:
- **`client->_event( )`** — causes a backend roundtrip; the event runs in the `main` method
- **`client->follow_up_action( )`** — runs an action in the browser; no backend call
`follow_up_action( )` is **one method in two positions**, and the position is
what decides when it happens:
- **in a view attribute** (`press = client->follow_up_action( … )`) its result
is consumed, and the action is wired to the control — the browser runs it
when the user presses, with no roundtrip at all;
- **as a statement** in your `main` method it is scheduled, and the browser
runs it after the response arrives — i.e. after your backend work.
::: tip `_event_client( )` is the older name for the first form
It is obsolete: in the view-attribute position the two produce the identical
wire, byte for byte. It stays in the interface so existing apps keep compiling
— rename the calls at your leisure.
:::
The following frontend events are available:
```abap
CONSTANTS:
BEGIN OF cs_event,
"Framework
popup_close TYPE string VALUE `POPUP_CLOSE`,
popover_close TYPE string VALUE `POPOVER_CLOSE`,
set_size_limit TYPE string VALUE `SET_SIZE_LIMIT`,
set_odata_model TYPE string VALUE `SET_ODATA_MODEL`,
cross_app_nav_to_ext TYPE string VALUE `CROSS_APP_NAV_TO_EXT`,
cross_app_nav_to_prev_app TYPE string VALUE `CROSS_APP_NAV_TO_PREV_APP`,
"Actions
clipboard_copy TYPE string VALUE `CLIPBOARD_COPY`,
clipboard_app_state TYPE string VALUE `CLIPBOARD_APP_STATE`,
set_title TYPE string VALUE `SET_TITLE`,
set_title_launchpad TYPE string VALUE `SET_TITLE_LAUNCHPAD`,
set_focus TYPE string VALUE `SET_FOCUS`,
scroll_to TYPE string VALUE `SCROLL_TO`,
scroll_into_view TYPE string VALUE `SCROLL_INTO_VIEW`,
start_timer TYPE string VALUE `START_TIMER`,
keyboard_set_mode TYPE string VALUE `KEYBOARD_SET_MODE`,
keyboard_shortcut TYPE string VALUE `KEYBOARD_SHORTCUT`,
open_new_tab TYPE string VALUE `OPEN_NEW_TAB`,
location_reload TYPE string VALUE `LOCATION_RELOAD`,
nav_to_route TYPE string VALUE `NAV_TO_ROUTE`,
system_logout TYPE string VALUE `SYSTEM_LOGOUT`,
download_b64_file TYPE string VALUE `DOWNLOAD_B64_FILE`,
urlhelper TYPE string VALUE `URLHELPER`,
history_back TYPE string VALUE `HISTORY_BACK`,
store_data TYPE string VALUE `STORE_DATA`,
play_audio TYPE string VALUE `PLAY_AUDIO`,
wizard_set_next_step TYPE string VALUE `WIZARD_SET_NEXT_STEP`,
"Control calls (positional t_arg)
control_by_id TYPE string VALUE `CONTROL_BY_ID`,
control_global TYPE string VALUE `CONTROL_GLOBAL`,
binding_call TYPE string VALUE `BINDING_CALL`,
bind_element TYPE string VALUE `BIND_ELEMENT`,
"Smart controls (sap.ui.comp)
smart_variant_init TYPE string VALUE `SMART_VARIANT_INIT`,
filter_bar_variant_init TYPE string VALUE `FILTER_BAR_VARIANT_INIT`,
END OF cs_event.
```
Some of these events have their own pages: [`keyboard_shortcut`](/cookbook/browser_interaction/keyboard_shortcuts) binds key combinations to backend events, [`nav_to_route`](/cookbook/event_navigation/routing) navigates by hash route, and [`smart_variant_init` / `filter_bar_variant_init`](/cookbook/expert_more/smart_controls) wire variant management for smart controls.
For example, to open a new tab directly from a button press (no backend involved):
```abap
METHOD z2ui5_if_app~main.
DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `View` ns = `mvc`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
)->ele( `Page`
)->tag( `Button`
)->a( n = `text` v = `open new tab`
)->a( n = `press` v = client->follow_up_action(
val = client->cs_event-open_new_tab
t_arg = VALUE #( ( `https://github.com/abap2UI5` ) ) ) ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
```
## Calling control methods on the frontend
The control-call constants — `control_by_id`, `control_global`, `binding_call` and `bind_element` — are frontend events too, but instead of a fixed built-in action they operate on a control, a global object, a binding or a whole view slot. Their arguments are **positional**: an empty argument between two filled ones keeps its slot as `` `` ``.
| Event | `t_arg` (positional) |
| ---------------- | ------------------------------------------------------------------------------------ |
| `control_by_id` | `id`, `method`, `params…` — call a method on a control resolved by id |
| `control_global` | `object`, `method`, `params…` — `MESSAGE_TOAST`, `MESSAGE_BOX`, `BUSY_INDICATOR`, `THEMING` |
| `binding_call` | `id`, `aggregation`, `method`, `params…` — e.g. `filter` (path, operator, value1, value2) or `sort` (path, descending, group) on the aggregation's binding |
| `bind_element` | `index`, `_bind( table )` — element-bind a whole view slot to a table row, see below |
For `control_by_id`, any public control method is callable as long as it is not on the framework's **denylist**: methods that would break abap2UI5's own invariants (destroying views, re-rendering, detaching the framework's handlers, …) are blocked, ordinary setters and toggles (`setVisible`, `toggleBy`, `enablePostButton`, …) simply work. A small set of methods is additionally special-cased for typed arguments. `control_global` and `binding_call` remain strict whitelists — only the listed global objects and the binding methods `filter` / `sort` are callable.
```abap
" toggle a MessagePopover open, anchored to the pressing button, no roundtrip
press = client->follow_up_action(
val = client->cs_event-control_by_id
t_arg = VALUE #( ( `msgPopover` ) ( `toggleBy` ) ( `${$source>/id}` ) ) )
```
The same events also work as a **statement** in your `main` method, with the identical `t_arg` — then the browser runs them after the response arrives.
### Element-binding a view slot: `bind_element`
`bind_element` binds a whole view slot (popup, popover, main, …) to one row of a bound table — the abap2UI5 equivalent of `oControl.bindElement( )`. All *relative* bindings in that slot (`{NAME}`, `{CATEGORY}`, nested aggregations) then resolve against the selected row, so a detail popup needs no data copied into event arguments:
```abap
" element-bind the popup slot to row of t_product
client->follow_up_action(
val = client->cs_event-bind_element
view = client->cs_view-popup
t_arg = VALUE #( ( index ) ( client->_bind( t_product ) ) ) ).
```
The `view` parameter selects the slot to bind; `t_arg` carries the row index and the table's binding path. See demo app 470 in the [samples repository](https://github.com/abap2UI5/samples) for a complete example.
### The `view` parameter
For `control_by_id`, the control is looked up by id. `follow_up_action( )` (and the obsolete `_event_client( )`) takes a separate `view` parameter (default `cs_view-main`) that scopes this lookup:
- omit it (or pass `cs_view-main`) — the id is resolved across all open views;
- pass `cs_view-popup` / `cs_view-popover` / `cs_view-nested` / … — the lookup is scoped to a control hosted in that view (e.g. a control living inside a popup).
```abap
" call a method on a control that lives inside the popup view
press = client->follow_up_action(
val = client->cs_event-control_by_id
view = client->cs_view-popup
t_arg = VALUE #( ( `NavCon` ) ( `to` ) ( `${$parameters>/selectedKey}` ) ) )
```
::: warning Migrated from a positional view slot
The view used to be the second entry of `t_arg` (`id`, `view`, `method`, …). It is now the dedicated `view` importing parameter, and the framework injects it into the argument list itself. Older examples that still pass `` `MAIN` `` as the second `t_arg` element **no longer work** — the extra entry shifts every argument by one and the call fails on the frontend. Drop the positional view entry and use the `view` parameter instead.
:::
`control_global` ignores `view` (it is not resolved by id), and `binding_call` always resolves its id across all open views. For `bind_element`, `view` selects the slot to element-bind (see above).
## Working Samples
Complete apps from the [sample catalogue](https://github.com/abap2UI5/samples/blob/main/SAMPLES.md)
that use what this page describes. Each is a single class — pull the repository with
[abapGit](https://abapgit.org) and start it with `?app_start=`.
| Sample | Class |
|---|---|
| Link with preventDefault (A) | [`Z2UI5_CL_SMP_APP_472`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_472.clas.abap) |
| Element Binding to the Selected Row (A) | [`Z2UI5_CL_SMP_APP_470`](https://github.com/abap2UI5/samples/blob/main/src/01/z2ui5_cl_smp_app_470.clas.abap) |
---
# Action (Obsolete)
::: warning Do Not Use Anymore
`client->action->gen( )` is obsolete. Use
[`client->follow_up_action( )`](/cookbook/expert_more/follow_up_action) instead —
it takes the same frontend event and arguments and now covers this case
directly.
:::
## What Changed
`action->gen( )` used to schedule a frontend event by name with typed arguments:
```abap
" obsolete — do not use
client->action->gen(
val = client->cs_event-open_new_tab
t_arg = VALUE #( ( `https://github.com/abap2UI5` ) ) ).
```
`follow_up_action( )` now accepts the same `val` and `t_arg`, so the migration is
a plain rename:
```abap
client->follow_up_action(
val = client->cs_event-open_new_tab
t_arg = VALUE #( ( `https://github.com/abap2UI5` ) ) ).
```
`val` is one of the frontend events from `z2ui5_if_client=>cs_event` (see
[Frontend](./frontend.md)); `t_arg` carries the arguments the event expects. See
[Follow-up Action](/cookbook/expert_more/follow_up_action) for the full details.
---
# Follow-up Action
Sometimes, once your backend event handler has finished, you want to trigger an
action that runs on the frontend — set the browser title, move focus, scroll,
copy to the clipboard, and so on. `client->follow_up_action( )` schedules such a
frontend action; it runs in the browser right after the response arrives.
It is also the method to reach for when an older app calls `_event_client( )` or
wraps a browser interaction in a custom control — both are obsolete, see
[Deprecations](/resources/deprecations).
## Frontend event + arguments
The usual way: pass a built-in frontend event as the first parameter `val` and
its arguments in `t_arg`. The framework assembles the frontend call for you.
```abap
METHOD z2ui5_if_app~main.
client->follow_up_action(
val = client->cs_event-set_title
t_arg = VALUE #( ( `Invoice 4711` ) ) ).
ENDMETHOD.
```
`val` is one of the frontend events from `z2ui5_if_client=>cs_event` (see
[Frontend](/cookbook/event_navigation/frontend)) — for example `set_title`,
`set_focus`, `scroll_to`, `start_timer`, `download_b64_file`, `play_audio`.
`t_arg` carries the arguments the event expects; events without arguments need
no `t_arg`:
```abap
client->follow_up_action( client->cs_event-popup_close ).
```
See the dedicated cookbook pages under
[Browser Interaction](/cookbook/browser_interaction/title) and
[Device Capabilities](/cookbook/device_capabilities/upload_download) for the
argument list of each event.
::: tip Replacing a custom control
Earlier versions of abap2UI5 needed an invisible custom UI5 control for each of
these interactions — title, focus, scrolling, timer, soft keyboard. Every one of
them is a built-in event now. The full old-control-to-event list is in
[Deprecations](/resources/deprecations#invisible-custom-controls).
:::
## Calling a control method
The control calls — `cs_event-control_by_id`, `control_global`, `binding_call`
and `bind_element` — are frontend events too, so `follow_up_action( )` can invoke a
method on a control once the backend response arrives. For `control_by_id`, any
public control method works unless it is on the framework's denylist;
`control_global` and `binding_call` are strict whitelists. Their `t_arg` is
positional (see [Frontend → Calling control methods](/cookbook/event_navigation/frontend#calling-control-methods-on-the-frontend)):
```abap
" after backend processing, advance a wizard step
client->follow_up_action(
val = client->cs_event-control_by_id
t_arg = VALUE #( ( `wiz` ) ( `setNextStep` ) ( `STEP2` ) ) ).
```
For `control_by_id`, the control is resolved by id. A separate `view` parameter
(default `cs_view-main`, which resolves the id across all open views) scopes the
lookup to a single view — pass `cs_view-popup` / `cs_view-popover` / … for a
control hosted in a popup or popover:
```abap
client->follow_up_action(
val = client->cs_event-control_by_id
view = client->cs_view-popup
t_arg = VALUE #( ( `NavCon` ) ( `to` ) ( `detail` ) ) ).
```
See demo apps 470 (element binding) and 471 (keyboard shortcuts) in the [samples repository](https://github.com/abap2UI5/samples) for complete `follow_up_action` examples.
## Raw JavaScript
The second way to call `follow_up_action( )`: pass a raw JavaScript expression as
`val` (without `t_arg`). It runs as-is in the browser.
```abap
client->follow_up_action( `myFunction()` ).
```
`follow_up_action( )` decides which way applies from the content of `val`: a
plain event name (only `A-Z`, `a-z`, `0-9`, `_`) becomes a frontend event call,
anything containing JavaScript syntax runs verbatim.
::: warning Not Recommended
This is still available, but its use is **strongly discouraged**. Injecting
arbitrary JavaScript from the backend into the frontend introduces serious
security risks. Only use it if you fully understand the consequences and have no
alternative.
:::
::: tip `cs_event-z2ui5` is the older form of the same thing
The constant calls a function you registered as a `z2ui5.*` global
(``follow_up_action( val = cs_event-z2ui5 t_arg = VALUE #( ( `myFunction` ) ) )``).
It still works and is still dispatched, but it sits with the obsolete constants
in `z2ui5_if_client`: passing the expression directly, as above, is the same
call without the indirection. If the global is missing the frontend logs
`Z2UI5: 'z2ui5.myFunction' is not a function` rather than failing silently.
:::
### Why It Is a Security Risk
Custom JS works by sending a JavaScript string from the ABAP backend to the frontend, where it is injected into the DOM as an HTML `|
)->ele( `Page`
)->tag( `Button`
)->a( n = `text` v = `call custom JS`
)->a( n = `press` v = client->_event( `CUSTOM_JS` ) ).
client->view_display( view->stringify( ) ).
ENDIF.
IF client->get( )-event = `CUSTOM_JS`.
client->follow_up_action( `myFunction()` ).
ENDIF.
ENDMETHOD.
```
::: danger Never Inject Untrusted Input
If you must use this, ensure the JavaScript content is **entirely static and hardcoded**. Never concatenate user input, database values, translatable texts, or any other dynamic data into the script string — doing so turns the feature into a direct XSS vulnerability.
:::
### Embedding JavaScript Directly in an XML View
::: warning Also Not Recommended
The same security considerations apply: any `
```
## Add or Override Attributes
To add an attribute — or override one of the defaults — append a row to `cs_config-t_add_config`. Each row contributes one `name='value'` pair to the script tag:
```abap
METHOD z2ui5_if_exit~set_config_http_get.
cs_config-t_add_config = VALUE #(
( n = `data-sap-ui-libs` v = `sap.m,sap.ui.table` )
( n = `data-sap-ui-language` v = `en` )
( n = `data-sap-ui-frameOptions` v = `allow` )
( n = `data-sap-ui-preload` v = `async` ) ).
ENDMETHOD.
```
## Useful Attributes
| Attribute | Purpose |
|---------------------------------|---------|
| `data-sap-ui-libs` | Comma-separated list of UI5 libraries to preload (e.g. `sap.m,sap.ui.table`). Trade load time against startup speed. |
| `data-sap-ui-language` | UI5 locale; overrides the browser language. See [Language](/configuration/setup/logon_language). |
| `data-sap-ui-compatVersion` | Compatibility version, controls UI5 behaviour for deprecated APIs. abap2UI5 defaults to `edge`. |
| `data-sap-ui-async` | Asynchronous module loading. Default `true` — only change for legacy reasons. |
| `data-sap-ui-preload` | Module preloading strategy: `async`, `sync` or empty (off). |
| `data-sap-ui-frameOptions` | Clickjacking protection: `trusted`, `allow`, `deny`. Default `trusted`. |
| `data-sap-ui-allowlistService` | Endpoint for the URL allowlist service. |
| `data-sap-ui-bindingSyntax` | Binding syntax: `complex` (default) or `simple`. abap2UI5 expressions require `complex`. |
| `data-sap-ui-resourceroots` | Additional resource roots for custom libraries. |
| `data-sap-ui-xx-componentpreload` | Component-preload strategy for very large apps. |
Attributes set by abap2UI5 by default can be overridden — a row with the same name wins over the framework default.
## See Also
- Official UI5 [configuration options and URL parameters reference](https://sapui5.hana.ondemand.com/#/topic/91f2d03b6f4d1014b6dd926db0e91070) — the authoritative list of every supported attribute.
- [Bootstrapping](/configuration/setup/ui5_bootstrapping) — how to change the bootstrap script source.
---
# Style / CSS
UI5 supports app-specific CSS in addition to the theme. abap2UI5 injects whatever string you assign to `cs_config-styles_css` directly into a `