From 52d1d57e44fcecf221c0ac88ea7cb4d4b019ba68 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 17:35:34 +0000 Subject: [PATCH] Add four samples for the new frontend capabilities One sample per capability added to the framework (abap2UI5 PR "implement the five open ai-demokit framework requests"), all in Basic II (src/01/02) and marked (A) as frontend-action demos: - 471 Keyboard Shortcuts: Ctrl+S / Ctrl+D fire the backend events SAVE and DELETE via cs_event-keyboard_shortcut; a button registers and unregisters them again, a list logs what was triggered. - 472 Link with preventDefault: a sap.m.Link whose press is registered with s_ctrl-check_prevent_default, so the browser does not follow the href and the backend decides instead. A switch rebuilds the view with the flag off to compare both behaviours. - 473 Menu Item Path: a nested sap.m.Menu whose itemSelected toasts the full breadcrumb of the selected item ("Create New Site > Official Store"), resolved client-side by $controller.textPath( ) - no roundtrip. - 474 MessagePopover URL Policy: messages carrying an in-app and an external link; the popover is opened with RELATIVE_ONLY, ALLOW_ALL or DENY_ALL, the frontend installs the matching setAsyncURLHandler validator. Overview catalog regenerated (npm run launchpad). Note: these samples use framework API that is not on abap2UI5 main yet, so the abaplint gates stay red until that PR is merged (and, for ABAP_702, until the 702 branch is regenerated). Verified locally against the framework branch: ABAP_STANDARD, ABAP_CLOUD and the 702 downport all report 0 issues. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Amu2PTNKeQLzUJysdJUBUa --- src/01/02/z2ui5_cl_demo_app_471.clas.abap | 157 ++++++++++++++++++++++ src/01/02/z2ui5_cl_demo_app_471.clas.xml | 16 +++ src/01/02/z2ui5_cl_demo_app_472.clas.abap | 102 ++++++++++++++ src/01/02/z2ui5_cl_demo_app_472.clas.xml | 16 +++ src/01/02/z2ui5_cl_demo_app_473.clas.abap | 80 +++++++++++ src/01/02/z2ui5_cl_demo_app_473.clas.xml | 16 +++ src/01/02/z2ui5_cl_demo_app_474.clas.abap | 132 ++++++++++++++++++ src/01/02/z2ui5_cl_demo_app_474.clas.xml | 16 +++ src/01/z2ui5_cl_demo_app_g00.clas.abap | 4 + 9 files changed, 539 insertions(+) create mode 100644 src/01/02/z2ui5_cl_demo_app_471.clas.abap create mode 100644 src/01/02/z2ui5_cl_demo_app_471.clas.xml create mode 100644 src/01/02/z2ui5_cl_demo_app_472.clas.abap create mode 100644 src/01/02/z2ui5_cl_demo_app_472.clas.xml create mode 100644 src/01/02/z2ui5_cl_demo_app_473.clas.abap create mode 100644 src/01/02/z2ui5_cl_demo_app_473.clas.xml create mode 100644 src/01/02/z2ui5_cl_demo_app_474.clas.abap create mode 100644 src/01/02/z2ui5_cl_demo_app_474.clas.xml diff --git a/src/01/02/z2ui5_cl_demo_app_471.clas.abap b/src/01/02/z2ui5_cl_demo_app_471.clas.abap new file mode 100644 index 00000000..3a20036b --- /dev/null +++ b/src/01/02/z2ui5_cl_demo_app_471.clas.abap @@ -0,0 +1,157 @@ +CLASS z2ui5_cl_demo_app_471 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + TYPES: + BEGIN OF ty_s_log, + entry TYPE string, + END OF ty_s_log. + DATA t_log TYPE STANDARD TABLE OF ty_s_log WITH EMPTY KEY. + DATA registered TYPE abap_bool. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_init. + METHODS on_event. + METHODS view_display. + METHODS shortcuts_set + IMPORTING + event_save TYPE string + event_delete TYPE string. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_demo_app_471 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + on_init( ). + ELSEIF client->check_on_navigated( ). + view_display( ). + ELSEIF client->check_on_event( ). + on_event( ). + ENDIF. + + ENDMETHOD. + + + METHOD on_init. + + registered = abap_true. + shortcuts_set( event_save = `SAVE` + event_delete = `DELETE` ). + view_display( ). + + ENDMETHOD. + + + METHOD on_event. + + CASE client->get( )-event. + + WHEN `SAVE`. + INSERT VALUE #( entry = `Ctrl+S - save triggered` ) INTO TABLE t_log. + client->message_toast_display( `Ctrl+S: save triggered` ). + client->view_model_update( ). + + WHEN `DELETE`. + INSERT VALUE #( entry = `Ctrl+D - delete triggered` ) INTO TABLE t_log. + client->message_toast_display( `Ctrl+D: delete triggered` ). + client->view_model_update( ). + + WHEN `TOGGLE_REGISTRATION`. + registered = xsdbool( registered = abap_false ). + + IF registered = abap_true. + shortcuts_set( event_save = `SAVE` + event_delete = `DELETE` ). + ELSE. + " an empty event name removes the binding again + shortcuts_set( event_save = `` + event_delete = `` ). + ENDIF. + view_display( ). + + WHEN `CLEAR`. + t_log = VALUE #( ). + client->view_model_update( ). + + ENDCASE. + + ENDMETHOD. + + + METHOD shortcuts_set. + + " one registration per key combination: t_arg is positional - the + " combination (spelled like in UI5: Ctrl+S, Ctrl+Shift+D, F2) and the name + " of the backend event it fires. Registering the same combination again + " rebinds it, an empty event name removes it. The registry lives in the + " frontend, so it survives every roundtrip until the app is left. + client->follow_up_action( val = z2ui5_if_client=>cs_event-keyboard_shortcut + t_arg = VALUE #( ( `Ctrl+S` ) + ( event_save ) ) ). + + client->follow_up_action( val = z2ui5_if_client=>cs_event-keyboard_shortcut + t_arg = VALUE #( ( `Ctrl+D` ) + ( event_delete ) ) ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_xml_view=>factory( ). + + DATA(page) = view->shell( + )->page( + title = `abap2UI5 - Keyboard Shortcuts` + navbuttonpress = client->_event_nav_app_leave( ) + shownavbutton = client->check_app_prev_stack( ) ). + + page->message_strip( + text = `Ctrl+S and Ctrl+D fire the backend events SAVE and DELETE - the same events the ` && + `buttons below send, but from the keyboard and without a control. The binding is ` && + `pure data (cs_event-keyboard_shortcut with the combination and the event name), ` && + `the browser default for the combination is suppressed.` + type = `Information` + showicon = abap_true + class = `sapUiSmallMargin` ). + + page->hbox( class = `sapUiSmallMargin` + )->button( + text = COND #( WHEN registered = abap_true + THEN `Unregister the shortcuts` + ELSE `Register the shortcuts` ) + icon = `sap-icon://keyboard-and-mouse` + press = client->_event( `TOGGLE_REGISTRATION` ) + )->button( + text = `Save (Ctrl+S)` + type = `Emphasized` + class = `sapUiTinyMarginBegin` + press = client->_event( `SAVE` ) + )->button( + text = `Delete (Ctrl+D)` + class = `sapUiTinyMarginBegin` + press = client->_event( `DELETE` ) + )->button( + text = `Clear log` + class = `sapUiTinyMarginBegin` + press = client->_event( `CLEAR` ) ). + + page->list( + headertext = `Triggered events` + items = client->_bind_edit( t_log ) + )->standard_list_item( title = `{ENTRY}` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/02/z2ui5_cl_demo_app_471.clas.xml b/src/01/02/z2ui5_cl_demo_app_471.clas.xml new file mode 100644 index 00000000..277b7bd2 --- /dev/null +++ b/src/01/02/z2ui5_cl_demo_app_471.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_DEMO_APP_471 + E + More - Keyboard Shortcuts (A) + 1 + X + X + X + + + + diff --git a/src/01/02/z2ui5_cl_demo_app_472.clas.abap b/src/01/02/z2ui5_cl_demo_app_472.clas.abap new file mode 100644 index 00000000..131b5038 --- /dev/null +++ b/src/01/02/z2ui5_cl_demo_app_472.clas.abap @@ -0,0 +1,102 @@ +CLASS z2ui5_cl_demo_app_472 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + DATA block_navigation TYPE abap_bool. + DATA last_press TYPE string. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_event. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_demo_app_472 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + block_navigation = abap_true. + 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 `LINK_PRESS`. + + IF block_navigation = abap_true. + last_press = `Link pressed - the browser did NOT follow the href, the backend decides what happens.`. + ELSE. + last_press = `Link pressed - the href was followed by the browser as usual.`. + ENDIF. + client->view_model_update( ). + + WHEN `TOGGLE`. + " the flag is part of the event registration, so the view has to be + " rebuilt for the change to reach the frontend + view_display( ). + + ENDCASE. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_xml_view=>factory( ). + + DATA(page) = view->shell( + )->page( + title = `abap2UI5 - Event with preventDefault` + navbuttonpress = client->_event_nav_app_leave( ) + shownavbutton = client->check_app_prev_stack( ) ). + + page->message_strip( + text = `A sap.m.Link normally follows its href when pressed. Registered with ` && + `s_ctrl-check_prevent_default the event cancels that built-in default ` && + `(oEvent.preventDefault()) before the roundtrip - the event still reaches the ` && + `backend, so the app decides what happens instead. Flip the switch to compare.` + type = `Information` + showicon = abap_true + class = `sapUiSmallMargin` ). + + DATA(form) = page->simple_form( + title = `Link with a cancelled default` + editable = abap_true + )->content( `form` ). + + form->label( `Cancel the browser navigation` + )->switch( + state = client->_bind_edit( block_navigation ) + change = client->_event( `TOGGLE` ) + )->label( `Link` + )->link( + text = `Open abap2ui5.org` + href = `https://abap2ui5.org` + target = `_blank` + press = client->_event( + val = `LINK_PRESS` + s_ctrl = VALUE #( check_prevent_default = block_navigation ) ) + )->label( `Result` + )->text( client->_bind_edit( last_press ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/02/z2ui5_cl_demo_app_472.clas.xml b/src/01/02/z2ui5_cl_demo_app_472.clas.xml new file mode 100644 index 00000000..3e3cbeff --- /dev/null +++ b/src/01/02/z2ui5_cl_demo_app_472.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_DEMO_APP_472 + E + More - Link with preventDefault (A) + 1 + X + X + X + + + + diff --git a/src/01/02/z2ui5_cl_demo_app_473.clas.abap b/src/01/02/z2ui5_cl_demo_app_473.clas.abap new file mode 100644 index 00000000..9218dd29 --- /dev/null +++ b/src/01/02/z2ui5_cl_demo_app_473.clas.abap @@ -0,0 +1,80 @@ +CLASS z2ui5_cl_demo_app_473 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_demo_app_473 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + view_display( ). + ENDIF. + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_xml_view=>factory( ). + + DATA(page) = view->shell( + )->page( + title = `abap2UI5 - Menu Item Path` + navbuttonpress = client->_event_nav_app_leave( ) + shownavbutton = client->check_app_prev_stack( ) ). + + page->message_strip( + text = `The toast shows the full path of the selected menu item ` && + `("Create New Site > Official Store"), not only its own text. ` && + `$controller.textPath( ) walks the item's parent chain in the control tree ` && + `and joins the texts - a walk no binding path can express. Everything happens ` && + `on the client, the menu selection needs no roundtrip at all.` + type = `Information` + showicon = abap_true + class = `sapUiSmallMargin` ). + + " the item's breadcrumb is resolved on the client and substituted into the + " toast template ({0}); an argument starting with $ is a client-side + " expression, everything else travels as a plain string + DATA(menu_selected) = client->_event_client( + val = z2ui5_if_client=>cs_event-control_global + t_arg = VALUE #( ( `MESSAGE_TOAST` ) + ( `show` ) + ( `Action triggered on item: {0}` ) + ( `$controller.textPath(${$parameters>/item})` ) ) ). + + DATA(menu) = page->hbox( class = `sapUiSmallMargin` + )->menu_button( text = `Actions` + )->menu( itemselected = menu_selected ). + + menu->_generic( + name = `MenuItem` + t_prop = VALUE #( ( n = `text` v = `Create New Site` ) ) + )->menu_item( text = `Official Store` + )->menu_item( text = `Franchise Store` + )->menu_item( text = `Pop-up Store` ). + + menu->_generic( + name = `MenuItem` + t_prop = VALUE #( ( n = `text` v = `Manage Users` ) ) + )->menu_item( text = `Add User` + )->menu_item( text = `Remove User` ). + + menu->menu_item( text = `Log Out` ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/02/z2ui5_cl_demo_app_473.clas.xml b/src/01/02/z2ui5_cl_demo_app_473.clas.xml new file mode 100644 index 00000000..c4cd1621 --- /dev/null +++ b/src/01/02/z2ui5_cl_demo_app_473.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_DEMO_APP_473 + E + More - Menu Item Path (A) + 1 + X + X + X + + + + diff --git a/src/01/02/z2ui5_cl_demo_app_474.clas.abap b/src/01/02/z2ui5_cl_demo_app_474.clas.abap new file mode 100644 index 00000000..69e0709d --- /dev/null +++ b/src/01/02/z2ui5_cl_demo_app_474.clas.abap @@ -0,0 +1,132 @@ +CLASS z2ui5_cl_demo_app_474 DEFINITION PUBLIC. + + PUBLIC SECTION. + INTERFACES z2ui5_if_app. + + PROTECTED SECTION. + DATA client TYPE REF TO z2ui5_if_client. + + METHODS on_event. + METHODS popover_open + IMPORTING + policy TYPE string. + METHODS view_display. + + PRIVATE SECTION. +ENDCLASS. + + +CLASS z2ui5_cl_demo_app_474 IMPLEMENTATION. + + METHOD z2ui5_if_app~main. + + me->client = client. + IF client->check_on_init( ). + 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 `OPEN_RELATIVE_ONLY`. + popover_open( `RELATIVE_ONLY` ). + + WHEN `OPEN_ALLOW_ALL`. + popover_open( `ALLOW_ALL` ). + + WHEN `OPEN_DENY_ALL`. + popover_open( `DENY_ALL` ). + + ENDCASE. + + ENDMETHOD. + + + METHOD popover_open. + + " setAsyncURLHandler takes a JS callback in a UI5 controller, which no + " backend payload can carry - the frontend therefore takes the NAME of a + " built-in policy and installs the matching validator itself: + " RELATIVE_ONLY (only in-app links stay clickable), ALLOW_ALL, DENY_ALL + client->follow_up_action( val = z2ui5_if_client=>cs_event-control_by_id + t_arg = VALUE #( ( `msgPopover` ) + ( `setAsyncURLHandler` ) + ( policy ) ) ). + + " ... and only then open it, anchored to the button that fired the event + client->follow_up_action( val = z2ui5_if_client=>cs_event-control_by_id + t_arg = VALUE #( ( `msgPopover` ) + ( `openBy` ) + ( client->get_event_arg( ) ) ) ). + + ENDMETHOD. + + + METHOD view_display. + + DATA(view) = z2ui5_cl_xml_view=>factory( ). + + DATA(page) = view->shell( + )->page( + title = `abap2UI5 - MessagePopover URL Policy` + navbuttonpress = client->_event_nav_app_leave( ) + shownavbutton = client->check_app_prev_stack( ) ). + + page->message_strip( + text = `Each message below carries an in-app link and an external one. The policy ` && + `applied when opening decides which of them the popover keeps clickable - ` && + `RELATIVE_ONLY blocks everything that leaves the app, ALLOW_ALL keeps every ` && + `link, DENY_ALL blocks all of them. The policy travels as data, the frontend ` && + `installs the validator (setAsyncURLHandler).` + type = `Information` + showicon = abap_true + class = `sapUiSmallMargin` ). + + page->dependents( + )->message_popover( id = `msgPopover` + )->message_item( + type = `Error` + title = `Order cannot be released` + markupdescription = abap_true + description = `Check the order details or the ` && + `documentation.` + )->get_parent( + )->message_item( + type = `Warning` + title = `Delivery date in the past` + markupdescription = abap_true + description = `Open the delivery list or the ` && + `UI5 demo kit.` + )->get_parent( + )->get_parent( ). + + page->hbox( class = `sapUiSmallMargin` + )->button( + text = `Open with RELATIVE_ONLY` + type = `Emphasized` + press = client->_event( val = `OPEN_RELATIVE_ONLY` + t_arg = VALUE #( ( `$event.oSource.sId` ) ) ) + )->button( + text = `Open with ALLOW_ALL` + class = `sapUiTinyMarginBegin` + press = client->_event( val = `OPEN_ALLOW_ALL` + t_arg = VALUE #( ( `$event.oSource.sId` ) ) ) + )->button( + text = `Open with DENY_ALL` + class = `sapUiTinyMarginBegin` + press = client->_event( val = `OPEN_DENY_ALL` + t_arg = VALUE #( ( `$event.oSource.sId` ) ) ) ). + + client->view_display( view->stringify( ) ). + + ENDMETHOD. + +ENDCLASS. diff --git a/src/01/02/z2ui5_cl_demo_app_474.clas.xml b/src/01/02/z2ui5_cl_demo_app_474.clas.xml new file mode 100644 index 00000000..166bb38c --- /dev/null +++ b/src/01/02/z2ui5_cl_demo_app_474.clas.xml @@ -0,0 +1,16 @@ + + + + + + Z2UI5_CL_DEMO_APP_474 + E + More - MessagePopover URL Policy (A) + 1 + X + X + X + + + + diff --git a/src/01/z2ui5_cl_demo_app_g00.clas.abap b/src/01/z2ui5_cl_demo_app_g00.clas.abap index 0c638b80..da28b69b 100644 --- a/src/01/z2ui5_cl_demo_app_g00.clas.abap +++ b/src/01/z2ui5_cl_demo_app_g00.clas.abap @@ -269,6 +269,10 @@ CLASS z2ui5_cl_demo_app_g00 IMPLEMENTATION. ( group = `Basic II` header = `More` sub = `Data Loss Protection (C)` app = `z2ui5_cl_demo_app_279` ) ( group = `Basic II` header = `More` sub = `File Uploader (C)` app = `z2ui5_cl_demo_app_074` ) ( group = `Basic II` header = `More` sub = `Geoloaction (C)` app = `z2ui5_cl_demo_app_120` ) + ( group = `Basic II` header = `More` sub = `Keyboard Shortcuts (A)` app = `z2ui5_cl_demo_app_471` ) + ( group = `Basic II` header = `More` sub = `Link with preventDefault (A)` app = `z2ui5_cl_demo_app_472` ) + ( group = `Basic II` header = `More` sub = `Menu Item Path (A)` app = `z2ui5_cl_demo_app_473` ) + ( group = `Basic II` header = `More` sub = `MessagePopover URL Policy (A)` app = `z2ui5_cl_demo_app_474` ) ( group = `Basic II` header = `More` sub = `Multi Input (C)` app = `z2ui5_cl_demo_app_078` ) ( group = `Basic II` header = `More` sub = `Panel, setExpanded (A)` app = `z2ui5_cl_demo_app_448` ) ( group = `Basic II` header = `More` sub = `PDF Viewer Display (A)` app = `z2ui5_cl_demo_app_449` )