Skip to content

abap2UI5/sample-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

abap2UI5 — Tutorial

A step-by-step tutorial to learn abap2UI5 — build SAP UI5 apps purely in ABAP, no JavaScript, OData or RAP required.

The tutorial consists of seven lessons. Each lesson is a small, self-contained app that builds on the concepts of the previous one — from a simple input form to a table application with create, delete and confirmation popups backed by a database table.

Lesson Class Branch Topic
01 z2ui5_cl_tutorial_01 lesson-01 Selection screen with data input
02 z2ui5_cl_tutorial_02 lesson-02 Internal table displayed with sap.m.Table
03 z2ui5_cl_tutorial_03 lesson-03 Database table read into an internal table
04 z2ui5_cl_tutorial_04 lesson-04 Filtering and sorting the table
05 z2ui5_cl_tutorial_05 lesson-05 Toolbar, selecting and deleting an entry
06 z2ui5_cl_tutorial_06 lesson-06 Confirmation popup before deleting
07 z2ui5_cl_tutorial_07 lesson-07 Creating a new entry with a popup form

z2ui5_cl_tutorial_00 is an overview app that lists all lessons and navigates to them. It ships with branch lesson-07 (and main), because it references all lesson classes.

Installation

Prerequisite

Install the abap2UI5 framework with abapGit and set up the HTTP service as described in the documentation.

Option A — Step by step, one branch per lesson (recommended)

Every lesson has its own branch. Branch lesson-NN contains the repository up to and including lesson NN — so you can pull the lessons into your system one at a time, always working with exactly the objects you have already learned about:

Branch Contains New objects
lesson-01 Lesson 01 z2ui5_cl_tutorial_01
lesson-02 Lessons 01–02 z2ui5_cl_tutorial_02
lesson-03 Lessons 01–03 z2ui5_cl_tutorial_03, table Z2UI5_T_TUTORIAL
lesson-04 Lessons 01–04 z2ui5_cl_tutorial_04
lesson-05 Lessons 01–05 z2ui5_cl_tutorial_05
lesson-06 Lessons 01–06 z2ui5_cl_tutorial_06
lesson-07 Lessons 01–07 z2ui5_cl_tutorial_07, overview app z2ui5_cl_tutorial_00
  1. In abapGit choose New Online, enter this repository's URL, select branch lesson-01 and a separate package, then pull.
  2. Activate the objects and work through lesson 01 (see below).
  3. When you are ready for the next lesson: in abapGit choose Switch branch, select lesson-02 and pull again — only the new objects of lesson 02 are added, everything you already pulled stays untouched.
  4. Repeat for each lesson. From branch lesson-03 onwards, remember to activate the new database table Z2UI5_T_TUTORIAL as well.

Option B — Everything at once

Install the main branch with abapGit into a separate package and activate all objects — including the database table Z2UI5_T_TUTORIAL used from lesson 03 onwards.

Starting an app

Start an app by appending ?app_start=z2ui5_cl_tutorial_01 (or any other lesson class — z2ui5_cl_tutorial_00 for the overview) to the URL of your abap2UI5 HTTP service.

How an abap2UI5 App Works

Every app is a plain ABAP class that implements z2ui5_if_app with a single method main. The framework calls main on every roundtrip (HTTP POST from the browser). Inside main you check the lifecycle situation and react to it:

IF client->check_on_init( ).
  " first call: set up data, display the view
ELSEIF client->check_on_event( ).
  " the user triggered an event (button press, search, ...)
ENDIF.

Views are XML strings built with the fluent builder z2ui5_cl_xml_view and sent to the browser with client->view_display( ). Data binding connects ABAP variables to UI5 controls:

  • client->_bind( var ) — one-way binding (display only)
  • client->_bind_edit( var ) — two-way binding (user input is written back to the ABAP variable on the next roundtrip)

With that, you are ready for lesson 01.

Lesson 01 — Selection Screen with Data Input

Class: z2ui5_cl_tutorial_01Branch: lesson-01

The classic starting point: a form with two input fields and a button — the abap2UI5 equivalent of a SELECTION-SCREEN with PARAMETERS.

What you build

Everything starts with a plain class implementing z2ui5_if_app. The attributes you want to bind to the UI must be public, because the framework reads and writes them via RTTI:

CLASS z2ui5_cl_tutorial_01 DEFINITION PUBLIC.

  PUBLIC SECTION.
    INTERFACES z2ui5_if_app.

    DATA product  TYPE string.
    DATA quantity TYPE string.

On the first call (check_on_init), defaults are set and the view is built — a page containing a simple_form with labels, inputs and a button:

