abap2UI5

Browse Orders

Master-detail app for browsing orders. - the UI5 demo app "Browse Orders", rebuilt as one self-contained abap2UI5 class.

Z2UI5_CL_SMPC_DEMO_002

Controls sap.m UI5 1.71

The facts

Repository
abap2UI5/samples-controls
Class
Z2UI5_CL_SMPC_DEMO_002
Source file
z2ui5_cl_smpc_demo_002.clas.abap ↗
Library
sap.m
Minimum UI5
1.71 — the floor abap2UI5 holds its samples to
In the playground
runs in the browser, with no system and nothing installed

Keywords: browse, orders, app, flexiblecolumnlayout, viewsettingsdialog, viewsettingsfilteritem, viewsettingsitem, title, list, toolbar, overflowtoolbar, searchfield

Controls it builds

Read out of the builder chain by the abap2UI5 linter, not from the category this sample is filed under — which is what makes “which samples build one of these” a question the catalogue can answer at all.

Libraries

Run it here

The sample running, in this page — the app on its own, with no editor over it. Switch to Playground with this code

Nothing is installed and nothing is sent anywhere: the ABAP is compiled to JavaScript in this browser and runs against abap2UI5 itself.

The ABAP

z2ui5_cl_smpc_demo_002.clas.abap — 1399 lines Read it on GitHub ↗
" @keywords browse orders app flexiblecolumnlayout viewsettingsdialog viewsettingsfilteritem viewsettingsitem title list toolbar overflowtoolbar searchfield
" @summary Master-detail app for browsing orders. - the UI5 demo app "Browse Orders", rebuilt as one self-contained abap2UI5 class.
" @origin demo app Browse Orders (sap.m/orderbrowser) - https://sdk.openui5.org/demoapps (status: generated - machine-written, not yet reviewed)
"! <p class="shorttext">demo app - Browse Orders</p>
"!
"! The UI5 demo app Browse Orders - the master-detail showcase of the demo kit
"! - rebuilt as ONE abap2UI5 class, view for view and control for control:
"! the FlexibleColumnLayout with the order list in the begin column and the
"! order in the mid column, the search, the ViewSettingsDialog that filters
"! and groups (with its group order), the info bar, the line-item table with
"! its totals, the shipping and processor tabs, the share, close and full
"! screen actions, and the NotFound and DetailObjectNotFound pages.
"!
"! The original's router is kept, and so is its URL: the app owns the hash
"! (cs_event-hash_attach_changed) and route_hash( ) matches it against the
"! patterns of manifest.json - "" is the master route,
"! "Orders/{objectId}/:?query:" the object route, anything else is bypassed
"! to NotFound. Each route shows the same pages in the same columns and sets
"! the layouts the route-matched handlers set. navTo is hash_set( ) or - where
"! the original passes bReplace - hash_replace( ): a selection replaces the
"! hash on a desktop and pushes it on a phone, a tab switch replaces it, an
"! object route without a valid ?tab= is rewritten to tab=shipping, and the
"! Back buttons of the not-found pages are onNavBack (cs_event-hash_back,
"! falling back to the master route when the app has no history of its own).
"!
"! Where it differs from the original, and why - only what an app without a
"! browser-side model layer cannot do the same way:
"!
"!  - the OData V2 service and its mock server become ABAP data - the five
"!    mock files verbatim, the Edm.DateTime values as the epoch milliseconds
"!    the mock carries. What the service does is done where the data is,
"!    with the mock server's semantics: the search is its case-sensitive
"!    substringof on the order's own CustomerName (so order 3115, whose
"!    CustomerName the mock leaves empty, is never found), the two filters
"!    are ShippedDate ne/eq null, and the list's sort order is the one the
"!    binding asks for - OrderID descending until the dialog is first
"!    confirmed, then the grouper's sorter (a stable sort on Customer/
"!    CompanyName, OrderDate or ShippedDate, ascending or descending), and no
"!    sorter at all - the mock's own row order - once a confirm carries no
"!    grouping, because _applyGrouper replaces the binding's sorters on every
"!    confirm.
"!  - a GROUP HEADER is made by the list binding, from a Sorter with a group
"!    function the original passes per grouping. A backend cannot hand over a
"!    function, so the rows are bound as JSON the class composes
"!    (_bind( json = abap_true )): each row carries its group as an object
"!    { key, text } - exactly what the original's group functions return - and
"!    cs_event-binding_call sorts on it with group = X. The default group
"!    function then yields that object as the group, and the default
"!    comparator ranks two objects as equal, so the sort leaves the order the
"!    backend chose. A view rebuild loses the sorter; view_display re-issues it.
"!  - the formatter module is business logic and moves to the backend: the
"!    delivery state and text, the unit price's two digits, the order total and
"!    the employee picture's fallback. The dates and amounts keep the
"!    original's DateType and CurrencyType bindings, so they format in the
"!    browser's locale and time zone exactly as there. The group header texts
"!    ("Ordered in November 2016") are composed in ABAP from the UTC date,
"!    where the original's group function reads the month in the browser's
"!    time zone - so east of UTC one order (2686, shipped 2016-10-31 23:00
"!    UTC) is listed under the other month of its shipping date.
"!  - the share action's mail body loses its line breaks: abap2UI5's URLHELPER
"!    action refuses CR/LF in its parameters (a header-injection guard), and
"!    the i18n text carries three of them, so they become spaces. Subject,
"!    recipient-less mailto: and the link to the order are the original's.
"!  - the processor's picture is the original's fallback image
"!    (images/Employee.png, as every Photo of the mock is empty), served from
"!    the demo kit host, because this archive carries no binaries.
"!  - the IllustratedMessage is @since 1.98 and this package holds the 1.71
"!    floor: the empty list shows the same text as a noDataText (the List's
"!    noData aggregation is @since 1.101), and the two not-found pages show
"!    theirs in a MessagePage, the 1.71 empty-page control.
"!  - the i18n resource bundle becomes literals. The busy indicators the
"!    original shows until its OData metadata and data have arrived, and the
"!    ErrorHandler's MessageBox for a failed request, have no service to wait
"!    for or to fail here. The content density needs nothing: abap2UI5's page
"!    already carries sapUiSizeCompact on its body, which is the case in which
"!    the original's getContentDensityClass adds no class either.
"!
"! Original: src/sap.m/test/sap/m/demokit/orderbrowser in OpenUI5, archived
"! under ui5/demoapps/sap.m/orderbrowser.
"! Demo apps: https://sdk.openui5.org/demoapps
CLASS z2ui5_cl_smpc_demo_002 DEFINITION PUBLIC.

  PUBLIC SECTION.
    INTERFACES z2ui5_if_app.

    " Edm.DateTime as the epoch milliseconds of the mock - what the
    " DateType bindings read with source pattern 'timestamp'
    TYPES ty_ms TYPE p LENGTH 8 DECIMALS 0.
    TYPES ty_amount TYPE p LENGTH 12 DECIMALS 2.
    TYPES:
      BEGIN OF ty_s_item,
        productname TYPE string,
        productid   TYPE string,
        unitprice   TYPE string,
        quantity    TYPE string,
        item_total  TYPE ty_amount,
      END OF ty_s_item.
    TYPES:
      BEGIN OF ty_s_mail,
        email   TYPE string,
        subject TYPE string,
        body    TYPE string,
      END OF ty_s_mail.
    TYPES:
      BEGIN OF ty_s_tel,
        tel TYPE string,
      END OF ty_s_tel.

    " the master list - JSON composed in rows_build( ), see the header
    DATA rows_json          TYPE string VALUE `[]`.
    DATA t_items            TYPE STANDARD TABLE OF ty_s_item WITH EMPTY KEY.
    " appView>/layout and appView>/actionButtonsInfo/midColumn/fullScreen
    DATA layout             TYPE string VALUE `OneColumn`.
    DATA check_fullscreen   TYPE abap_bool.
    " masterView
    DATA title_count        TYPE string.
    DATA no_data_text       TYPE string VALUE `No orders are currently available`.
    DATA filter_bar_visible TYPE abap_bool.
    DATA filter_bar_label   TYPE string.
    DATA search_value       TYPE string.
    " the dialog's selections, bound so a rebuilt view shows them again
    DATA flt_shipped        TYPE abap_bool.
    DATA flt_not_shipped    TYPE abap_bool.
    DATA grp_customer       TYPE abap_bool.
    DATA grp_order          TYPE abap_bool.
    DATA grp_shipped        TYPE abap_bool.
    DATA group_descending   TYPE abap_bool.
    " detailView and the bound order
    DATA selected_tab       TYPE string VALUE `shipping`.
    DATA det_title          TYPE string.
    DATA det_customer       TYPE string.
    DATA det_orderdate      TYPE p LENGTH 8 DECIMALS 0.
    DATA det_shippeddate    TYPE p LENGTH 8 DECIMALS 0.
    DATA det_total          TYPE ty_amount.
    DATA det_currency       TYPE string VALUE `EUR`.
    DATA det_items_title    TYPE string VALUE `Line Items`.
    DATA det_shipname       TYPE string.
    DATA det_shipaddress    TYPE string.
    DATA det_shipzipcity    TYPE string.
    DATA det_shipregion     TYPE string.
    DATA det_shipcountry    TYPE string.
    DATA det_employee       TYPE string.
    DATA det_employeeid     TYPE string.
    DATA det_jobtitle       TYPE string.
    DATA det_photo          TYPE string.
    DATA s_tel              TYPE ty_s_tel.
    DATA s_mail             TYPE ty_s_mail.

  PROTECTED SECTION.
    TYPES:
      BEGIN OF ty_s_order,
        orderid      TYPE i,
        customerid   TYPE string,
        customername TYPE string,
        companyname  TYPE string,
        employeeid   TYPE i,
        orderdate    TYPE ty_ms,
        requireddate TYPE ty_ms,
        shippeddate  TYPE ty_ms,
        shipname     TYPE string,
        shipaddress  TYPE string,
        shipcity     TYPE string,
        shipregion   TYPE string,
        shippostal   TYPE string,
        shipcountry  TYPE string,
      END OF ty_s_order.
    TYPES ty_t_order TYPE STANDARD TABLE OF ty_s_order WITH EMPTY KEY.
    TYPES:
      BEGIN OF ty_s_customer,
        customerid  TYPE string,
        companyname TYPE string,
      END OF ty_s_customer.
    TYPES:
      BEGIN OF ty_s_employee,
        employeeid TYPE i,
        firstname  TYPE string,
        lastname   TYPE string,
        title      TYPE string,
        homephone  TYPE string,
      END OF ty_s_employee.
    TYPES:
      BEGIN OF ty_s_product,
        productid   TYPE i,
        productname TYPE string,
      END OF ty_s_product.
    TYPES:
      BEGIN OF ty_s_detail,
        orderid   TYPE i,
        productid TYPE i,
        unitprice TYPE ty_amount,
        quantity  TYPE i,
      END OF ty_s_detail.

    DATA client          TYPE REF TO z2ui5_if_client.
    DATA t_orders        TYPE ty_t_order.
    DATA t_customers     TYPE STANDARD TABLE OF ty_s_customer WITH EMPTY KEY.
    DATA t_employees     TYPE STANDARD TABLE OF ty_s_employee WITH EMPTY KEY.
    DATA t_products      TYPE STANDARD TABLE OF ty_s_product WITH EMPTY KEY.
    DATA t_details       TYPE STANDARD TABLE OF ty_s_detail WITH EMPTY KEY.
    " Master.controller's _oListFilterState and the binding's sorters
    DATA search_term     TYPE string.
    DATA filter_key      TYPE string.
    DATA group_key       TYPE string.
    DATA check_sorted    TYPE abap_bool.
    " the route's objectId, the order bound to the detail page and the one
    " the ListSelector marks in the master list
    DATA route_objectid  TYPE string.
    DATA order_shown     TYPE i.
    DATA order_selected  TYPE i.
    DATA previous_layout TYPE string.
    " the page each column shows, and what the client shows right now -
    " bookkeeping the view never binds
    DATA page_begin      TYPE string VALUE `page`.
    DATA page_mid        TYPE string VALUE `detailPage`.
    DATA page_begin_live TYPE string.
    DATA page_mid_live   TYPE string.
    DATA group_live      TYPE string.

    METHODS view_display.
    METHODS view_dialog
      IMPORTING
        view TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS view_master
      IMPORTING
        fcl TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS view_detail
      IMPORTING
        fcl TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS view_detail_tabs
      IMPORTING
        column TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS view_detail_items
      IMPORTING
        column TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS on_event.
    METHODS on_select
      IMPORTING
        orderid TYPE string.
    METHODS on_search.
    METHODS on_view_settings.
    METHODS on_toggle_fullscreen.
    METHODS route_hash.
    METHODS route_master.
    METHODS route_object
      IMPORTING
        objectid TYPE string
        tab      TYPE string.
    METHODS route_bypassed.
    METHODS detail_bind
      IMPORTING
        order TYPE ty_s_order.
    METHODS pages_sync.
    METHODS group_sync
      IMPORTING
        check_force TYPE abap_bool DEFAULT abap_false.
    METHODS rows_build.
    METHODS rows_sort
      CHANGING
        t_order TYPE ty_t_order.
    METHODS row_json
      IMPORTING
        order         TYPE ty_s_order
        pos           TYPE i
      RETURNING
        VALUE(result) TYPE string.
    METHODS json_text
      IMPORTING
        val           TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS utc_date
      IMPORTING
        val           TYPE ty_ms
      RETURNING
        VALUE(result) TYPE d.
    METHODS month_name
      IMPORTING
        val           TYPE d
      RETURNING
        VALUE(result) TYPE string.
    METHODS amount
      IMPORTING
        val           TYPE ty_amount
      RETURNING
        VALUE(result) TYPE string.
    METHODS model_init.

  PRIVATE SECTION.
