abap2UI5

Shopping Cart

The classic business process of finding and ordering products. - the UI5 demo app "Shopping Cart", rebuilt as one self-contained abap2UI5 class.

Z2UI5_CL_SMPC_DEMO_004

Controls sap.m UI5 1.71

The facts

Repository
abap2UI5/samples-controls
Class
Z2UI5_CL_SMPC_DEMO_004
Source file
z2ui5_cl_smpc_demo_004.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: shopping, cart, app, flexiblecolumnlayout, navcontainer, viewsettingsfilteritem, viewsettingsitem, viewsettingscustomitem, verticallayout, rangeslider, messageitem, link

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_004.clas.abap — 4813 lines Read it on GitHub ↗
" @keywords shopping cart app flexiblecolumnlayout navcontainer viewsettingsfilteritem viewsettingsitem viewsettingscustomitem verticallayout rangeslider messageitem link
" @summary The classic business process of finding and ordering products. - the UI5 demo app "Shopping Cart", rebuilt as one self-contained abap2UI5 class.
" @origin demo app Shopping Cart (sap.m/cart) - https://sdk.openui5.org/demoapps (status: generated - machine-written, not yet reviewed)
"! <p class="shorttext">demo app - Shopping Cart</p>
"!
"! The UI5 demo app Shopping Cart - the demo kit's showcase application -
"! rebuilt as ONE abap2UI5 class, page for page and control for control:
"! the category list with its live search, the product list per category
"! with its filter dialog, the welcome page, the product page, the product
"! comparison, the cart with its edit mode and save-for-later, the checkout
"! wizard with payment branch, addresses, delivery type and its summary,
"! and the order confirmation - with every MessageBox and MessageToast the
"! original shows. The original's FlexibleColumnLayout and its router are
"! kept: every route of its manifest.json is a call of route_to( ) here,
"! which puts the same pages into the same three columns, and the column
"! layouts its controllers set are set at the same moments.
"!
"! Where it differs from the original, and why - only what an app without a
"! browser-side model layer or a URL cannot do the same way:
"!
"!  - the OData V2 service and its mock server become ABAP data: the 123
"!    products, the 16 categories and the 13 featured products of the demo
"!    kit mock, verbatim. The search keeps the mock server's semantics (a
"!    case-sensitive substringof on the name), the category filter the
"!    original's Filter objects (OR inside a group, AND across groups).
"!  - there is no URL, so a route is a method call rather than a hash. The
"!    Back buttons (shown on small screens only, as there) walk the history
"!    of the routes taken instead of the browser's, and the NotFound target
"!    - reachable only through an unknown id typed into the URL - has
"!    nothing that could reach it and is not rebuilt.
"!  - the cart and the saved-for-later list stay in the BROWSER's local
"!    storage, exactly as the original's LocalStorageModel keeps them, under
"!    the same key (SHOPPING_CART). abap2UI5 ships both halves: the
"!    STORE_DATA frontend action writes - with a payload this class composes
"!    as JSON, because that action destructures its one argument and a
"!    `${ _bind( ) }` in it is resolved only for a VIEW-WIRED action, so the
"!    binding a handler passes arrives as text and the frontend deletes the
"!    key instead of writing it - and the invisible z2ui5.cc.Storage
"!    control reads the key back into its two-way bound `value`, so what it
"!    found is in s_storage-value by the time the `finished` event is
"!    handled - no JSON is parsed anywhere in this class. So a closed
"!    browser loses nothing here either - and the backend still sees the
"!    cart on every round-trip, which is where a price, a reservation or an
"!    order would be decided. Only a round-trip that CHANGED the cart writes
"!    (cart_refresh); startup and the restore mirror the tables into the
"!    bound structure without writing (cart_mirror), and the read wire
"!    carries check_queue_last. The comparison model's own local storage
"!    key (PRODUCT_COMPARISON) is not kept: every route that shows a
"!    comparison carries its two products, so the stored copy is never read.
"!  - the formatter module is business logic and moves to the backend: the
"!    price format, the status text and its ValueState, the cart total. The
"!    welcome tiles keep the original's CurrencyType binding, which formats
"!    in the browser's locale - a type, not a formatter.
"!  - the wizard's input checks run in ABAP. The original types every
"!    required input with a StringType (minLength, a `search` regex) or its
"!    EmailType, so a changed field turns red with the type's message, the
"!    message lands in the message model its footer's MessagePopover lists,
"!    and each step's change handler calls validateStep/invalidateStep. Here
"!    the same constraints are ABAP (input_error), decided on the same
"!    change events; the value states, the message list behind the same
"!    footer button and each step's bound `validated` follow from them. The
"!    regexes are the original's with \s spelled as a space and \w as
"!    [a-zA-Z0-9_], the one spelling ABAP POSIX and JavaScript read alike
"!    inside a bracket expression.
"!  - the original's _setDiscardableProperty compares a step with
"!    Wizard.getProgressStep( ), which no backend can read. The activate
"!    events of the steps behind the payment step and behind the invoice
"!    step set a flag instead (payment_passed, invoice_passed), and the
"!    Yes/No warnings ask on those.
"!  - the carousel's eight-second advance is the START_TIMER frontend
"!    event: its CAROUSEL_TICK event answers with the carousel's next( ) as
"!    a follow-up action and nothing else, so the tick re-renders nothing.
"!    A page change re-arms the timer from the view with no round-trip, and
"!    since START_TIMER holds one timer per app, that restarts the wait as
"!    onCarouselPageChanged does. Every round-trip cancels the pending timer,
"!    so every response re-arms it while the carousel is on screen - which
"!    also means any round-trip, not only a page change, restarts the
"!    eight seconds. The original's setTimeout keeps running on every page;
"!    this timer does not, because each of its ticks is a round-trip. Both
"!    armings pass START_TIMER's optional third argument `X`, so that
"!    round-trip keeps the busy overlay down - the original's client-side
"!    advance shows none either. abap2UI5 1.145.0 ignores the argument and
"!    raises the overlay on a slow tick as before. The random start page
"!    and the two promoted items are drawn in ABAP with cl_abap_random_int -
"!    once per app start, as there.
"!  - the i18n resource bundle becomes literals. The busy indicator the
"!    original shows until its OData metadata has loaded has nothing to wait
"!    for here, and the content density is abap2UI5's shell's to pick.
"!  - the style.css rules ride along in a core:HTML style block, because a
"!    port has no manifest to link a stylesheet from - scoped under the same
"!    `.sapUiDemoCart` class the App and the FlexibleColumnLayout carry.
"!
"! Original: src/sap.m/test/sap/m/demokit/cart in OpenUI5, archived under
"! ui5/demoapps/sap.m/cart.
"! Demo apps: https://sdk.openui5.org/demoapps
CLASS z2ui5_cl_smpc_demo_004 DEFINITION PUBLIC.

  PUBLIC SECTION.
    INTERFACES z2ui5_if_app.

    TYPES ty_amount TYPE p LENGTH 13 DECIMALS 2.
    TYPES:
      BEGIN OF ty_s_category,
        category         TYPE string,
        categoryname     TYPE string,
        numberofproducts TYPE i,
      END OF ty_s_category.
    " one product as a list row or a welcome tile shows it
    TYPES:
      BEGIN OF ty_s_row,
        productid    TYPE string,
        category     TYPE string,
        name         TYPE string,
        suppliername TYPE string,
        price        TYPE ty_amount,
        price_text   TYPE string,
        currencycode TYPE string,
        pictureurl   TYPE string,
        status       TYPE string,
        status_text  TYPE string,
        status_state TYPE string,
        selected     TYPE abap_bool,
      END OF ty_s_row.
    TYPES ty_t_row TYPE STANDARD TABLE OF ty_s_row WITH EMPTY KEY.
    " one product as the product page and a comparison panel show it
    TYPES:
      BEGIN OF ty_s_detail,
        productid        TYPE string,
        category         TYPE string,
        name             TYPE string,
        suppliername     TYPE string,
        shortdescription TYPE string,
        price_text       TYPE string,
        pictureurl       TYPE string,
        status_text      TYPE string,
        status_state     TYPE string,
        weight_text      TYPE string,
        measures_text    TYPE string,
      END OF ty_s_detail.
    " one cart entry - the fields the cart lists, the checkout and the
    " summary show, and what the browser's local storage keeps
    TYPES:
      BEGIN OF ty_s_entry,
        productid    TYPE string,
        category     TYPE string,
        name         TYPE string,
        pictureurl   TYPE string,
        price_text   TYPE string,
        currencycode TYPE string,
        quantity     TYPE i,
        status_text  TYPE string,
        status_state TYPE string,
      END OF ty_s_entry.
    TYPES ty_t_entry TYPE STANDARD TABLE OF ty_s_entry WITH EMPTY KEY.
    TYPES:
      BEGIN OF ty_s_store,
        cart  TYPE ty_t_entry,
        saved TYPE ty_t_entry,
      END OF ty_s_store.
    TYPES:
      BEGIN OF ty_s_storage,
        type   TYPE string,
        prefix TYPE string,
        key    TYPE string,
        value  TYPE ty_s_store,
      END OF ty_s_storage.
    TYPES:
      BEGIN OF ty_s_supplier,
        suppliername TYPE string,
        selected     TYPE abap_bool,
      END OF ty_s_supplier.
    " one entry of the checkout's message model, as its MessagePopover lists it
    TYPES:
      BEGIN OF ty_s_message,
        type           TYPE string,
        message        TYPE string,
        additionaltext TYPE string,
      END OF ty_s_message.
    " one component per checked input, named like the field it checks: the
    " value state the original's StringType constraints put on a field when
    " it is changed, and the type's message. Enum-typed, so `None` and never
    " empty (model_init)
    TYPES:
      BEGIN OF ty_s_checks,
        cc_name       TYPE string,
        cc_number     TYPE string,
        cc_code       TYPE string,
        cc_expire     TYPE string,
        cod_firstname TYPE string,
        cod_lastname  TYPE string,
        cod_phone     TYPE string,
        cod_email     TYPE string,
        inv_address   TYPE string,
        inv_city      TYPE string,
        inv_zip       TYPE string,
        inv_country   TYPE string,
        del_address   TYPE string,
        del_city      TYPE string,
        del_zip       TYPE string,
        del_country   TYPE string,
      END OF ty_s_checks.

    " the app view model of the original (appView>): the FCL layout and the
    " smallScreenMode its stateChange handler keeps - true until the first
    " stateChange says otherwise, as there
    DATA layout            TYPE string.
    DATA small_screen      TYPE abap_bool VALUE abap_true.

    " Home
    DATA search_visible    TYPE abap_bool.
    DATA t_search          TYPE ty_t_row.
    DATA t_categories      TYPE STANDARD TABLE OF ty_s_category WITH EMPTY KEY.

    " Category, and its filter dialog
    DATA category_name     TYPE string.
    DATA t_category        TYPE ty_t_row.
    DATA t_suppliers       TYPE STANDARD TABLE OF ty_s_supplier WITH EMPTY KEY.
    DATA flt_available     TYPE abap_bool.
    DATA flt_out_of_stock  TYPE abap_bool.
    DATA flt_discontinued  TYPE abap_bool.
    DATA flt_low           TYPE i.
    DATA flt_high          TYPE i VALUE 5000.
    DATA flt_price_count   TYPE i.
    DATA info_visible      TYPE abap_bool.
    DATA info_text         TYPE string.

    " Welcome
    DATA t_promoted        TYPE ty_t_row.
    DATA t_viewed          TYPE ty_t_row.
    DATA t_favorite        TYPE ty_t_row.

    " Product - enum-typed status_state: the UI5 default until a product is
    " opened, an empty value is rejected outright
    DATA s_prod            TYPE ty_s_detail.

    " Comparison
    DATA s_cmp1            TYPE ty_s_detail.
    DATA s_cmp2            TYPE ty_s_detail.
    DATA cmp1_visible      TYPE abap_bool.
    DATA cmp2_visible      TYPE abap_bool.
    DATA cmp_placeholder   TYPE abap_bool.

    " Cart - the original's cfg> model is in_delete; the title follows it
    DATA t_cart            TYPE ty_t_entry.
    DATA t_saved           TYPE ty_t_entry.
    " what the original's LocalStorageModel is: the cart under its own key in
    " the browser's local storage. The whole structure is what STORE_DATA
    " writes, and `value` is what the z2ui5.cc.Storage control reads back
    DATA s_storage         TYPE ty_s_storage.
    DATA cart_total        TYPE string.
    DATA in_delete         TYPE abap_bool.
    " formatter.hasItems for the Edit button (either list) and for Proceed
    " (the cart)
    DATA cart_any          TYPE abap_bool.
    DATA cart_filled       TYPE abap_bool.
    DATA cart_title        TYPE string VALUE `Shopping Cart`.

    " Checkout - the original's one JSON model, one flat set of fields
    DATA pay_type          TYPE string.
    DATA cc_name           TYPE string.
    DATA cc_number         TYPE string.
    DATA cc_code           TYPE string.
    DATA cc_expire         TYPE string.
    DATA cod_firstname     TYPE string.
    DATA cod_lastname      TYPE string.
    DATA cod_phone         TYPE string.
    DATA cod_email         TYPE string.
    DATA inv_address       TYPE string.
    DATA inv_city          TYPE string.
    DATA inv_zip           TYPE string.
    DATA inv_country       TYPE string.
    DATA inv_note          TYPE string.
    DATA del_different     TYPE abap_bool.
    DATA del_address       TYPE string.
    DATA del_city          TYPE string.
    DATA del_zip           TYPE string.
    DATA del_country       TYPE string.
    DATA del_note          TYPE string.
    DATA del_type          TYPE string.
    DATA s_state           TYPE ty_s_checks.
    DATA s_state_text      TYPE ty_s_checks.
    " the four steps with inputs, validated="false" in the original until
    " their check passes - bound to each step's `validated`
    DATA cc_valid          TYPE abap_bool.
    DATA cod_valid         TYPE abap_bool.
    DATA inv_valid         TYPE abap_bool.
    DATA del_valid         TYPE abap_bool.
    " the message model: one entry per input that failed its type
    DATA t_messages        TYPE STANDARD TABLE OF ty_s_message WITH EMPTY KEY.
    DATA msg_count         TYPE i.

  PROTECTED SECTION.
    TYPES:
      BEGIN OF ty_s_product,
        productid        TYPE string,
        name             TYPE string,
        category         TYPE string,
        suppliername     TYPE string,
        shortdescription TYPE string,
        pictureurl       TYPE string,
        price            TYPE string,
        currencycode     TYPE string,
        status           TYPE string,
        weight           TYPE string,
        weightunit       TYPE string,
        dimensionwidth   TYPE string,
        dimensiondepth   TYPE string,
        dimensionheight  TYPE string,
        dimensionunit    TYPE string,
      END OF ty_s_product.
    TYPES:
      BEGIN OF ty_s_featured,
        productid TYPE string,
        type      TYPE string,
      END OF ty_s_featured.
    " a route of the original's manifest.json with its arguments - what the
    " URL hash holds there
    TYPES:
      BEGIN OF ty_s_route,
        name     TYPE string,
        category TYPE string,
        product  TYPE string,
        item1    TYPE string,
        item2    TYPE string,
      END OF ty_s_route.

    " What the original's pictureUrl formatter resolves a mock path against.
    "
    " The mock rows carry the original's own spelling, `sap/ui/demo/mock/
    " images/HT-1000.jpg` (data_fidelity compares them against Products.json,
    " so they stay verbatim), and the original resolves it with
    " `sap.ui.require.toUrl( )` against a resource root the app DECLARES:
    " test/testsuite.qunit.js maps `sap/ui/demo/mock` to
    " `./../localService/mockdata`, and its formatter unit test asserts
    " exactly that. So the images are files of the demo app, not assets of
    " the UI5 runtime - `/resources/sap/ui/demo/mock/...`, which is what this
    " constant used to say, is a 404 and left every product without a
    " picture.
    "
    " A port has no app folder to serve them from, so the prefix is replaced
    " with the demo kit's deployed copy of that folder - the same
    " sdk.openui5.org/test-resources host the src/03 collection already uses
    " for sample images.
    CONSTANTS c_mock_prefix TYPE string VALUE `sap/ui/demo/mock/`.
    CONSTANTS c_base        TYPE string
      VALUE `https://sdk.openui5.org/test-resources/sap/m/demokit/cart/webapp/localService/mockdata/`.

    " the welcome carousel's four teaser images are files of the APP, not of
    " the mock: the original resolves `sap/ui/demo/cart/img/...` with
    " `sap.ui.require.toUrl( )` against its own webapp root. Same answer as
    " c_base, one folder up
    CONSTANTS c_img         TYPE string
      VALUE `https://sdk.openui5.org/test-resources/sap/m/demokit/cart/webapp/img/`.
    " Welcome.controller's _iCarouselLoopTime: the carousel moves on after
    " eight seconds - the START_TIMER delay, in milliseconds
    CONSTANTS c_carousel_ms TYPE string VALUE `8000`.

    DATA client          TYPE REF TO z2ui5_if_client.
    DATA t_all           TYPE STANDARD TABLE OF ty_s_product WITH EMPTY KEY.
    DATA t_featured      TYPE STANDARD TABLE OF ty_s_featured WITH EMPTY KEY.
    " the route on show and the ones before it - what the browser history
    " holds there, walked by the Back buttons
    DATA s_route         TYPE ty_s_route.
    DATA t_history       TYPE STANDARD TABLE OF ty_s_route WITH EMPTY KEY.
    " the page each NavContainer shows, re-issued after a rebuilt view
    DATA page_begin      TYPE string VALUE `page-home`.
    DATA page_mid        TYPE string VALUE `page-welcome`.
    DATA page_end        TYPE string VALUE `page-cart`.
    DATA page_wizard     TYPE string VALUE `wizardContentPage`.
    " the carousel page drawn at start (onInit's random setActivePage)
    DATA carousel_page   TYPE string.
    " the term of the last search - read by the search, bound by nothing
    DATA search_term     TYPE string.
    " the comparison model of the original: the category and the two items
    DATA cmp_category    TYPE string.
    DATA cmp_item1       TYPE string.
    DATA cmp_item2       TYPE string.
    " the slider values of the last confirmed filter - the original's
    " _iLowFilterPreviousValue / _iHighFilterPreviousValue
    DATA flt_low_prev    TYPE i.
    DATA flt_high_prev   TYPE i VALUE 5000.
    " the filter the list binding carries since the last confirm - it stays
    " when another category is opened, as a binding's filters do
    DATA flt_status      TYPE string_table.
    DATA flt_supplier    TYPE string_table.
    DATA flt_price       TYPE abap_bool.
    " the out-of-stock product waiting for the OK of its confirmation box
    DATA add_pending     TYPE string.
    " the cart entry waiting for the DELETE of its confirmation box
    DATA delete_pending  TYPE string.
    DATA delete_list     TYPE string.
    " the wizard's progress is past the payment step / the invoice step -
    " set by the activate events of every step behind it, cleared when that
    " progress is discarded. What getProgressStep( ) tells the original
    DATA payment_passed  TYPE abap_bool.
    DATA invoice_passed  TYPE abap_bool.
    " the values before the change a warning asks about: NO puts them back,
    " as the original's _oHistory
    DATA pay_type_prev   TYPE string.
    DATA del_prev        TYPE abap_bool.

    METHODS view_display.
    METHODS page_home
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS page_category
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS page_welcome
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS page_product
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS page_comparison
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS page_cart
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS page_checkout
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS page_summary
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS page_order_completed
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS dialogs
      IMPORTING
        parent TYPE REF TO z2ui5_cl_ui5_view_builder.
    METHODS on_event.
    METHODS on_event_shop
      RETURNING
        VALUE(result) TYPE abap_bool.
    METHODS on_event_cart
      RETURNING
        VALUE(result) TYPE abap_bool.
    METHODS on_event_checkout
      RETURNING
        VALUE(result) TYPE abap_bool.
    METHODS route_to
      IMPORTING
        name     TYPE string
        category TYPE string OPTIONAL
        product  TYPE string OPTIONAL
        item1    TYPE string OPTIONAL
        item2    TYPE string OPTIONAL.
    METHODS route_apply.
    METHODS set_layout
      IMPORTING
        columns TYPE string.
    METHODS nav_to
      IMPORTING
        nav  TYPE string
        page TYPE string.
    METHODS category_load
      IMPORTING
        category  TYPE string
        productid TYPE string OPTIONAL.
    METHODS filter_confirm.
    METHODS filter_match
      IMPORTING
        product       TYPE ty_s_product
      RETURNING
        VALUE(result) TYPE abap_bool.
    METHODS category_rows
      IMPORTING
        category  TYPE string
        productid TYPE string OPTIONAL.
    METHODS product_show
      IMPORTING
        productid TYPE string.
    METHODS product_route
      IMPORTING
        productid TYPE string
        name      TYPE string DEFAULT `product`.
    METHODS category_of
      IMPORTING
        productid     TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS detail_of
      IMPORTING
        productid     TYPE string
      RETURNING
        VALUE(result) TYPE ty_s_detail.
    METHODS comparison_show.
    METHODS cart_add
      IMPORTING
        productid TYPE string.
    METHODS cart_add_request
      IMPORTING
        productid TYPE string.
    METHODS cart_delete.
    METHODS cart_edit_toggle.
    METHODS cart_show_product
      IMPORTING
        productid TYPE string.
    METHODS search_refresh.
    METHODS cart_refresh.
    METHODS storage_json
      RETURNING
        VALUE(result) TYPE string.
    METHODS entries_json
      IMPORTING
        entries       TYPE ty_t_entry
      RETURNING
        VALUE(result) TYPE string.
    METHODS json_escape
      IMPORTING
        val           TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS cart_mirror.
    METHODS cart_restore.
    METHODS entry_of
      IMPORTING
        productid     TYPE string
      RETURNING
        VALUE(result) TYPE ty_s_entry.
    METHODS input_check
      IMPORTING
        field TYPE string.
    METHODS input_error
      IMPORTING
        field         TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS string_error
      IMPORTING
        val           TYPE string
        min           TYPE i
        max           TYPE i OPTIONAL
        regex         TYPE string OPTIONAL
      RETURNING
        VALUE(result) TYPE string.
    METHODS step_valid
      IMPORTING
        fields        TYPE string_table
      RETURNING
        VALUE(result) TYPE abap_bool.
    METHODS steps_check.
    METHODS messages_refresh.
    METHODS messages_clear.
    METHODS pay_type_apply.
    METHODS delivery_apply.
    METHODS wizard_branch.
    METHODS wizard_reset.
    METHODS wizard_to_step
      IMPORTING
        step TYPE string.
    METHODS row_of
      IMPORTING
        product       TYPE ty_s_product
      RETURNING
        VALUE(result) TYPE ty_s_row.
    METHODS status_text
      IMPORTING
        status        TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS status_state
      IMPORTING
        status        TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS price_text
      IMPORTING
        val           TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS picture_url
      IMPORTING
        val           TYPE string
      RETURNING
        VALUE(result) TYPE string.
    METHODS model_init.

  PRIVATE SECTION.