IF client->check_on_init( ).

  product  = `Notebook Basic 15`.
  quantity = `10`.

  DATA(view) = z2ui5_cl_xml_view=>factory( ).
  view->shell(
      )->page( title = `abap2UI5 Tutorial - 01 Selection Screen`
          )->simple_form(
              title    = `Order Entry`
              editable = abap_true
              )->content( `form`
              )->label( `Product`
              )->input( client->_bind_edit( product )
              )->label( `Quantity`
              )->input( client->_bind_edit( quantity )
              )->button(
                  text  = `Post`
                  press = client->_event( `POST` ) ).
  client->view_display( view->stringify( ) ).

Two things carry the whole framework idea:

  • client->_bind_edit( product )two-way binding. When the user changes the value in the browser, the framework writes it back into the ABAP attribute automatically on the next roundtrip. No event handling needed for the data transport.
  • client->_event( 'POST' ) — registers a server event for the button. When pressed, main is called again and you handle it in the same method:
ELSEIF client->check_on_event( `POST` ).
  client->message_toast_display( |{ quantity } x { product } - posted to the server| ).
ENDIF.

The toast proves that the user input really arrived in the ABAP backend — without you writing a single line of transport code.

Key takeaway: bound attributes must be PUBLIC so the framework can read and write them via RTTI. State survives between roundtrips because the framework serializes and restores the whole app instance (draft persistence).

Lesson 02 — Internal Table with sap.m.Table

Class: z2ui5_cl_tutorial_02Branch: lesson-02

The ABAP developer's bread and butter: fill an internal table and display it — this time not with cl_salv_table, but with sap.m.Table.

What's new compared to lesson 01

1. A structure type and a public table attribute. Just like the scalar attributes in lesson 01, the table must be public to be bindable:

PUBLIC SECTION.
  INTERFACES z2ui5_if_app.

  TYPES:
    BEGIN OF ty_s_product,
      product_id TYPE string,
      name       TYPE string,
      supplier   TYPE string,
      quantity   TYPE i,
    END OF ty_s_product.
  DATA t_products TYPE STANDARD TABLE OF ty_s_product WITH EMPTY KEY.

2. The canonical structure for larger apps. The app outgrows a single main method, so main becomes a pure dispatcher and the logic moves into dedicated methods. All following lessons keep this shape:

METHOD z2ui5_if_app~main.

  me->client = client.
  IF client->check_on_init( ).
    on_init( ).
  ENDIF.

ENDMETHOD.

3. The table view. The table is bound with _bind — one-way is enough, the user does not change the data. columns( ) defines the headers, items( ) defines one template row:

DATA(tab) = view->shell(
    )->page( title = `abap2UI5 Tutorial - 02 Internal Table`
    )->table( client->_bind( t_products ) ).

tab->columns(
    )->column(
        )->text( `Product ID` )->get_parent(
    )->column(
        )->text( `Name` ).
" ...

tab->items(
    )->column_list_item(
        )->cells(
            )->text( `{PRODUCT_ID}`
            )->text( `{NAME}`
            )->text( `{SUPPLIER}`
            )->text( `{QUANTITY}` ).

Binding paths like {PRODUCT_ID} refer to the components of the row structure — always uppercase.

Key takeaway: one column_list_item is a template — UI5 repeats it for every row of the bound table.

Lesson 03 — Database Table

Class: z2ui5_cl_tutorial_03DDIC object: Z2UI5_T_TUTORIALBranch: lesson-03

Real applications read from the database. This lesson ships a transparent table Z2UI5_T_TUTORIAL (key field PRODUCT_ID, plus NAME, SUPPLIER, QUANTITY). The view stays identical to lesson 02 — only the data source changes.

What's new compared to lesson 02

1. The row type is now typed against the DDIC table instead of generic string components — the only change in the public section:

TYPES:
  BEGIN OF ty_s_product,
    product_id TYPE z2ui5_t_tutorial-product_id,
    name       TYPE z2ui5_t_tutorial-name,
    supplier   TYPE z2ui5_t_tutorial-supplier,
    quantity   TYPE z2ui5_t_tutorial-quantity,
  END OF ty_s_product.

2. data_seed fills the table with demo records on first use. MODIFY ... FROM TABLE is idempotent, so the lesson can be restarted any time:

METHOD data_seed.

  SELECT COUNT( * ) FROM z2ui5_t_tutorial INTO @DATA(count).

  IF count > 0.
    RETURN.
  ENDIF.

  DATA t_db TYPE STANDARD TABLE OF z2ui5_t_tutorial WITH EMPTY KEY.
  t_db = VALUE #(
      ( product_id = `HT-1000` name = `Notebook Basic 15` supplier = `SAP` quantity = 10 )
      " ...
      ).

  MODIFY z2ui5_t_tutorial FROM TABLE @t_db.

