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` )