ENDCLASS.


CLASS z2ui5_cl_smpc_demo_004 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.

    " Welcome.onCarouselPageChanged: the carousel's next( ) after eight
    " seconds, as a START_TIMER whose CAROUSEL_TICK comes back here. Every
    " round-trip cancels the pending timer, so every response re-arms it while
    " the carousel is on screen - the mid column's welcome page, not a phone,
    " where the original hides the carousel. `X`: nobody waits on the tick, so
    " it keeps the busy overlay down
    IF page_mid = `page-welcome` AND layout <> `OneColumn`
        AND client->get( )-s_device-system <> client->cs_device-system-phone.
      client->follow_up_action( val   = client->cs_event-start_timer
                                t_arg = VALUE #( ( `CAROUSEL_TICK` ) ( c_carousel_ms ) ( `X` ) ) ).
    ENDIF.

  ENDMETHOD.


  METHOD view_display.

    " the chain hangs off the factory( ), so `view` holds the mvc:View and the
    " statements below add INTO it (view-chain-layout)
    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:l`      v = `sap.ui.layout`
            )->a( n = `xmlns:form`   v = `sap.ui.layout.form`
            )->a( n = `xmlns:core`   v = `sap.ui.core`
            )->a( n = `xmlns:z2ui5`  v = `z2ui5.cc` ).

    " the read half of the original's LocalStorageModel: an invisible control
    " that reads the key and fires `finished` when what it finds differs from
    " the bound value. The write half is the STORE_DATA action in cart_refresh( )
    view->tag( n = `Storage` ns = `z2ui5`
        )->a( n = `type`     v = client->_bind( s_storage-type )
        )->a( n = `prefix`   v = client->_bind( s_storage-prefix )
        )->a( n = `key`      v = client->_bind( s_storage-key )
        )->a( n = `value`    v = client->_bind( s_storage-value )
        )->a( n = `finished` v = client->_event( val    = `CART_LOADED`
                                                 " no arg: the value is BOUND, so it arrives in
                                                 " s_storage-value with this event and the handler
                                                 " reads it there (cart_restore). An event argument
                                                 " would hand the same payload over a second time,
                                                 " as JSON the app would then have to parse.
                                                 "
                                                 " the control reads and fires while the INITIAL view
                                                 " renders - i.e. while that very roundtrip still counts
                                                 " as in flight, which drops an ordinary wire's event
                                                 " (View1.eB busy guard) and with it the whole restore.
                                                 " check_queue_last keeps it and dispatches it once the
                                                 " response has landed
                                                 s_ctrl = VALUE #( check_queue_last = abap_true ) ) ).

    dialogs( view ).

    " App.view.xml: the App and its FlexibleColumnLayout, both with the
    " sapUiDemoCart class the style.css rules are scoped under. stateChange
    " is BaseController.onStateChange - it keeps smallScreenMode and turns a
    " OneColumn layout back into two columns once there is room. The FCL
    " fires it while the initial view renders, which is why the wire queues
    " (check_queue_last), like the storage read above
    DATA(fcl) = view->ele( `App`
        )->a( n = `id`    v = `app`
        )->a( n = `class` v = `sapUiDemoCart`

        )->ele( n = `FlexibleColumnLayout` ns = `f`
            )->a( n = `id`               v = `layout`
            )->a( n = `layout`           v = client->_bind( layout )
            )->a( n = `backgroundDesign` v = `Translucent`
            )->a( n = `class`            v = `sapUiDemoCart`
            )->a( n = `stateChange`      v = client->_event( val    = `STATE_CHANGE`
                                                             t_arg  = VALUE #( ( `${$parameters>/maxColumnsCount}` )
                                                                               ( `${$parameters>/layout}` ) )
                                                             s_ctrl = VALUE #( check_queue_last = abap_true ) ) ).

    " the router's targets, column by column (manifest.json): home, category,
    " checkout and ordercompleted go to the begin column, welcome, product
    " and comparison to the mid column, the cart to the end column
    DATA(nav_begin) = fcl->ele( n = `beginColumnPages` ns = `f`
        )->ele( `NavContainer`
            )->a( n = `id` v = `nav-begin` ).
    page_home( nav_begin ).
    page_category( nav_begin ).
    page_checkout( nav_begin ).
    page_order_completed( nav_begin ).

    DATA(nav_mid) = fcl->ele( n = `midColumnPages` ns = `f`
        )->ele( `NavContainer`
            )->a( n = `id` v = `nav-mid` ).
    page_welcome( nav_mid ).
    page_product( nav_mid ).
    page_comparison( nav_mid ).

    DATA(nav_end) = fcl->ele( n = `endColumnPages` ns = `f`
        )->ele( `NavContainer`
            )->a( n = `id` v = `nav-end` ).
    page_cart( nav_end ).

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

    " a rebuilt NavContainer starts on its first page while the page a column
    " should show survives as class state - re-issue all of them
    nav_to( nav = `nav-begin`          page = page_begin ).
    nav_to( nav = `nav-mid`            page = page_mid ).
    nav_to( nav = `nav-end`            page = page_end ).
    nav_to( nav = `wizardNavContainer` page = page_wizard ).

    " the same for the wizard's branches: nextStep is an association no
    " binding can carry, and XMLView.create has just rebuilt the steps, so the
    " payment branch and the delivery-address branch are re-issued here or
    " they are gone after any redisplay (sample z2ui5_cl_smp_app_202, and the
    " linter's control-state-lost-on-rebuild)
    wizard_branch( ).

    " Welcome.controller.onInit: the carousel opens on a page drawn at random
    " (model_init draws it, so a rebuilt view opens on the same one)
    IF carousel_page IS NOT INITIAL.
      client->follow_up_action( val   = client->cs_event-control_by_id
                                t_arg = VALUE #( ( `welcomeCarousel` ) ( `setActivePage` ) ( carousel_page ) ) ).
    ENDIF.

  ENDMETHOD.


  METHOD dialogs.

    " the two popups the original creates in its controllers - the category
    " filter (CategoryFilterDialog.fragment.xml) and the checkout's
    " MessagePopover - held in the view's dependents and opened by id from a
    " view-wired control call, so opening either costs no round-trip
    DATA(dependents) = parent->ele( n = `dependents` ns = `mvc` ).

    " every selection of the dialog is two-way bound: the ViewSettingsItems'
    " `selected`, the RangeSlider's two values and the custom item's
    " filterCount. The dialog restores its items itself on cancel, so what
    " arrives with confirm, cancel and reset is what it shows
    dependents->ele( `ViewSettingsDialog`
        )->a( n = `id`           v = `categoryFilterDialog`
        )->a( n = `confirm`      v = client->_event( `FILTER_CONFIRM` )
        )->a( n = `cancel`       v = client->_event( `FILTER_CANCEL` )
        )->a( n = `resetFilters` v = client->_event( `FILTER_RESET` )

        )->ele( `filterItems`
            )->ele( `ViewSettingsFilterItem`
                )->a( n = `text` v = `Availability`
                )->a( n = `key`  v = `availabilityKey`

                )->ele( `items`
                    )->tag( `ViewSettingsItem`
                        )->a( n = `text`     v = `Available`
                        )->a( n = `key`      v = `Available`
                        )->a( n = `selected` v = client->_bind( flt_available )
                    )->tag( `ViewSettingsItem`
                        )->a( n = `text`     v = `Out of Stock`
                        )->a( n = `key`      v = `OutOfStock`
                        )->a( n = `selected` v = client->_bind( flt_out_of_stock )
                    )->tag( `ViewSettingsItem`
                        )->a( n = `text`     v = `Discontinued`
                        )->a( n = `key`      v = `Discontinued`
                        )->a( n = `selected` v = client->_bind( flt_discontinued )

                )->end(
            )->end(
            )->ele( `ViewSettingsCustomItem`
                )->a( n = `text`        v = `Price`
                )->a( n = `key`         v = `Price`
                )->a( n = `filterCount` v = client->_bind( flt_price_count )

                )->ele( `customControl`
                    )->ele( n = `VerticalLayout` ns = `l`
                        )->a( n = `width` v = `100%`
                        )->a( n = `class` v = `sapUiContentPadding`

                        )->tag( `RangeSlider`
                            )->a( n = `id`     v = `rangeSlider`
                            )->a( n = `width`  v = `100%`
                            )->a( n = `value`  v = client->_bind( flt_low )
                            )->a( n = `value2` v = client->_bind( flt_high )
                            )->a( n = `class`  v = `sapUiSmallMarginTop`
                            )->a( n = `max`    v = `5000`
                            )->a( n = `step`   v = `10`
                            )->a( n = `change` v = client->_event( val   = `FILTER_CHANGE`
                                                                   t_arg = VALUE #( ( `${$parameters>/range}[0]` )
                                                                                    ( `${$parameters>/range}[1]` ) ) )

                    )->end(
                )->end(
            )->end(
            )->ele( `ViewSettingsFilterItem`
                )->a( n = `text`  v = `Supplier`
                )->a( n = `key`   v = `supplierKey`
                )->a( n = `items` v = client->_bind( t_suppliers )

                )->ele( `items`
                    )->tag( `ViewSettingsItem`
                        )->a( n = `text`     v = `{SUPPLIERNAME}`
                        )->a( n = `key`      v = `{SUPPLIERNAME}`
                        )->a( n = `selected` v = `{SELECTED}` ).

    " Checkout.controller.onShowMessagePopoverPress: the message model's
    " entries, each with the original's "Show more information" link
    dependents->ele( `MessagePopover`
        )->a( n = `id`    v = `messagePopover`
        )->a( n = `items` v = client->_bind( t_messages )

        )->ele( `items`
            )->ele( `MessageItem`
                )->a( n = `type`     v = `{TYPE}`
                )->a( n = `title`    v = `{MESSAGE}`
                )->a( n = `subtitle` v = `{ADDITIONALTEXT}`

                )->ele( `link`
                    )->tag( `Link`
                        )->a( n = `text`   v = `Show more information`
                        )->a( n = `href`   v = `http://sap.com`
                        )->a( n = `target` v = `_blank` ).

  ENDMETHOD.


  METHOD page_home.

    DATA(page) = parent->ele( `Page`
        )->a( n = `id`               v = `page-home`
        )->a( n = `title`            v = `Product Catalog`
        )->a( n = `backgroundDesign` v = `Solid` ).

    page->ele( `landmarkInfo`
        )->tag( `PageAccessibleLandmarkInfo`
            )->a( n = `rootRole`       v = `Region`
            )->a( n = `rootLabel`      v = `Product Catalog Search and Navigation`
            )->a( n = `subHeaderRole`  v = `Search`
            )->a( n = `subHeaderLabel` v = `Products`
            )->a( n = `contentRole`    v = `Navigation`
            )->a( n = `contentLabel`   v = `List Of Categories`
            )->a( n = `headerRole`     v = `Region`
            )->a( n = `headerLabel`    v = `Home Header` ).

    page->ele( `headerContent`
        )->tag( `Button`
            )->a( n = `icon`    v = `sap-icon://home`
            )->a( n = `press`   v = client->_event( `HOME` )
            )->a( n = `visible` v = client->_bind( small_screen ) ).

    " the search runs on every keystroke (liveChange), as the original's
    " does. The text travels as the event's argument and the field's value is
    " NOT bound: a bound value would come back with every response and
    " overwrite what was typed while the round-trip was on its way. The queue
    " keeps the last keystroke when they come faster than the round-trips
    page->ele( `subHeader`
        )->ele( `Toolbar`
            )->a( n = `id` v = `searchBar33343`

            )->tag( `SearchField`
                )->a( n = `id`          v = `searchField`
                )->a( n = `liveChange`  v = client->_event( val    = `SEARCH`
                                                            arg    = `${$parameters>/newValue}`
                                                            s_ctrl = VALUE #( check_queue_last = abap_true
                                                                              check_no_busy    = abap_true ) )
                )->a( n = `placeholder` v = `Search`
                )->a( n = `tooltip`     v = `Search`
                )->a( n = `width`       v = `100%` ).

    DATA(content) = page->ele( `content` ).

    content->tag( `PullToRefresh`
        )->a( n = `id`      v = `pullToRefresh`
        )->a( n = `visible` v = `{device>/support/touch}`
        )->a( n = `refresh` v = client->_event( `SEARCH_REFRESH` ) ).

    " the search results. On a desktop the list is SingleSelectMaster and a
    " click SELECTS the row - ListItemBase.ontap takes the
    " isIncludedIntoSelection( ) branch and never fires the item's press - so
    " the navigation hangs off the list's selectionChange; on a phone the
    " list has no mode and the item's press carries it, as there
    content->ele( `List`
        )->a( n = `id`                 v = `productList`
        )->a( n = `visible`            v = client->_bind( search_visible )
        )->a( n = `mode`               v = `{= ${device>/system/phone} ? 'None' : 'SingleSelectMaster'}`
        )->a( n = `selectionChange`    v = client->_event( val = `PRODUCT_SELECT`
                                                           arg = `${$parameters>/listItem}.getBindingContext().getProperty('PRODUCTID')` )
        )->a( n = `noDataText`         v = `No products found`
        )->a( n = `busyIndicatorDelay` v = `0`
        )->a( n = `items`              v = client->_bind( t_search )

        )->ele( `items`
            )->ele( `ObjectListItem`
                )->a( n = `type`             v = `{= ${device>/system/phone} ? 'Active' : 'Inactive'}`
                )->a( n = `icon`             v = `{PICTUREURL}`
                )->a( n = `title`            v = `{NAME}`
                )->a( n = `number`           v = `{PRICE_TEXT}`
                )->a( n = `numberUnit`       v = `EUR`
                )->a( n = `press`            v = client->_event( val = `PRODUCT` arg = `${PRODUCTID}` )
                )->a( n = `iconDensityAware` b = abap_false

                )->ele( `attributes`
                    )->tag( `ObjectAttribute`
                        )->a( n = `text` v = `{SUPPLIERNAME}`

                )->end(

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

More in sap.m

Search every abap2UI5 sample · the full list on one page

On this page