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
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
- sap.f.FlexibleColumnLayout
- sap.m.App
- sap.m.Bar
- sap.m.Button
- sap.m.Carousel
- sap.m.CheckBox
- sap.m.DatePicker
- sap.m.FlexBox
- sap.m.FormattedText
- sap.m.HBox
- sap.m.Image
- sap.m.Input
- sap.m.Label
- sap.m.LightBox
- sap.m.LightBoxItem
- sap.m.Link
- sap.m.List
- sap.m.MaskInput
- sap.m.MaskInputRule
- sap.m.MessageItem
- sap.m.MessagePopover
- sap.m.NavContainer
- sap.m.ObjectAttribute
- sap.m.ObjectHeader
- sap.m.ObjectIdentifier
- sap.m.ObjectListItem
- sap.m.ObjectStatus
- sap.m.Page
- sap.m.PageAccessibleLandmarkInfo
- sap.m.Panel
- sap.m.PullToRefresh
- sap.m.RangeSlider
- sap.m.ScrollContainer
- sap.m.SearchField
- sap.m.SegmentedButton
- sap.m.SegmentedButtonItem
- sap.m.StandardListItem
- sap.m.Text
- sap.m.TextArea
- sap.m.Title
- sap.m.ToggleButton
- sap.m.Toolbar
- sap.m.ToolbarSpacer
- sap.m.VBox
- sap.m.ViewSettingsCustomItem
- sap.m.ViewSettingsDialog
- sap.m.ViewSettingsFilterItem
- sap.m.ViewSettingsItem
- sap.m.Wizard
- sap.m.WizardStep
- sap.ui.core.HTML
- sap.ui.core.mvc.View
- sap.ui.layout.BlockLayout
- sap.ui.layout.BlockLayoutCell
- sap.ui.layout.BlockLayoutRow
- sap.ui.layout.Grid
- sap.ui.layout.GridData
- sap.ui.layout.VerticalLayout
- sap.ui.layout.form.Form
- sap.ui.layout.form.FormContainer
- sap.ui.layout.form.FormElement
- sap.ui.layout.form.ResponsiveGridLayout
- sap.ui.layout.form.SimpleForm
- z2ui5.cc.Storage
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
Nothing is installed and nothing is sent anywhere: the ABAP is compiled to JavaScript in this browser and runs against abap2UI5 itself.
The ABAP
" @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
- Combo box with value state — The combo box can show different value states.
- Icon Tab Bar - Background Design — In this example the backgroundDesign and headerBackgroundDesign properties are demonstrat…
- Icon Tab Bar - Process — In this example, the Icon Tab Bar is used to apply filters on the same content along a bu…
- Icon Tab Bar - Responsive Padding — This example demonstrates the Icon Tab Bar's support for paddings that depend on the widt…
- Icon Tab Bar - Tab Density Mode — In this example, the Icon Tab Bar is used in different tab density modes.
- Input - Assisted Tabular Suggestions — In this example assisted input is provided with table-like suggestions where several colu…
- Input - Checked — Input checks are handled via the validation of the data binding. In this example there ar…
- Input - States — This example demonstrates the different input field states, e.g. disabled, editable, with…
- Manage Products — Created with the 'Worklist App' tutorial. - the UI5 demo app "Manage Products", rebuilt a…
- Browse Orders — Master-detail app for browsing orders. - the UI5 demo app "Browse Orders", rebuilt as one…
- Team Calendar — Calendar demo app for team and team members. - the UI5 demo app "Team Calendar", rebuilt…
- Bulletin Board — Worklist app created with the 'Testing' tutorial. - the UI5 demo app "Bulletin Board", re…