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
- sap.f.FlexibleColumnLayout
- sap.f.semantic.CloseAction
- sap.f.semantic.ExitFullScreenAction
- sap.f.semantic.FullScreenAction
- sap.f.semantic.SemanticPage
- sap.f.semantic.SendEmailAction
- sap.m.App
- sap.m.Button
- sap.m.Column
- sap.m.ColumnListItem
- sap.m.IconTabBar
- sap.m.IconTabFilter
- sap.m.Image
- sap.m.Label
- sap.m.Link
- sap.m.List
- sap.m.MessagePage
- sap.m.ObjectAttribute
- sap.m.ObjectIdentifier
- sap.m.ObjectListItem
- sap.m.ObjectNumber
- sap.m.ObjectStatus
- sap.m.OverflowToolbar
- sap.m.OverflowToolbarLayoutData
- sap.m.Page
- sap.m.SearchField
- sap.m.Table
- sap.m.Text
- sap.m.Title
- sap.m.Toolbar
- sap.m.ToolbarSpacer
- sap.m.VBox
- sap.m.ViewSettingsDialog
- sap.m.ViewSettingsFilterItem
- sap.m.ViewSettingsItem
- sap.ui.core.Title
- sap.ui.core.mvc.View
- sap.ui.layout.HorizontalLayout
- sap.ui.layout.VerticalLayout
- sap.ui.layout.form.SimpleForm
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
Click a number to link to a line — shift-click for a range.
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
- 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…
- Team Calendar — Calendar demo app for team and team members. - the UI5 demo app "Team Calendar", rebuilt…
- Shopping Cart — The classic business process of finding and ordering products. - the UI5 demo app "Shoppi…
- Bulletin Board — Worklist app created with the 'Testing' tutorial. - the UI5 demo app "Bulletin Board", re…
On this page