ENDCLASS.


CLASS z2ui5_cl_smpc_demo_002 IMPLEMENTATION.

  METHOD z2ui5_if_app~main.

    me->client = client.

    IF client->check_on_init( ).
      model_init( ).
      view_display( ).
    ELSEIF client->check_on_navigated( ).
      view_display( ).
    ELSEIF client->check_on_event( ).
      on_event( ).
    ENDIF.

  ENDMETHOD.


  METHOD view_display.

    " the app owns the hash: the browser Back/Forward buttons and a
    " hand-edited URL round-trip as HASH_CHANGED. Queued FIRST - a route
    " below may already rewrite the hash, and without the listener in place
    " the frontend would append that value instead of writing it. The
    " registration dies with an app switch, so it is re-issued per render
    client->follow_up_action( val = client->cs_event-hash_attach_changed t_arg = VALUE #( ( `HASH_CHANGED` ) ) ).

    " a start, a reload or a shared link: the live hash is matched like the
    " router's initialize( ) matches it
    route_hash( ).
    rows_build( ).

    DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
        )->ele( n = `View` ns = `mvc`
            )->a( n = `displayBlock`   v = `true`
            )->a( n = `height`         v = `100%`
            )->a( n = `xmlns`          v = `sap.m`
            )->a( n = `xmlns:mvc`      v = `sap.ui.core.mvc`
            )->a( n = `xmlns:f`        v = `sap.f`
            )->a( n = `xmlns:semantic` v = `sap.f.semantic`
            )->a( n = `xmlns:core`     v = `sap.ui.core`
            )->a( n = `xmlns:form`     v = `sap.ui.layout.form`
            )->a( n = `xmlns:l`        v = `sap.ui.layout` ).

    view_dialog( view ).

    " App.view.xml. The layout is bound two-way, as appView>/layout is: the
    " FCL's own arrows change it, and toggleFullScreen stores what they left
    DATA(fcl) = view->ele( `App`
        )->a( n = `id` v = `app`

        )->ele( n = `FlexibleColumnLayout` ns = `f`
            )->a( n = `id`               v = `layout`
            )->a( n = `layout`           v = client->_bind( layout )
            )->a( n = `backgroundDesign` v = `Translucent` ).

    view_master( fcl ).
    view_detail( fcl ).

    client->view_display( view->stringify( ) ).

    " a rebuilt FlexibleColumnLayout starts on the first page of each column
    " and a rebuilt list binding without a sorter - both re-issued from the
    " state that survived
    page_begin_live = `page`.
    page_mid_live   = `detailPage`.
    pages_sync( ).
    group_live = ``.
    group_sync( ).

  ENDMETHOD.


  METHOD view_dialog.

    " ViewSettingsDialog.fragment.xml, which Master.onOpenViewSettings loads
    " into the view's dependents - declared there and opened by id without a
    " round-trip. The confirm hands over what _onConfirm reads: the text and
    " key of the filter item, the group item's key and the group order. The
    " selections are bound as well, so a rebuilt view restores them
    view->ele( n = `dependents` ns = `mvc`
        )->ele( `ViewSettingsDialog`
            )->a( n = `id`              v = `viewSettingsDialog`
            )->a( n = `groupDescending` v = client->_bind( group_descending )
            )->a( n = `confirm`         v = client->_event( val   = `VIEW_SETTINGS`
                                                            t_arg = VALUE #( ( `${$parameters>/filterItems}.length ? ${$parameters>/filterItems}[0].getText() : ''` )
                                                                             ( `${$parameters>/filterItems}.length ? ${$parameters>/filterItems}[0].getKey() : ''` )
                                                                             ( `${$parameters>/groupItem} ? ${$parameters>/groupItem}.getKey() : ''` )
                                                                             ( `${$parameters>/groupDescending} ? 'X' : ''` ) ) )

            )->ele( `filterItems`
                )->ele( `ViewSettingsFilterItem`
                    )->a( n = `id`          v = `filterItems`
                    )->a( n = `text`        v = `Orders`
                    )->a( n = `key`         v = `Orders`
                    )->a( n = `multiSelect` b = abap_false

                    )->ele( `items`
                        )->tag( `ViewSettingsItem`
                            )->a( n = `id`       v = `viewFilter1`
                            )->a( n = `text`     v = `Only Shipped Orders`
                            )->a( n = `key`      v = `Shipped`
                            )->a( n = `selected` v = client->_bind( flt_shipped )
                        )->tag( `ViewSettingsItem`
                            )->a( n = `id`       v = `viewFilter2`
                            )->a( n = `text`     v = `Only Orders without Shipment`
                            )->a( n = `key`      v = `NotShipped`
                            )->a( n = `selected` v = client->_bind( flt_not_shipped )

                    )->end(
                )->end(
            )->end(
            )->ele( `groupItems`
                )->tag( `ViewSettingsItem`
                    )->a( n = `text`     v = `Group by Customer`
                    )->a( n = `key`      v = `CompanyName`
                    )->a( n = `selected` v = client->_bind( grp_customer )
                )->tag( `ViewSettingsItem`
                    )->a( n = `text`     v = `Group by Order Period`
                    )->a( n = `key`      v = `OrderDate`
                    )->a( n = `selected` v = client->_bind( grp_order )
                )->tag( `ViewSettingsItem`
                    )->a( n = `text`     v = `Group by Shipped Period`
                    )->a( n = `key`      v = `ShippedDate`
                    )->a( n = `selected` v = client->_bind( grp_shipped ) ).

  ENDMETHOD.


  METHOD view_master.

    DATA(open_filter) = client->follow_up_action( val   = client->cs_event-control_by_id
                                                  t_arg = VALUE #( ( `viewSettingsDialog` ) ( `open` ) ( `filter` ) ) ).

    " the begin column: Master.view.xml and the NotFound target
    DATA(column) = fcl->ele( n = `beginColumnPages` ns = `f` ).

    DATA(list) = column->ele( n = `SemanticPage` ns = `semantic`
        )->a( n = `id`           v = `page`
        )->a( n = `core:require` v = `{DateType: 'sap/ui/model/type/Date'}`

        )->ele( n = `titleHeading` ns = `semantic`
            )->tag( `Title`
                )->a( n = `id`   v = `masterHeaderTitle`
                )->a( n = `text` v = client->_bind( title_count )

        )->end(
        )->ele( n = `content` ns = `semantic`
            )->ele( `List`
                )->a( n = `id`                  v = `list`
                )->a( n = `width`               v = `auto`
                )->a( n = `class`               v = `sapFDynamicPageAlignContent`
                )->a( n = `items`               v = client->_bind( val = rows_json json = abap_true )
                )->a( n = `noDataText`          v = client->_bind( no_data_text )
                )->a( n = `mode`                v = `{= ${device>/system/phone} ? 'None' : 'SingleSelectMaster'}`
                )->a( n = `growing`             b = abap_true
                )->a( n = `growingScrollToLoad` b = abap_true
                " a click in SingleSelectMaster mode fires the list's
                " selectionChange and never the item's press
                )->a( n = `selectionChange`     v = client->_event( val = `SELECT`
                                                                    arg = `${$parameters>/listItem}.getBindingContext().getProperty('orderid')` ) ).

    " the info bar and the three header controls open the dialog on the page
    " onOpenViewSettings picks: "group" for the group button, "filter" for
    " everything else
    list->ele( `infoToolbar`
        )->ele( `Toolbar`
            )->a( n = `active`  b = abap_true
            )->a( n = `id`      v = `filterBar`
            )->a( n = `visible` v = client->_bind( filter_bar_visible )
            )->a( n = `press`   v = open_filter

            )->tag( `Title`
                )->a( n = `id`   v = `filterBarLabel`
                )->a( n = `text` v = client->_bind( filter_bar_label ) ).

    list->ele( `headerToolbar`
        )->ele( `OverflowToolbar`

            )->ele( `SearchField`
                )->a( n = `id`                 v = `searchField`
                )->a( n = `showRefreshButton`  b = abap_true
                )->a( n = `tooltip`            v = `Enter an order name or a part of it.`
                )->a( n = `width`              v = `100%`
                )->a( n = `value`              v = client->_bind( search_value )
                )->a( n = `search`             v = client->_event( val   = `SEARCH`
                                                                   t_arg = VALUE #( ( `${$parameters>/query}` )
                                                                                    ( `${$parameters>/refreshButtonPressed} ? 'X' : ''` ) ) )

                )->ele( `layoutData`
                    )->tag( `OverflowToolbarLayoutData`
                        )->a( n = `minWidth`   v = `150px`
                        )->a( n = `maxWidth`   v = `240px`
                        )->a( n = `shrinkable` b = abap_true
                        )->a( n = `priority`   v = `NeverOverflow`

                )->end(
            )->end(
            )->tag( `ToolbarSpacer`
            )->tag( `Button`
                )->a( n = `id`    v = `filterButton`
                )->a( n = `press` v = open_filter
                )->a( n = `icon`  v = `sap-icon://filter`
                )->a( n = `type`  v = `Transparent`
            )->tag( `Button`
                )->a( n = `id`    v = `groupButton`
                )->a( n = `press` v = client->follow_up_action( val   = client->cs_event-control_by_id
                                                                t_arg = VALUE #( ( `viewSettingsDialog` ) ( `open` ) ( `group` ) ) )
                )->a( n = `icon`  v = `sap-icon://group-2`
                )->a( n = `type`  v = `Transparent` ).

    " the rows are the JSON of rows_build( ), so their fields are the lower
    " case keys composed there
    list->ele( `items`
        )->ele( `ObjectListItem`
            )->a( n = `type`     v = `{= ${device>/system/phone} ? 'Active' : 'Inactive'}`
            )->a( n = `press`    v = client->_event( val = `SELECT` arg = `${orderid}` )
            )->a( n = `selected` v = `{selected}`
            )->a( n = `title`    v = `{title}`
            )->a( n = `number`   v = `{ path: 'orderdate', type: 'DateType', formatOptions: { style: 'short', source: { pattern: 'timestamp' } } }`

            )->ele( `firstStatus`
                )->tag( `ObjectStatus`
                    )->a( n = `state` v = `{state}`
                    )->a( n = `text`  v = `{text}`

            )->end(
            )->ele( `attributes`
                )->tag( `ObjectAttribute`
                    )->a( n = `id`   v = `companyName`
                    )->a( n = `text` v = `{companyname}`
                )->tag( `ObjectAttribute`
                    )->a( n = `title` v = `Shipped`
                    )->a( n = `text`  v = `{= ${shippeddate} ? ${ path: 'shippeddate', type: 'DateType', ` &&
                                          `formatOptions: { style: 'medium', source: { pattern: 'timestamp' } } } : 'Not shipped yet' }` ).

    " NotFound.view.xml - the bypassed target
    column->ele( `Page`
        )->a( n = `id`             v = `notFoundPage`
        )->a( n = `title`          v = `Not Found`
        )->a( n = `showNavButton`  b = abap_true
        )->a( n = `navButtonPress` v = client->_event( `NAV_BACK` )

        )->tag( `MessagePage`
            )->a( n = `showHeader`  b = abap_false
            )->a( n = `text`        v = `The requested resource was not found`
            )->a( n = `description` v = ``
            )->a( n = `icon`        v = `sap-icon://documents` ).

  ENDMETHOD.


  METHOD view_detail.


    " the mid column: Detail.view.xml and the DetailObjectNotFound target
    DATA(column) = fcl->ele( n = `midColumnPages` ns = `f` ).

    DATA(detail) = column->ele( n = `SemanticPage` ns = `semantic`
        )->a( n = `id`           v = `detailPage`
        )->a( n = `core:require` v = `{DateType: 'sap/ui/model/type/Date', CurrencyType: 'sap/ui/model/type/Currency'}` ).

    detail->ele( n = `titleHeading` ns = `semantic`
        )->tag( `Title`
            )->a( n = `text` v = client->_bind( det_title ) ).

    DATA(header) = detail->ele( n = `headerContent` ns = `semantic`
        )->ele( n = `HorizontalLayout` ns = `l` ).

    header->ele( n = `VerticalLayout` ns = `l`
        )->a( n = `class` v = `sapUiMediumMarginEnd`

        )->tag( `ObjectAttribute`
            )->a( n = `title` v = `Customer`
            )->a( n = `text`  v = client->_bind( det_customer )
        )->tag( `ObjectAttribute`
            )->a( n = `title` v = `Ordered`
            )->a( n = `text`  v = |\{ path: '{ client->_bind( val = det_orderdate path = abap_true ) }', type: 'DateType', | &&
                                  |formatOptions: \{ style: 'medium', source: \{ pattern: 'timestamp' \} \} \}|
        )->tag( `ObjectAttribute`
            )->a( n = `title` v = `Shipped`
            )->a( n = `text`  v = |\{= ${ client->_bind( det_shippeddate ) } ? $\{ path: '{ client->_bind( val = det_shippeddate path = abap_true ) }', | &&
                                  |type: 'DateType', formatOptions: \{ style: 'medium', source: \{ pattern: 'timestamp' \} \} \} : 'Not shipped yet' \}| ).

    header->ele( n = `VerticalLayout` ns = `l`
        )->tag( `Label`
            )->a( n = `text` v = `Price`
        )->tag( `ObjectNumber`
            )->a( n = `number` v = |\{ parts: [ \{ path: '{ client->_bind( val = det_total path = abap_true ) }' \}, | &&
                                   |\{ path: '{ client->_bind( val = det_currency path = abap_true ) }' \} ], | &&
                                   |type: 'CurrencyType', formatOptions: \{ showMeasure: false \} \}|
            )->a( n = `unit`   v = client->_bind( det_currency ) ).

    DATA(content) = detail->ele( n = `content` ns = `semantic`
        )->ele( n = `VerticalLayout` ns = `l` ).
    view_detail_tabs( content ).
    view_detail_items( content ).

    " the share action is URLHelper.triggerEmail - a frontend action that
    " reads the bound mail structure when it is pressed, no round-trip
    detail->ele( n = `sendEmailAction` ns = `semantic`
        )->tag( n = `SendEmailAction` ns = `semantic`
            )->a( n = `id`    v = `shareEmail`
            )->a( n = `press` v = client->follow_up_action( val   = client->cs_event-urlhelper
                                                            t_arg = VALUE #( ( `TRIGGER_EMAIL` ) ( |${ client->_bind( s_mail ) }| ) ) ) ).

    detail->ele( n = `closeAction` ns = `semantic`
        )->tag( n = `CloseAction` ns = `semantic`
            )->a( n = `id`    v = `closeColumn`
            )->a( n = `press` v = client->_event( `CLOSE_DETAIL` ) ).

    detail->ele( n = `fullScreenAction` ns = `semantic`
        )->tag( n = `FullScreenAction` ns = `semantic`
            )->a( n = `id`      v = `enterFullScreen`
            )->a( n = `visible` v = |\{= !$\{device>/system/phone\} && !${ client->_bind( check_fullscreen ) } \}|
            )->a( n = `press`   v = client->_event( `TOGGLE_FULLSCREEN` ) ).

    detail->ele( n = `exitFullScreenAction` ns = `semantic`
        )->tag( n = `ExitFullScreenAction` ns = `semantic`
            )->a( n = `id`      v = `exitFullScreen`
            )->a( n = `visible` v = |\{= !$\{device>/system/phone\} && ${ client->_bind( check_fullscreen ) } \}|
            )->a( n = `press`   v = client->_event( `TOGGLE_FULLSCREEN` ) ).

    " DetailObjectNotFound.view.xml - the target _onBindingChange displays
    " when the route names an order the service does not have
    column->ele( `Page`
        )->a( n = `id`             v = `detailObjectNotFoundPage`
        )->a( n = `title`          v = `Order Details`
        )->a( n = `showNavButton`  v = `{= ${device>/system/phone} || ${device>/system/tablet} && ${device>/orientation/portrait} }`
        )->a( n = `navButtonPress` v = client->_event( `NAV_BACK` )

        )->tag( `MessagePage`
            )->a( n = `showHeader`  b = abap_false
            )->a( n = `text`        v = `This order is not available`
            )->a( n = `description` v = ``
            )->a( n = `icon`        v = `sap-icon://search` ).

  ENDMETHOD.


  METHOD view_detail_tabs.

    " the shipping and processor targets are displayed INTO these two tabs;
    " a tab switch is onTabSelect, which rewrites the route's ?tab= query
    DATA(tabs) = column->ele( `IconTabBar`
        )->a( n = `id`                     v = `iconTabBar`
        )->a( n = `headerBackgroundDesign` v = `Transparent`
        )->a( n = `select`                 v = client->_event( val = `TAB_SELECT` arg = `${$parameters>/selectedKey}` )
        )->a( n = `selectedKey`            v = client->_bind( selected_tab )

        )->ele( `items` ).

    " Shipping.view.xml
    tabs->ele( `IconTabFilter`
        )->a( n = `id`      v = `iconTabFilterShipping`
        )->a( n = `icon`    v = `sap-icon://shipping-status`
        )->a( n = `tooltip` v = `Shipping Info`
        )->a( n = `key`     v = `shipping`

        )->ele( `VBox`
            )->ele( n = `SimpleForm` ns = `form`
                )->a( n = `id`         v = `SimpleFormShipAddress`
                )->a( n = `editable`   b = abap_false
                )->a( n = `layout`     v = `ResponsiveGridLayout`
                )->a( n = `title`      v = `Shipping Address`
                )->a( n = `labelSpanL` v = `3`
                )->a( n = `labelSpanM` v = `3`
                )->a( n = `emptySpanL` v = `4`
                )->a( n = `emptySpanM` v = `4`
                )->a( n = `columnsL`   v = `1`
                )->a( n = `columnsM`   v = `1`

                )->ele( n = `content` ns = `form`
                    )->tag( `Label`
                        )->a( n = `text` v = `Name`
                    )->tag( `Text`
                        )->a( n = `text` v = client->_bind( det_shipname )
                    )->tag( `Label`
                        )->a( n = `text` v = `Street`
                    )->tag( `Text`
                        )->a( n = `text` v = client->_bind( det_shipaddress )
                    )->tag( `Label`
                        )->a( n = `text` v = `ZIP Code / City`
                    )->tag( `Text`
                        )->a( n = `text` v = client->_bind( det_shipzipcity )
                    )->tag( `Label`
                        )->a( n = `text` v = `Region`
                    )->tag( `Text`
                        )->a( n = `text` v = client->_bind( det_shipregion )
                    )->tag( `Label`
                        )->a( n = `text` v = `Country`
                    )->tag( `Text`
                        )->a( n = `text` v = client->_bind( det_shipcountry ) ).

    " Processor.view.xml. The phone link is URLHelper.triggerTel - a frontend
    " action reading the bound number, no round-trip
    tabs->ele( `IconTabFilter`
        )->a( n = `id`      v = `iconTabFilterProcessor`
        )->a( n = `icon`    v = `sap-icon://employee`
        )->a( n = `tooltip` v = `Processor`
        )->a( n = `key`     v = `processor`

        )->ele( `VBox`
            )->ele( n = `SimpleForm` ns = `form`
                )->a( n = `id`         v = `SimpleFormProcessorInfo`
                )->a( n = `editable`   b = abap_false
                )->a( n = `layout`     v = `ResponsiveGridLayout`
                )->a( n = `title`      v = `Processor Information`
                )->a( n = `labelSpanL` v = `3`
                )->a( n = `labelSpanM` v = `3`
                )->a( n = `emptySpanL` v = `4`
                )->a( n = `emptySpanM` v = `4`
                )->a( n = `columnsL`   v = `2`
                )->a( n = `columnsM`   v = `2`

                )->ele( n = `content` ns = `form`
                    )->tag( n = `Title` ns = `core`
                        )->a( n = `text` v = `Details`
                    )->tag( `Label`
                        )->a( n = `text` v = `Name`
                    )->tag( `Text`
                        )->a( n = `text` v = client->_bind( det_employee )
                    )->tag( `Label`
                        )->a( n = `text` v = `Employee ID`
                    )->tag( `Text`
                        )->a( n = `text` v = client->_bind( det_employeeid )
                    )->tag( `Label`
                        )->a( n = `text` v = `Job Title`
                    )->tag( `Text`
                        )->a( n = `text` v = client->_bind( det_jobtitle )
                    )->tag( `Label`
                        )->a( n = `text` v = `Phone`
                    )->tag( `Link`
                        )->a( n = `text`  v = client->_bind( s_tel-tel )
                        )->a( n = `press` v = client->follow_up_action( val   = client->cs_event-urlhelper
                                                                        t_arg = VALUE #( ( `TRIGGER_TEL` ) ( |${ client->_bind( s_tel ) }| ) ) )
                    )->tag( n = `Title` ns = `core`
                        )->a( n = `text` v = `Picture`
                    )->tag( `Image`
                        )->a( n = `src`    v = client->_bind( det_photo )
                        )->a( n = `width`  v = `50%`
                        )->a( n = `height` v = `50%` ).

  ENDMETHOD.


  METHOD view_detail_items.

    DATA(items) = column->ele( `Table`
        )->a( n = `id`         v = `lineItemsList`
        )->a( n = `class`      v = `sapUiSmallMarginTop`
        )->a( n = `width`      v = `auto`
        )->a( n = `items`      v = client->_bind( t_items )
        )->a( n = `noDataText` v = `No line items` ).

    items->ele( `headerToolbar`
        )->ele( `Toolbar`
            )->a( n = `id` v = `lineItemsToolbar`

            )->tag( `Title`
                )->a( n = `id`   v = `lineItemsHeader`
                )->a( n = `text` v = client->_bind( det_items_title ) ).

    items->ele( `columns`
        )->ele( `Column`

            )->tag( `Text`
                )->a( n = `text` v = `Product`

        )->end(
        )->ele( `Column`
            )->a( n = `minScreenWidth` v = `Tablet`
            )->a( n = `demandPopin`    b = abap_true
            )->a( n = `hAlign`         v = `End`

            )->tag( `Text`
                )->a( n = `text` v = `Unit Price`

        )->end(
        )->ele( `Column`
            )->a( n = `minScreenWidth` v = `Tablet`
            )->a( n = `demandPopin`    b = abap_true
            )->a( n = `hAlign`         v = `End`

            )->tag( `Text`
                )->a( n = `text` v = `Quantity`

        )->end(
        )->ele( `Column`
            )->a( n = `minScreenWidth` v = `Tablet`
            )->a( n = `demandPopin`    b = abap_true
            )->a( n = `hAlign`         v = `End`

            )->tag( `Text`
                )->a( n = `text` v = `Total` ).

    items->ele( `items`
        )->ele( `ColumnListItem`

            )->ele( `cells`
                )->tag( `ObjectIdentifier`
                    )->a( n = `title` v = `{PRODUCTNAME}`
                    )->a( n = `text`  v = `{PRODUCTID}`
                )->tag( `ObjectNumber`
                    )->a( n = `number` v = `{UNITPRICE}`
                    )->a( n = `unit`   v = client->_bind( det_currency )
                )->tag( `ObjectAttribute`
                    )->a( n = `text` v = `{QUANTITY}`
                )->tag( `ObjectNumber`
                    )->a( n = `number` v = |\{ parts: [ \{ path: 'ITEM_TOTAL' \}, \{ path: '{ client->_bind( val = det_currency path = abap_true ) }' \} ], | &&
                                           |type: 'CurrencyType', formatOptions: \{ showMeasure: false \} \}|
                    )->a( n = `unit`   v = client->_bind( det_currency ) ).

  ENDMETHOD.


  METHOD on_event.

    CASE client->get_event( ).

      WHEN `SELECT`.
        on_select( client->get_event_arg( ) ).

      WHEN `SEARCH`.
        on_search( ).

      WHEN `VIEW_SETTINGS`.
        on_view_settings( ).

      WHEN `HASH_CHANGED`.
        route_hash( ).

      WHEN `TAB_SELECT`.
        " onTabSelect: navTo( "object", { objectId, query: { tab } }, true )
        selected_tab = client->get_event_arg( ).
        client->hash_replace( |/Orders/{ route_objectid }/?tab={ selected_tab }| ).

      WHEN `CLOSE_DETAIL`.
        " onCloseDetailPress: out of full screen, no selection left in the
        " master list, and navTo( "master" ) - a pushed history entry
        check_fullscreen = abap_false.
        order_selected   = 0.
        client->hash_set( `/` ).
        route_master( ).

      WHEN `TOGGLE_FULLSCREEN`.
        on_toggle_fullscreen( ).

      WHEN `NAV_BACK`.
        " BaseController.onNavBack: one step back in the history, or - when
        " the app has none of its own - navTo( "master", {}, true )
        client->follow_up_action( val = client->cs_event-hash_back t_arg = VALUE #( ( `/` ) ) ).

    ENDCASE.

    rows_build( ).
    pages_sync( ).

  ENDMETHOD.


  METHOD on_select.

    " Master._showDetail: two columns, and navTo( "object" ) - replacing the
    " hash on a desktop, pushing it on a phone. The object route has no ?tab=
    " then, so _onObjectMatched rewrites it to tab=shipping in place: the net
    " effect is one write of the final hash
    layout = `TwoColumnsMidExpanded`.
    DATA(hash) = |/Orders/{ orderid }/?tab=shipping|.
    IF client->get( )-s_device-system = z2ui5_if_client=>cs_device-system-phone.
      client->hash_set( hash ).
    ELSE.
      client->hash_replace( hash ).
    ENDIF.
    route_object( objectid = orderid tab = `shipping` ).

  ENDMETHOD.


  METHOD on_search.

    " onSearch: the refresh button only refreshes the binding - the query in
    " the field is NOT applied then
    IF client->get_event_arg( 2 ) = `X`.
      RETURN.
    ENDIF.
    search_term = client->get_event_arg( ).
    group_sync( abap_true ).

  ENDMETHOD.


  METHOD on_view_settings.

    " onConfirmViewSettingsDialog: the filter and the info bar - which shows
    " only while a filter is set, but is labelled either way
    filter_bar_label   = |Filtered by { client->get_event_arg( ) }|.
    filter_key         = client->get_event_arg( 2 ).
    filter_bar_visible = xsdbool( filter_key IS NOT INITIAL ).
    flt_shipped        = xsdbool( filter_key = `Shipped` ).
    flt_not_shipped    = xsdbool( filter_key = `NotShipped` ).

    " _applyGrouper: the grouper's sorter REPLACES the binding's sorters -
    " the OrderID sorter of the view is gone after the first confirm
    group_key        = client->get_event_arg( 3 ).
    group_descending = xsdbool( client->get_event_arg( 4 ) = `X` ).
    grp_customer     = xsdbool( group_key = `CompanyName` ).
    grp_order        = xsdbool( group_key = `OrderDate` ).
    grp_shipped      = xsdbool( group_key = `ShippedDate` ).

The first 900 lines of 1399 — the whole class is on GitHub.

More in sap.m

Search every abap2UI5 sample · the full list on one page

On this page