ENDMETHOD.

3. data_read replaces the hard-coded VALUE #( ) constructor from lesson 02:

METHOD data_read.

  SELECT FROM z2ui5_t_tutorial
    FIELDS product_id, name, supplier, quantity
    ORDER BY product_id
    INTO TABLE @t_products.

ENDMETHOD.

on_init now chains the steps — this separation (data in data_* methods, rendering in view_display) is the pattern all following lessons build on:

METHOD on_init.

  data_seed( ).
  data_read( ).
  view_display( ).

ENDMETHOD.

Key takeaway: the view does not care where the data comes from. Keep SELECTs in dedicated data_* methods.

Lesson 04 — Filtering and Sorting

Class: z2ui5_cl_tutorial_04Branch: lesson-04

A table the user cannot filter or sort is only half a table.

What's new compared to lesson 03

1. A public search attribute and a search bar above the table. The search field is two-way bound, so the search term is already in the ABAP attribute when the event arrives:

DATA search TYPE string.
vbox->hbox(
    )->search_field(
        value  = client->_bind_edit( search )
        search = client->_event( `SEARCH` )
        width  = `17.5rem`
    )->button(
        icon  = `sap-icon://sort-ascending`
        press = client->_event( `SORT_ASCENDING` )
    )->button(
        icon  = `sap-icon://sort-descending`
        press = client->_event( `SORT_DESCENDING` ) ).

2. An on_event method dispatching multiple events. With more than one event, main delegates to on_event and a CASE distinguishes them:

METHOD on_event.

  CASE client->get( )-event.
    WHEN `SEARCH`.
      data_read( ).
    WHEN `SORT_ASCENDING`.
      SORT t_products BY name ASCENDING.
    WHEN `SORT_DESCENDING`.
      SORT t_products BY name DESCENDING.
  ENDCASE.

  client->view_model_update( ).

ENDMETHOD.

3. The filter logic in data_read. After the SELECT, all rows that do not contain the search term in NAME or SUPPLIER are removed (case-insensitive via to_upper):

DATA(filter) = to_upper( search ).
LOOP AT t_products INTO DATA(s_product).

  IF to_upper( s_product-name ) NS filter AND to_upper( s_product-supplier ) NS filter.
    DELETE t_products.
  ENDIF.

ENDLOOP.

4. view_model_update instead of view_display. After each event only the JSON model is transferred to the browser — the view itself is unchanged and is not re-rendered. Faster, and the UI keeps its state (focus, scroll position).

Key takeaway: view_display renders a new view, view_model_update only refreshes the data of the current view. Use the cheapest one that does the job.

Lesson 05 — Toolbar, Select and Delete

Class: z2ui5_cl_tutorial_05Branch: lesson-05

Now the table becomes interactive: the user selects a row and deletes it. (Search and sort from lesson 04 are left out here to keep the focus on selection — combining both is a good exercise.)

What's new compared to lesson 04

1. A selected component in the row structure. UI state like a row selection is just another bound field:

TYPES:
  BEGIN OF ty_s_product,
    selected   TYPE abap_bool,
    product_id TYPE z2ui5_t_tutorial-product_id,
    " ...
  END OF ty_s_product.

2. The table becomes selectable and editable. mode = 'SingleSelectLeft' adds radio buttons, and the items are now bound with _bind_edit — that is what writes the selection state back to t_products on every roundtrip. The item template binds the new component:

)->table(
    mode  = `SingleSelectLeft`
    items = client->_bind_edit( t_products ) ).
