Smart Link Popover with getLinks
a list of products whose ids are smart links - a press asks the launchpad which apps the user may open for the product and lists them in a popover
- Class
z2ui5_cl_smps_app_026- Repository
- abap2UI5/samples-stack
- UI5 libraries
- sap.m, sap.ui.core, sap.ui.layout
- Lowest UI5 release
- 1.71
- In the browser
- Needs a system - opens in the playground for reading
- Controls it builds
- sap.m.Column, sap.m.ColumnListItem, sap.m.Input, sap.m.Label, sap.m.Link, sap.m.List, sap.m.MessageStrip, sap.m.Page, sap.m.Popover, sap.m.Shell, sap.m.StandardListItem, sap.m.Table, sap.m.Text, sap.m.Title, sap.m.Toolbar, sap.ui.core.FragmentDefinition, sap.ui.core.mvc.View, sap.ui.layout.form.SimpleForm
The class
" @keywords launchpad fiori flp smart link popover getlinks intent semantic object cross_app_nav_get_links
" @summary a list of products whose ids are smart links - a press asks the launchpad which apps the user may open for the product and lists them in a popover
"! <p class="shorttext synchronized">Launchpad - Smart Link Popover with getLinks</p>
"! A Fiori elements list shows a key as a smart link: a press opens a
"! popover with every app the user may open for the object, each called
"! with the row's keys. Which apps those are depends on the user's roles
"! and catalogs, so only the launchpad knows - the backend asks it with
"! the frontend action cs_event-cross_app_nav_get_links:
"!
"! client->follow_up_action(
"! val = client->cs_event-cross_app_nav_get_links
"! t_arg = VALUE #( ( `{"semanticObject":"Z2UI5_CL_LP_SAMPLE_04"}` )
"! ( `{"PRODUCT":"102343333","QUANTITY":"500"}` )
"! ( `LINKS_READ` ) ) ).
"!
"! The answer arrives as the event LINKS_READ, its argument a JSON array
"! with one object per intent - intent (the hash), text, subTitle. The
"! sample lists them in a popover next to the pressed link, and a press on
"! an entry navigates there with cs_event-cross_app_nav_to_ext and the
"! hash as shellHash target.
"!
"! Needs abap2UI5 newer than 1.148.0 (cross_app_nav_get_links) and a
"! launchpad. The default semantic object is the one of the receiver
"! z2ui5_cl_smps_app_484, so the target mapping the package README sets up
"! for it is all the sample needs - the popover then lists the receiver,
"! which shows the product and quantity of the pressed row.
CLASS z2ui5_cl_smps_app_026 DEFINITION PUBLIC.
PUBLIC SECTION.
INTERFACES z2ui5_if_app.
TYPES:
BEGIN OF ty_s_product,
product TYPE string,
name TYPE string,
quantity TYPE string,
END OF ty_s_product.
TYPES ty_t_product TYPE STANDARD TABLE OF ty_s_product WITH EMPTY KEY.
TYPES:
BEGIN OF ty_s_link,
intent TYPE string,
text TYPE string,
sub_title TYPE string,
END OF ty_s_link.
TYPES ty_t_link TYPE STANDARD TABLE OF ty_s_link WITH EMPTY KEY.
DATA semantic_object TYPE string.
DATA t_products TYPE ty_t_product.
"! the intents of the last answer
DATA t_links TYPE ty_t_link.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
"! the row the intents were asked for
DATA s_product TYPE ty_s_product.
"! the id of the pressed link - the popover opens next to it
DATA anchor TYPE string.
METHODS on_event.
METHODS on_event_links.
METHODS on_event_links_read.
METHODS on_event_navigate.
METHODS view_display.
METHODS popover_display.
PRIVATE SECTION.
"! reads the string value behind marker, searching from offset - which
"! is moved behind the value, or set to -1 when there is none
METHODS json_read_string
IMPORTING
json TYPE string
marker TYPE string
EXPORTING
result TYPE string
CHANGING
offset TYPE i.
METHODS json_escape
IMPORTING
val TYPE string
RETURNING
VALUE(result) TYPE string.
ENDCLASS.
CLASS z2ui5_cl_smps_app_026 IMPLEMENTATION.
METHOD z2ui5_if_app~main.
me->client = client.
IF client->check_on_init( ).
semantic_object = `Z2UI5_CL_LP_SAMPLE_04`.
t_products = VALUE #( ( product = `102343333` name = `Notebook Basic 15` quantity = `500` )
( product = `102343334` name = `Notebook Basic 17` quantity = `25` )
( product = `102343335` name = `Ergo Screen E-I` quantity = `3` ) ).
IF client->get( )-check_launchpad_active = abap_false.
client->message_box_display( `No Launchpad Active, Sample not working!` ).
ENDIF.
view_display( ).
ELSEIF client->check_on_navigated( ).
view_display( ).
ELSEIF client->check_on_event( ).
on_event( ).
ENDIF.
ENDMETHOD.
METHOD on_event.
CASE client->get_event( ).
WHEN `LINKS`.
on_event_links( ).
WHEN `LINKS_READ`.
" raised by the frontend once getLinks has answered - named in the
" third argument of cross_app_nav_get_links in on_event_links( )
on_event_links_read( ).
WHEN `NAVIGATE`.
on_event_navigate( ).
ENDCASE.
ENDMETHOD.
METHOD on_event_links.
" the pressed link and its row - the popover opens next to the link
" once the launchpad has answered, in the roundtrip of LINKS_READ
anchor = client->get_event_arg( ).
s_product = VALUE #( product = client->get_event_arg( 2 )
name = client->get_event_arg( 3 )
quantity = client->get_event_arg( 4 ) ).
" target, intent parameters, the event that receives the answer - the
" parameters are the keys the target mappings expect, so an intent that
" needs one the row does not have is not offered
client->follow_up_action(
val = client->cs_event-cross_app_nav_get_links
t_arg = VALUE #(
( |\{"semanticObject":"{ json_escape( semantic_object ) }"\}| )
( |\{"PRODUCT":"{ json_escape( s_product-product ) }",| &&
|"QUANTITY":"{ json_escape( s_product-quantity ) }"\}| )
( `LINKS_READ` ) ) ).
ENDMETHOD.
METHOD on_event_links_read.
" [{"intent":"#SO-action?PRODUCT=...","text":"...","subTitle":"...",
" "semanticObject":"SO","action":"action","params":{...}}, ...]
" - written by the framework, one object per intent and intent always
" its first key, so the walk takes the three strings it shows in order
" and leaves the split parts alone
DATA(json) = client->get_event_arg( ).
DATA(offset) = 0.
DATA s_link TYPE ty_s_link.
CLEAR t_links.
DO.
CLEAR s_link.
json_read_string( EXPORTING json = json
marker = `{"intent":"`
IMPORTING result = s_link-intent
CHANGING offset = offset ).
IF offset < 0.
EXIT.
ENDIF.
json_read_string( EXPORTING json = json
marker = `"text":"`
IMPORTING result = s_link-text
CHANGING offset = offset ).
IF offset < 0.
EXIT.
ENDIF.
json_read_string( EXPORTING json = json
marker = `"subTitle":"`
IMPORTING result = s_link-sub_title
CHANGING offset = offset ).
IF offset < 0.
EXIT.
ENDIF.
INSERT s_link INTO TABLE t_links.
ENDDO.
popover_display( ).
ENDMETHOD.
METHOD on_event_navigate.
" the hash exactly as getLinks returned it - hrefForExternal takes a
" shellHash target as it is, parameters included
DATA(intent) = client->get_event_arg( ).
client->popover_destroy( ).
client->follow_up_action(
val = client->cs_event-cross_app_nav_to_ext
t_arg = VALUE #( ( |\{"shellHash":"{ json_escape( intent ) }"\}| ) ) ).
ENDMETHOD.
METHOD json_read_string.
CLEAR result.
DATA(start) = find( val = json sub = marker off = offset ).
IF start < 0.
offset = -1.
RETURN.
ENDIF.
DATA(pos) = start + strlen( marker ).
DATA(length) = strlen( json ).
" walked rather than cut at the next quote: a quote inside the value
" stands in the JSON as \" and would end it early
WHILE pos < length.
DATA(char) = substring( val = json off = pos len = 1 ).
pos = pos + 1.
IF char = `"`.
offset = pos.
RETURN.
ENDIF.
IF char <> `\`.
result = result && char.
CONTINUE.
ENDIF.
" an escape - a backslash at the very end is a truncated payload
IF pos >= length.
EXIT.
ENDIF.
DATA(escaped) = substring( val = json off = pos len = 1 ).
pos = pos + 1.
CASE escaped.
WHEN `n`.
result = result && |\n|.
WHEN `t`.
result = result && |\t|.
WHEN OTHERS.
" `"`, `\` and `/` stand for themselves
result = result && escaped.
ENDCASE.
ENDWHILE.
" no closing quote
offset = -1.
ENDMETHOD.
METHOD json_escape.
result = replace( val = val sub = `\` with = `\\` occ = 0 ).
result = replace( val = result sub = `"` with = `\"` occ = 0 ).
ENDMETHOD.
METHOD view_display.
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:form` v = `sap.ui.layout.form` ).
DATA(page) = view->ele( `Shell`
)->ele( `Page`
)->a( n = `title` v = `abap2UI5 - Launchpad - Smart Link Popover with getLinks`
)->a( n = `showNavButton` b = client->check_app_prev_stack( )
)->a( n = `navButtonPress` v = client->_event_nav_app_leave( )
)->a( n = `showHeader` b = xsdbool( client->get( )-check_launchpad_active = abap_false ) ).
page->tag( `MessageStrip`
)->a( n = `text` v = `Press a product: follow_up_action( cs_event-cross_app_nav_get_links ) asks the ` &&
`launchpad which apps the user may open for the semantic object with the row's ` &&
`PRODUCT and QUANTITY, and the popover lists what it answers - a press on an entry ` &&
`navigates there with cs_event-cross_app_nav_to_ext. Only works inside a launchpad; ` &&
`with the target mapping of z2ui5_cl_smps_app_484 the receiver is in the list.`
)->a( n = `type` v = `Information`
)->a( n = `showIcon` b = abap_true
)->a( n = `class` v = `sapUiSmallMargin` ).
page->ele( n = `SimpleForm` ns = `form`
)->a( n = `title` v = `The links of`
)->a( n = `editable` b = abap_true
)->ele( n = `content` ns = `form`
)->tag( `Label`
)->a( n = `text` v = `Semantic Object`
)->tag( `Input`
)->a( n = `value` v = client->_bind( semantic_object ) ).
DATA(table) = page->ele( `Table`
)->a( n = `items` v = client->_bind( t_products ) ).
table->ele( `headerToolbar`
)->ele( `Toolbar`
)->tag( `Title`
)->a( n = `text` v = `Products` ).
table->ele( `columns`
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Product`
)->end(
)->ele( `Column`
)->tag( `Text`
)->a( n = `text` v = `Name`
)->end(
)->ele( `Column`
)->a( n = `hAlign` v = `End`
)->tag( `Text`
)->a( n = `text` v = `Quantity` ).
table->ele( `items`
)->ele( `ColumnListItem`
)->ele( `cells`
)->tag( `Link`
)->a( n = `text` v = `{PRODUCT}`
)->a( n = `press` v = client->_event( val = `LINKS`
t_arg = VALUE #( ( `${$source>/id}` )
( `${PRODUCT}` )
( `${NAME}` )
( `${QUANTITY}` ) ) )
)->tag( `Text`
)->a( n = `text` v = `{NAME}`
)->tag( `Text`
)->a( n = `text` v = `{QUANTITY}` ).
client->view_display( view->stringify( ) ).
ENDMETHOD.
METHOD popover_display.
DATA(popover) = z2ui5_cl_ui5_view_builder=>factory(
)->ele( n = `FragmentDefinition` ns = `core`
)->a( n = `xmlns` v = `sap.m`
)->a( n = `xmlns:core` v = `sap.ui.core` ).
DATA(list) = popover->ele( `Popover`
)->a( n = `title` t = |{ s_product-name } ({ s_product-product })|
)->a( n = `placement` v = `Auto`
)->a( n = `contentWidth` v = `20rem`
)->ele( `List`
)->a( n = `items` v = client->_bind( t_links )
)->a( n = `noDataText` t = |The launchpad offers you no app for { semantic_object }.| ).
list->ele( `StandardListItem`
)->a( n = `title` v = `{TEXT}`
)->a( n = `description` v = `{SUB_TITLE}`
)->a( n = `type` v = `Navigation`
)->a( n = `press` v = client->_event( val = `NAVIGATE`
arg = `${INTENT}` ) ).
client->popover_display( xml = popover->stringify( ) by_id = anchor ).
ENDMETHOD.
ENDCLASS.