Skip to content

Commit 9e59e82

Browse files
committed
Add Full Example tutorial covering selection, table, popup, and post
1 parent ebe4904 commit 9e59e82

2 files changed

Lines changed: 271 additions & 0 deletions

File tree

docs/.vitepress/config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export default defineConfig({
108108
{ text: "Quickstart", link: "/get_started/quickstart" },
109109
{ text: "Use Cases", link: "/get_started/use_cases" },
110110
{ text: "Hello World", link: "/get_started/hello_world" },
111+
{ text: "Full Example", link: "/get_started/full_example" },
111112
{ text: `What's Next?`, link: "/get_started/next" },
112113
],
113114
},

docs/get_started/full_example.md

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
outline: [2, 4]
3+
---
4+
# Full Example
5+
6+
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.
7+
8+
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.
9+
10+
## What You Will Build
11+
12+
1. **Selection screen** — date range and a customer filter.
13+
2. **Read data** — fetch matching orders into an internal table on button press.
14+
3. **Result table** — show the orders, with one row editable via popup.
15+
4. **Popup** — open a dialog that lets the user change the delivery date.
16+
5. **Post** — confirm in the popup, write the change back, refresh the table.
17+
18+
## The Class Definition
19+
20+
The controller holds all state in **public** attributes so binding works (see [Lifecycle Pitfalls](/cookbook/event_navigation/life_cycle#lifecycle-pitfalls)). The handler methods are protected — they are called from `main` and never bound.
21+
22+
```abap
23+
CLASS z2ui5_cl_demo_full_example DEFINITION PUBLIC.
24+
25+
PUBLIC SECTION.
26+
INTERFACES z2ui5_if_app.
27+
28+
TYPES:
29+
BEGIN OF ty_row,
30+
order_id TYPE c LENGTH 10,
31+
customer TYPE c LENGTH 10,
32+
material TYPE c LENGTH 18,
33+
quantity TYPE i,
34+
delivery_date TYPE d,
35+
END OF ty_row.
36+
37+
" Selection screen
38+
DATA date_from TYPE d.
39+
DATA date_to TYPE d.
40+
DATA customer TYPE c LENGTH 10.
41+
42+
" Result set
43+
DATA orders TYPE STANDARD TABLE OF ty_row WITH EMPTY KEY.
44+
45+
" Edit buffer (used by the popup)
46+
DATA edit_row TYPE ty_row.
47+
DATA edit_index TYPE i.
48+
49+
PROTECTED SECTION.
50+
DATA client TYPE REF TO z2ui5_if_client.
51+
52+
METHODS render_main.
53+
METHODS open_edit_popup.
54+
METHODS read_data.
55+
METHODS save_changes.
56+
57+
ENDCLASS.
58+
```
59+
60+
## The `main` Dispatcher
61+
62+
`main` is the only entry point. It uses `CASE abap_true` to dispatch between init, events, and navigation returns — the standard [life-cycle pattern](/cookbook/event_navigation/life_cycle):
63+
64+
```abap
65+
CLASS z2ui5_cl_demo_full_example IMPLEMENTATION.
66+
67+
METHOD z2ui5_if_app~main.
68+
69+
me->client = client.
70+
71+
CASE abap_true.
72+
73+
WHEN client->check_on_init( ).
74+
" preset the selection screen with a useful default
75+
date_from = sy-datum - 30.
76+
date_to = sy-datum.
77+
render_main( ).
78+
79+
WHEN client->check_on_event( `SEARCH` ).
80+
read_data( ).
81+
render_main( ).
82+
83+
WHEN client->check_on_event( `EDIT` ).
84+
open_edit_popup( ).
85+
86+
WHEN client->check_on_event( `SAVE` ).
87+
save_changes( ).
88+
client->popup_destroy( ).
89+
90+
WHEN client->check_on_event( `CANCEL` ).
91+
client->popup_destroy( ).
92+
93+
ENDCASE.
94+
95+
ENDMETHOD.
96+
```
97+
98+
A few things worth noting:
99+
100+
- The view is rebuilt and re-sent in `render_main( )` on the events that change the visible structure (initial render, after the search). On `EDIT`, `SAVE`, and `CANCEL` we only open or close a popup — the underlying view stays.
101+
- Public attributes (`date_from`, `customer`, `orders`, …) carry the state across roundtrips. The framework serializes them between requests automatically.
102+
- Two-way-bound inputs (`_bind_edit`) write user changes back into the public attributes *before* the event handler runs.
103+
104+
## The Selection Screen and the Table
105+
106+
`render_main` paints the selection panel and, if `orders` already has rows, the result table below it. The two parts live in the same `Page`:
107+
108+
```abap
109+
METHOD render_main.
110+
111+
DATA(view) = z2ui5_cl_xml_view=>factory( ).
112+
DATA(page) = view->shell( )->page( title = `Sales Orders` ).
113+
114+
" --- Selection screen -------------------------------------------------
115+
DATA(form) = page->simple_form( title = `Selection` editable = abap_true
116+
)->content( `f` ).
117+
form->label( `Date from` )->date_picker( client->_bind_edit( date_from ) ).
118+
form->label( `Date to` )->date_picker( client->_bind_edit( date_to ) ).
119+
form->label( `Customer` )->input( client->_bind_edit( customer ) ).
120+
form->button( text = `Search`
121+
type = `Emphasized`
122+
press = client->_event( `SEARCH` ) ).
123+
124+
" --- Result table -----------------------------------------------------
125+
IF orders IS NOT INITIAL.
126+
DATA(tab) = page->table(
127+
items = client->_bind( orders )
128+
headerText = `Results` ).
129+
tab->columns(
130+
)->column( )->text( `Order` )->get_parent(
131+
)->column( )->text( `Customer` )->get_parent(
132+
)->column( )->text( `Material` )->get_parent(
133+
)->column( )->text( `Quantity` )->get_parent(
134+
)->column( )->text( `Delivery Date` )->get_parent(
135+
)->column( )->text( `` ).
136+
DATA(cells) = tab->items( )->column_list_item( )->cells( ).
137+
cells->text( `{ORDER_ID}` ).
138+
cells->text( `{CUSTOMER}` ).
139+
cells->text( `{MATERIAL}` ).
140+
cells->text( `{QUANTITY}` ).
141+
cells->text( `{DELIVERY_DATE}` ).
142+
cells->button( text = `Edit`
143+
press = client->_event(
144+
val = `EDIT`
145+
t_arg = VALUE #( ( `${ORDER_ID}` ) ) ) ).
146+
ENDIF.
147+
148+
client->view_display( view->stringify( ) ).
149+
150+
ENDMETHOD.
151+
```
152+
153+
The `Edit` button passes the bound row's `ORDER_ID` as an event argument. On the next roundtrip, the handler reads it via `client->get_event_arg( 1 )` to know which row was clicked.
154+
155+
## Reading Data
156+
157+
`read_data` is plain ABAP — replace the dummy fill with a real `SELECT` against your tables. The framework does not get in the way here:
158+
159+
```abap
160+
METHOD read_data.
161+
162+
CLEAR orders.
163+
164+
" Replace with your own SELECT, e.g.:
165+
"
166+
" SELECT vbeln AS order_id, kunnr AS customer,
167+
" matnr AS material, kwmeng AS quantity, edatu AS delivery_date
168+
" FROM vbap
169+
" INNER JOIN vbak ON vbap~vbeln = vbak~vbeln
170+
" WHERE vbak~erdat BETWEEN @date_from AND @date_to
171+
" AND ( @customer IS INITIAL OR vbak~kunnr = @customer )
172+
" INTO TABLE @orders.
173+
174+
" Dummy data so the tutorial runs without a database table:
175+
orders = VALUE #(
176+
( order_id = `0000010001` customer = `1000` material = `M-01`
177+
quantity = 5 delivery_date = sy-datum + 7 )
178+
( order_id = `0000010002` customer = `1000` material = `M-02`
179+
quantity = 12 delivery_date = sy-datum + 14 )
180+
( order_id = `0000010003` customer = `1010` material = `M-03`
181+
quantity = 1 delivery_date = sy-datum + 3 ) ).
182+
183+
ENDMETHOD.
184+
```
185+
186+
## The Edit Popup
187+
188+
When the user presses `Edit`, the handler picks the row out of `orders`, stores it in `edit_row`, and renders a popup with two-way-bound fields. Because `edit_row` is a public attribute, anything the user types ends up there before `SAVE` runs:
189+
190+
```abap
191+
METHOD open_edit_popup.
192+
193+
DATA(order_id) = client->get_event_arg( 1 ).
194+
READ TABLE orders WITH KEY order_id = order_id
195+
ASSIGNING FIELD-SYMBOL(<row>).
196+
IF sy-subrc <> 0.
197+
client->message_box_display(
198+
text = |Order { order_id } not found| type = `error` ).
199+
RETURN.
200+
ENDIF.
201+
202+
edit_row = <row>.
203+
edit_index = sy-tabix.
204+
205+
DATA(popup) = z2ui5_cl_xml_view=>factory_popup( ).
206+
DATA(dlg) = popup->dialog(
207+
title = |Edit Order { edit_row-order_id }|
208+
contentWidth = `400px` ).
209+
210+
DATA(form) = dlg->simple_form( editable = abap_true )->content( `f` ).
211+
form->label( `Customer` )->text( client->_bind( edit_row-customer ) ).
212+
form->label( `Material` )->text( client->_bind( edit_row-material ) ).
213+
form->label( `Quantity` )->input( client->_bind_edit( edit_row-quantity ) ).
214+
form->label( `Delivery Date` )->date_picker( client->_bind_edit( edit_row-delivery_date ) ).
215+
216+
dlg->buttons(
217+
)->button( text = `Save`
218+
type = `Emphasized`
219+
press = client->_event( `SAVE` )
220+
)->button( text = `Cancel`
221+
press = client->_event( `CANCEL` ) ).
222+
223+
client->popup_display( popup->stringify( ) ).
224+
225+
ENDMETHOD.
226+
```
227+
228+
Note that **the same** `client->view_display` from `render_main` is still active behind the popup. UI5 dims the page and overlays the dialog — there is no second view to manage.
229+
230+
## Posting the Changes
231+
232+
`save_changes` writes `edit_row` back into the `orders` table at the position captured when the popup opened, then runs whatever persistence you need. In a real app this is the place to call `UPDATE vbap …` or trigger a BAPI:
233+
234+
```abap
235+
METHOD save_changes.
236+
237+
READ TABLE orders INDEX edit_index ASSIGNING FIELD-SYMBOL(<row>).
238+
IF sy-subrc <> 0.
239+
client->message_box_display(
240+
text = `Row no longer exists — please search again` type = `error` ).
241+
RETURN.
242+
ENDIF.
243+
244+
<row> = edit_row.
245+
246+
" Persistence — replace with your real update / BAPI / EML call:
247+
"
248+
" UPDATE vbap
249+
" SET kwmeng = @edit_row-quantity, edatu = @edit_row-delivery_date
250+
" WHERE vbeln = @edit_row-order_id.
251+
" COMMIT WORK.
252+
253+
client->message_toast_display(
254+
|Order { edit_row-order_id } updated| ).
255+
256+
ENDMETHOD.
257+
258+
ENDCLASS.
259+
```
260+
261+
When `save_changes` returns, `main` calls `client->popup_destroy( )` to close the dialog. The table on the main view updates automatically because UI5 keeps reading from the same model path — the framework re-serializes `orders` on every response, so the edited row appears with its new values without a second `view_display( )` call.
262+
263+
## What to Take Away
264+
265+
- One controller class, one `main` method, all state in public attributes — that is the whole app.
266+
- The view tree is rebuilt only when the structure changes. Edits, saves, and popup open/close do not need a fresh `view_display( )`.
267+
- Popups are just a different factory (`factory_popup`) on the same `z2ui5_cl_xml_view`, displayed via `popup_display` / `popup_destroy` while the main view stays in place.
268+
- 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.
269+
270+
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.

0 commit comments

Comments
 (0)