" ...
tab->items(
    )->column_list_item( selected = `{SELECTED}`
        )->cells( ...

3. A header_toolbar with a title, a spacer and a Delete button:

tab->header_toolbar(
    )->toolbar(
        )->title( `Products`
        )->toolbar_spacer(
        )->button(
            text  = `Delete`
            icon  = `sap-icon://delete`
            type  = `Reject`
            press = client->_event( `DELETE` ) ).

4. The DELETE event handler. It finds the selected row in the internal table, guards against nothing being selected, deletes from the database and refreshes the model:

METHOD on_event_delete.

  IF NOT line_exists( t_products[ selected = abap_true ] ).
    client->message_toast_display( `Select an entry first` ).
    RETURN.
  ENDIF.

  DATA(s_product) = t_products[ selected = abap_true ].
  DELETE FROM z2ui5_t_tutorial WHERE product_id = @s_product-product_id.

  data_read( ).
  client->view_model_update( ).
  client->message_toast_display( |{ s_product-name } deleted| ).

ENDMETHOD.

Key takeaway: UI state like a row selection is just another bound field — read it from the internal table like any other value.

Lesson 06 — Confirmation Popup

Class: z2ui5_cl_tutorial_06Branch: lesson-06

Deleting without asking is rude. abap2UI5 ships ready-made popups — here we use z2ui5_cl_pop_to_confirm.

What's new compared to lesson 05

1. The DELETE handler no longer deletes. Instead it opens the confirmation popup as a sub-app — the popup is pushed onto the app stack with nav_app_call and reports the user's choice back as a regular event:

METHOD on_event_delete.

  IF NOT line_exists( t_products[ selected = abap_true ] ).
    client->message_toast_display( `Select an entry first` ).
    RETURN.
  ENDIF.

  DATA(s_product) = t_products[ selected = abap_true ].
  client->nav_app_call( z2ui5_cl_pop_to_confirm=>factory(
      i_question_text = |Delete { s_product-name }?|
      i_event_confirm = `DELETE_CONFIRMED`
      i_event_cancel  = `DELETE_CANCELLED` ) ).

ENDMETHOD.

2. Two new events in the dispatcher. Depending on the user's choice, our app receives DELETE_CONFIRMED or DELETE_CANCELLED — the actual DELETE FROM only runs in the confirmed branch:

CASE client->get( )-event.
  WHEN `DELETE`.
    on_event_delete( ).
  WHEN `DELETE_CONFIRMED`.
    on_event_delete_confirmed( ).
  WHEN `DELETE_CANCELLED`.
    client->message_toast_display( `Deletion cancelled` ).
ENDCASE.

The view is completely unchanged compared to lesson 05 — the safety net is pure event flow.

Key takeaway: popups in abap2UI5 are just apps. nav_app_call pushes them on the app stack, and they report back via events.

Lesson 07 — Creating a New Entry

Class: z2ui5_cl_tutorial_07Branch: lesson-07

The final lesson completes the app with an Add button and a custom popup form.

What's new compared to lesson 06

1. A public input structure for the new entry. The popup form binds against it:

DATA s_create TYPE ty_s_product.

2. An Add button in the toolbar. Its handler resets the input structure and opens the popup:

WHEN `ADD`.

  s_create = VALUE #( ).
  popup_create_display( ).

3. A custom dialog built with factory_popup. Same view builder, same binding as the main view — only the factory and the display call differ (popup_display instead of view_display):

METHOD popup_create_display.

  DATA(popup) = z2ui5_cl_xml_view=>factory_popup( ).
  DATA(dialog) = popup->dialog( `Create Product` ).

  dialog->simple_form( editable = abap_true
      )->content( `form`
      )->label( `Product ID`
      )->input( client->_bind_edit( s_create-product_id )
      )->label( `Name`
      )->input( client->_bind_edit( s_create-name )
      " ...
      ).

  dialog->buttons(
      )->button(
          text  = `Cancel`
          press = client->_event( `CREATE_CANCEL` )
      )->button(
          text  = `Save`
          press = client->_event( `CREATE_SAVE` )
          type  = `Emphasized` ).

  client->popup_display( popup->stringify( ) ).

ENDMETHOD.

4. Save and cancel handlers. CREATE_SAVE validates the input, maps the structure to the DDIC row with CORRESPONDING, persists it with MODIFY, closes the popup with popup_destroy and refreshes the table. CREATE_CANCEL simply closes the popup:

METHOD on_event_create_save.

  IF s_create-product_id IS INITIAL OR s_create-name IS INITIAL.
    client->message_toast_display( `Enter at least a product id and a name` ).
    RETURN.
  ENDIF.

  DATA(s_db) = CORRESPONDING z2ui5_t_tutorial( s_create ).
  MODIFY z2ui5_t_tutorial FROM @s_db.

  client->popup_destroy( ).
  data_read( ).
  client->view_model_update( ).
  client->message_toast_display( |{ s_create-name } created| ).

ENDMETHOD.

Delete with confirmation from lesson 06 is still on board — the result is a small but complete maintenance app: list, create, delete with confirmation. This branch also ships the overview app z2ui5_cl_tutorial_00, which lists all lessons and navigates to them with nav_app_call.

Key takeaway: custom popups use the same view builder and the same binding as the main view — only the factory (factory_popup) and the display call (popup_display) differ.

Where to Go Next

Development

Run the linter before committing:

npm install
npx abaplint

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages