|
| 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