Samples / Controls / sap.m

Message Popover - Message Handling

The message handling concept sample shows how you can connect an error inside the page (such as input validation error) with an error, visualized as an item in a message popover.

Class
z2ui5_cl_smpc_app_065
Repository
abap2UI5/samples-controls
Ports
sap.m.sample.MessagePopoverMessageHandling
UI5 libraries
sap.m, sap.ui.core, sap.ui.layout, z2ui5.cc
Lowest UI5 release
1.84
In the browser
Runs in the playground
Controls it builds
sap.m.Button, sap.m.Input, sap.m.Label, sap.m.MessageItem, sap.m.MessagePopover, sap.m.OverflowToolbar, sap.m.Page, sap.m.Select, sap.m.ToolbarSpacer, sap.m.VBox, sap.ui.core.Item, sap.ui.core.Title, sap.ui.core.mvc.View, sap.ui.layout.form.SimpleForm, z2ui5.cc.MessageManager

The class

" @keywords messagepopover message popover sap.m handling vbox simpleform title label input select item
" @summary The message handling concept sample shows how you can connect an error inside the page (such as input validation error) with an error, visualized as an item in a message popover.
" @origin sap.m.sample.MessagePopoverMessageHandling - https://sdk.openui5.org/entity/sap.m.MessagePopover/sample/sap.m.sample.MessagePopoverMessageHandling (status: checked - verified in a running system)
CLASS z2ui5_cl_smpc_app_065 DEFINITION PUBLIC.

  PUBLIC SECTION.
    INTERFACES z2ui5_if_app.

    TYPES:
      BEGIN OF ty_s_form,
        name          TYPE string,
        street_name   TYPE string,
        street_number TYPE string,
        zipcode       TYPE string,
        zip_city      TYPE string,
        country       TYPE string,
        email         TYPE string,
        phone_number  TYPE string,
        phone_time    TYPE string,
        website       TYPE string,
      END OF ty_s_form.

    TYPES:
      BEGIN OF ty_s_employment,
        jobtitle    TYPE string,
        paygrade    TYPE string,
        weeklyhours TYPE string,
        unit        TYPE string,
        class       TYPE string,
        fte         TYPE string,
      END OF ty_s_employment.

    TYPES:
      BEGIN OF ty_s_message,
        message        TYPE string,
        description    TYPE string,
        type           TYPE string,
        target         TYPE string,
        additionaltext TYPE string,
        code           TYPE string,
      END OF ty_s_message.

    DATA t_forms      TYPE STANDARD TABLE OF ty_s_form WITH EMPTY KEY.
    DATA t_employment TYPE STANDARD TABLE OF ty_s_employment WITH EMPTY KEY.
    DATA t_messages   TYPE STANDARD TABLE OF ty_s_message WITH EMPTY KEY.

  PROTECTED SECTION.
    DATA client TYPE REF TO z2ui5_if_client.

    METHODS view_display.
    METHODS on_event.
    METHODS model_init.

  PRIVATE SECTION.
ENDCLASS.


CLASS z2ui5_cl_smpc_app_065 IMPLEMENTATION.

  METHOD z2ui5_if_app~main.

    me->client = client.
    IF client->check_on_init( ).
      model_init( ).
      view_display( ).
      " original onInit: MessageToast.show('Press "Save" to trigger validation.')
      client->message_toast_display( `Press "Save" to trigger validation.` ).
    ELSEIF client->check_on_navigated( ).
      view_display( ).
    ELSEIF client->check_on_event( ).
      on_event( ).
    ENDIF.

  ENDMETHOD.


  METHOD view_display.

    DATA(view) = z2ui5_cl_ui5_view_builder=>factory( ).

    view->ele( n = `View` ns = `mvc`
        )->a( n = `height`      v = `100%`
        )->a( n = `xmlns`       v = `sap.m`
        )->a( n = `xmlns:mvc`   v = `sap.ui.core.mvc`
        )->a( n = `xmlns:core`  v = `sap.ui.core`
        )->a( n = `xmlns:form`  v = `sap.ui.layout.form`
        )->a( n = `xmlns:z2ui5` v = `z2ui5.cc`

        )->ele( `Page`
            )->a( n = `id`         v = `messageHandlingPage`
            )->a( n = `showHeader` v = `false`

            )->ele( `content`

                )->ele( `VBox`
                    )->a( n = `id`    v = `formContainer`
                    )->a( n = `class` v = `sapUiSmallMargin`
                    )->a( n = `items` v = client->_bind( t_forms )

                    )->ele( n = `SimpleForm` ns = `form`
                        )->a( n = `id`        v = `SimpleFormChangeColumn_twoGroups234`
                        )->a( n = `editable`  v = `true`
                        )->a( n = `layout`    v = `ColumnLayout`
                        )->a( n = `title`     v = `Personal`
                        )->a( n = `columnsM`  v = `2`
                        )->a( n = `columnsL`  v = `2`
                        )->a( n = `columnsXL` v = `2`

                        )->ele( n = `content` ns = `form`
                            )->tag( n = `Title` ns = `core`
                                )->a( n = `text` v = `Information`
                            )->tag( `Label`
                                )->a( n = `text` v = `Name`
                            )->tag( `Input`
                                )->a( n = `required` v = `true`
                                )->a( n = `value`    v = `{ path: 'NAME', type: 'sap.ui.model.type.String' }`
                                )->a( n = `change`   v = client->_event( `CHANGE` )
                            )->tag( `Label`
                                )->a( n = `text` v = `Street/No.`
                            )->tag( `Input`
                                )->a( n = `value` v = `{STREET_NAME}`
                            )->tag( `Input`
                                )->a( n = `value` v = `{STREET_NUMBER}`
                            )->tag( `Label`
                                )->a( n = `text` v = `ZIP Code/City`
                            )->tag( `Input`
                                )->a( n = `value` v = `{ path: 'ZIPCODE', type: 'sap.ui.model.type.Integer' }`
                            )->tag( `Input`
                                )->a( n = `value` v = `{ZIP_CITY}`
                            )->tag( `Label`
                                )->a( n = `text` v = `Country`
                            )->ele( `Select`
                                )->a( n = `selectedKey` v = `{COUNTRY}`
                                )->tag( n = `Item` ns = `core`
                                    )->a( n = `key`  v = `England`
                                    )->a( n = `text` v = `England`
                                )->tag( n = `Item` ns = `core`
                                    )->a( n = `key`  v = `Germany`
                                    )->a( n = `text` v = `Germany`
                                )->tag( n = `Item` ns = `core`
                                    )->a( n = `key`  v = `USA`
                                    )->a( n = `text` v = `USA`

                            )->end(
                            )->tag( n = `Title` ns = `core`
                                )->a( n = `text` v = `Contact`
                            )->tag( `Label`
                                )->a( n = `text` v = `Email`
                            )->tag( `Input`
                                )->a( n = `value` v = `{ path: 'EMAIL', type: 'sap.ui.model.type.String', constraints: { search: '^\\w+[\\w-+\\.]*\\@[a-zA-Z]+.[a-zA-Z]+' } }`
                            )->tag( `Label`
                                )->a( n = `text` v = `Phone Number`
                            )->tag( `Input`
                                )->a( n = `value` v = `{PHONE_NUMBER}`
                            )->tag( `Input`
                                )->a( n = `value` v = `{PHONE_TIME}`
                            )->tag( `Label`
                                )->a( n = `text` v = `Personal website`
                            )->tag( `Input`
                                )->a( n = `value` v = `{WEBSITE}`

                        )->end(
                    )->end(
                )->end(

                )->ele( `VBox`
                    )->a( n = `id`    v = `formContainerEmployment`
                    )->a( n = `class` v = `sapUiSmallMargin`
                    )->a( n = `items` v = client->_bind( t_employment )

                    )->ele( n = `SimpleForm` ns = `form`
                        )->a( n = `editable`  v = `true`
                        )->a( n = `layout`    v = `ColumnLayout`
                        )->a( n = `title`     v = `Personal`
                        )->a( n = `columnsM`  v = `2`
                        )->a( n = `columnsL`  v = `2`
                        )->a( n = `columnsXL` v = `2`

                        )->ele( n = `content` ns = `form`
                            )->tag( n = `Title` ns = `core`
                                )->a( n = `text` v = `Information`
                            )->tag( `Label`
                                )->a( n = `text` v = `Job Classification`
                            )->tag( `Input`
                                )->a( n = `value` v = `{JOBTITLE}`
                            )->tag( `Label`
                                )->a( n = `text` v = `Pay Grade`
                            )->tag( `Input`
                                )->a( n = `value` v = `{PAYGRADE}`
                            )->tag( `Label`
                                )->a( n = `text` v = `Standard Weekly Hours`
                            )->tag( `Input`
                                )->a( n = `value`  v = `{ path: 'WEEKLYHOURS', type: 'sap.ui.model.type.Integer', constraints: { maximum: 40 } }`
                                )->a( n = `change` v = client->_event( `CHANGE` )
                            )->tag( n = `Title` ns = `core`
                                )->a( n = `text` v = `Rating`
                            )->tag( `Label`
                                )->a( n = `text` v = `Unit`
                            )->tag( `Input`
                                )->a( n = `value` v = `{UNIT}`
                            )->tag( `Label`
                                )->a( n = `text` v = `Employee Class`
                            )->tag( `Input`
                                )->a( n = `value` v = `{CLASS}`
                            )->tag( `Label`
                                )->a( n = `text` v = `FTE`
                            )->tag( `Input`
                                )->a( n = `value` v = `{FTE}`

                        )->end(
                    )->end(
                )->end(

                )->tag( n = `MessageManager` ns = `z2ui5`
                    )->a( n = `items` v = client->_bind( t_messages )

            )->end(

            )->ele( `footer`
                )->ele( `OverflowToolbar`

                    )->ele( `Button`
                        )->a( n = `id`           v = `messagePopoverBtn`
                        )->a( n = `visible`      v = |\{= !!$\{message>/\}.length \}|
                        )->a( n = `text`         v = |\{= $\{message>/\}.length \}|
                        )->a( n = `type`         v = `Emphasized`
                        )->a( n = `ariaHasPopup` v = `Dialog`
                        " original: this.oMP.toggle(oEvent.getSource()) - a pure client-side toggle, so
                        " wired roundtrip-free (no on_event) anchored to the button by its own id
                        )->a( n = `press`        v = client->follow_up_action( val   = client->cs_event-control_by_id
                                                                               t_arg = VALUE #( ( `messagePopover` ) ( `toggleBy` ) ( `messagePopoverBtn` ) ) )

                        )->ele( `dependents`
                            )->ele( `MessagePopover`
                                )->a( n = `id`               v = `messagePopover`
                                )->a( n = `items`            v = `{message>/}`
                                )->a( n = `groupItems`       v = `true`
                                " activeTitlePress is a MessagePopover event (not MessageItem); it ships the
                                " pressed message's target control id so the handler can scroll+focus it
                                )->a( n = `activeTitlePress` v = client->_event( val = `ACTIVE_TITLE` arg = `${$parameters>/item}.getBindingContext('message').getObject().getControlIds()[0]` )
                                )->tag( `MessageItem`
                                    )->a( n = `title`       v = `{message>message}`
                                    )->a( n = `subtitle`    v = `{message>additionalText}`
                                    )->a( n = `type`        v = `{message>type}`
                                    )->a( n = `description` v = `{message>message}`
                                    )->a( n = `activeTitle` v = `true`
                                    " group name (Personal, <section>): a domain classification computed in
                                    " the backend (see model above) and carried on the Message code field,
                                    " NOT a frontend expression - the original derives it in its controller's
                                    " getGroupName; only Email sits in the Contact group, the rest in Information
                                    )->a( n = `groupName`   v = `{message>code}`

                            )->end(
                        )->end(
                    )->end(

                    )->tag( `ToolbarSpacer`
                    )->tag( `Button`
                        )->a( n = `type`  v = `Emphasized`
                        )->a( n = `text`  v = `Save`
                        )->a( n = `press` v = client->_event( `SAVE` )
                    )->tag( `Button`
                        )->a( n = `text` v = `Cancel`

                )->end(
            )->end(
        )->end( ).

    client->view_display( view->stringify( ) ).

  ENDMETHOD.


  METHOD on_event.

    CASE client->get_event( ).

      WHEN `ACTIVE_TITLE`.
        " original: activeTitlePress scrolls to the message's target control, closes the popover
        " and focuses the control; the full control id travels from the pressed MessageItem's
        " message object (getControlIds()[0]) and the frontend SCROLL_INTO_VIEW + SET_FOCUS act on it
        DATA(control_id) = client->get_event_arg( ).
        IF control_id IS NOT INITIAL.
          client->follow_up_action( val   = client->cs_event-scroll_into_view
                                    t_arg = VALUE #( ( control_id ) ) ).
          client->follow_up_action( val   = client->cs_event-control_by_id
                                    t_arg = VALUE #( ( `messagePopover` ) ( `close` ) ) ).
          client->follow_up_action( val   = client->cs_event-set_focus
                                    t_arg = VALUE #( ( control_id ) ) ).
        ENDIF.

      WHEN `SAVE`.
        " original generateInvalidUserInput(): force a set of fields invalid to demo the message
        " handling, then open the MessagePopover. abap2UI5 only auto-collects validation messages
        " from USER input, not from programmatically-set model values, so Save mirrors the four
        " issues explicitly on the SAME rows the original targets - formContainer.getItems()[4/5/6]
        " = John Miller / Stefan Bosch / Maria Fontes, plus the employment row: it injects the
        " invalid values and authors the matching four messages (3 Errors + 1 Warning), which the
        " z2ui5.cc.MessageManager reconciles into the message manager.
        IF lines( t_forms ) >= 7.
          " John Miller  -> /T_FORMS/4/NAME
          t_forms[ 5 ]-name     = ``.
          " Stefan Bosch -> /T_FORMS/5/ZIPCODE
          t_forms[ 6 ]-zipcode = `AAA`.
          " Maria Fontes -> /T_FORMS/6/EMAIL
          t_forms[ 7 ]-email    = `MariaFontes.com`.
        ENDIF.
        IF t_employment IS NOT INITIAL.
          t_employment[ 1 ]-weeklyhours = `400`.
        ENDIF.
        t_messages = VALUE #(
          ( message = `A mandatory field is required` type = `Error` additionaltext = `Name`
            target = `/T_FORMS/4/NAME` )
          ( message = `Enter a number with no decimal places` type = `Error` additionaltext = `ZIP Code/City`
            target = `/T_FORMS/5/ZIPCODE` )
          ( message = `Enter a valid value` type = `Error` additionaltext = `Email`
            target = `/T_FORMS/6/EMAIL` )
          ( message = `The value should not exceed 40` type = `Warning` additionaltext = `Standard Weekly Hours`
            description = `The value of the working hours field should not exceed 40 hours.`
            target = `/T_EMPLOYMENT/0/WEEKLYHOURS` ) ).
        " the message group (Personal, <section>) is a domain classification, so
        " it is computed in the backend (thin frontend) and rides on the Message
        " code field - the original derives it in its controller's getGroupName.
        LOOP AT t_messages REFERENCE INTO DATA(msg).
          msg->code = COND #( WHEN msg->additionaltext = `Email`
                                 THEN `Personal, Contact`
                                 ELSE `Personal, Information` ).
        ENDLOOP.
        " original: oMP.openBy(oButton) after the values are set - open the popover anchored to the button
        client->follow_up_action( val   = client->cs_event-control_by_id
                                  t_arg = VALUE #( ( `messagePopover` ) ( `openBy` ) ( `messagePopoverBtn` ) ) ).

      WHEN `CHANGE`.
        " the sample's onChange manually adds/removes required-field and constraint messages; here
        " the typed binding + constraints collect those AUTOMATICALLY into the message> model
        " (no app code), so the handler only pushes the model back

    ENDCASE.

  ENDMETHOD.


  METHOD model_init.

    t_forms = VALUE #(
      ( name = `Julie Armstrong` street_name = `Mainstreet` street_number = `1278`
        zipcode = `12345` zip_city = `Maintown` country = `Germany`
        email = `Julie.Armstrong@company.com` phone_number = `+1 (610) 661-1000` phone_time = `12:00` website = `n/a` )
      ( name = `Denise Smith` street_name = `Mainstreet` street_number = `1567`
        zipcode = `12345` zip_city = `Maintown` country = `Germany`
        email = `Denise.Smith@company.com` phone_number = `+1 (610) 661-1000` phone_time = `12:00` website = `n/a` )
      ( name = `Richard Wilson` street_name = `Mainstreet` street_number = `2984`
        zipcode = `12345` zip_city = `Maintown` country = `Germany`
        email = `Richard.Wilson@company.com` phone_number = `+1 (610) 661-1000` phone_time = `12:00` website = `n/a` )
      ( name = `Gerd Becker` street_name = `Mainstreet` street_number = `3614`
        zipcode = `12345` zip_city = `Maintown` country = `Germany`
        email = `Gerd.Becker@company.com` phone_number = `+1 (610) 661-1000` phone_time = `12:00` website = `n/a` )
      ( name = `John Miller` street_name = `Mainstreet` street_number = `1618`
        zipcode = `AAA` zip_city = `Maintown` country = `Germany`
        email = `John.Miller@company.com` phone_number = `+1 (610) 661-1000` phone_time = `12:00` website = `n/a` )
      ( name = `Stefan Bosch` street_name = `Mainstreet` street_number = `4864`
        zipcode = `12345` zip_city = `Maintown` country = `Germany`
        email = `Stefan.Bosch@company.com` phone_number = `+1 (610) 661-1000` phone_time = `12:00` website = `n/a` )
      ( name = `Maria Fontes` street_name = `Mainstreet` street_number = `4864`
        zipcode = `12345` zip_city = `Maintown` country = `Germany`
        email = `` phone_number = `+1 (610) 661-1000` phone_time = `12:00` website = `MariaFontescompany.com` )
      ( name = `Antonio Ferrari` street_name = `Mainstreet` street_number = `2598`
        zipcode = `12345` zip_city = `Maintown` country = `Germany`
        email = `Antonio.Ferrari@company.com` phone_number = `+1 (610) 661-1000` phone_time = `12:00` website = `n/a` ) ).

    t_employment = VALUE #(
      ( jobtitle = `Senior UI Developer (UIDEV-SR)` paygrade = `Salary Grade 18 (GR-14` weeklyhours = `0`
        unit = `ABC` class = `Employee` fte = `1` ) ).

  ENDMETHOD.

ENDCLASS.