Samples / Controls / sap.ui.unified

Special Days with Legend

Calendar with special days and legend

Class
z2ui5_cl_smpc_app_308
Repository
abap2UI5/samples-controls
Ports
sap.ui.unified.sample.CalendarSpecialDaysLegend
UI5 libraries
sap.m, sap.ui.core, sap.ui.layout, sap.ui.unified
Lowest UI5 release
1.81
In the browser
Runs in the playground
Controls it builds
sap.m.ToggleButton, sap.ui.core.HTML, sap.ui.core.mvc.View, sap.ui.layout.VerticalLayout, sap.ui.unified.Calendar, sap.ui.unified.CalendarLegend, sap.ui.unified.CalendarLegendItem, sap.ui.unified.DateTypeRange

The class

" @keywords calendar sap.ui.unified calendarspecialdayslegend html verticallayout datetyperange calendarlegend calendarlegenditem togglebutton
" @summary Calendar with special days and legend
" @origin sap.ui.unified.sample.CalendarSpecialDaysLegend - https://sdk.openui5.org/entity/sap.ui.unified.Calendar/sample/sap.ui.unified.sample.CalendarSpecialDaysLegend (status: reviewed - read against the original, not run)
CLASS z2ui5_cl_smpc_app_308 DEFINITION PUBLIC.

  PUBLIC SECTION.
    INTERFACES z2ui5_if_app.

    TYPES:
      BEGIN OF ty_s_legend,
        type TYPE string,
        text TYPE string,
      END OF ty_s_legend.
    TYPES ty_t_legend TYPE STANDARD TABLE OF ty_s_legend WITH EMPTY KEY.
    TYPES:
      BEGIN OF ty_s_special,
        start_date     TYPE string,
        end_date       TYPE string,
        type           TYPE string,
        secondary_type TYPE string,
        tooltip        TYPE string,
        color          TYPE string,
      END OF ty_s_special.
    TYPES ty_t_special TYPE STANDARD TABLE OF ty_s_special WITH EMPTY KEY.

    DATA pressed    TYPE abap_bool.
    DATA t_legend1  TYPE ty_t_legend.
    DATA t_legend2  TYPE ty_t_legend.
    DATA t_special1 TYPE ty_t_special.
    DATA t_special2 TYPE ty_t_special.

  PROTECTED SECTION.
    DATA client TYPE REF TO z2ui5_if_client.

    METHODS view_display.
    METHODS on_event.
    METHODS special_days_fill.

  PRIVATE SECTION.
ENDCLASS.


CLASS z2ui5_cl_smpc_app_308 IMPLEMENTATION.

  METHOD z2ui5_if_app~main.

    me->client = client.
    IF 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( ).

    " DateTypeRange.startDate/endDate are typed "object" and demand a real JS Date;
    " the model keeps ABAP DATS strings and Formatter.DateAbapDateToDateObject converts them at
    " the point of use (needs UI5 >= 1.74). endDate is optional, and needs no
    " guard: DateAbapDateToDateObject answers null for a non-date, where
    " DateCreateObject's new Date('') is an Invalid Date - truthy, and enough to
    " kill the whole view
    view->ele( n = `View` ns = `mvc`
        )->a( n = `xmlns:l`      v = `sap.ui.layout`
        )->a( n = `xmlns:u`      v = `sap.ui.unified`
        )->a( n = `xmlns:mvc`    v = `sap.ui.core.mvc`
        )->a( n = `xmlns:core`   v = `sap.ui.core`
        )->a( n = `xmlns`        v = `sap.m`
        )->a( n = `class`        v = `viewPadding`
        )->a( n = `core:require` v = `{Formatter: 'z2ui5/model/formatter'}`


        " the sample's own ../style.css (shared by the sap.ui.unified samples and
        " listed in this sample's manifest) - the view carries the class and the
        " rule behind it has to come with it. \{ \} escaped: the XMLView parser
        " reads an unescaped brace as a binding
        )->tag( n = `HTML` ns = `core`
            )->a( n = `content` v = `<style>.viewPadding\{padding:1rem\}` &&
                                    `.sap-phone .viewPadding\{padding:0rem\}` &&
                                    `.sap-phone .sapUiCal\{position:relative\}</style>`
        )->ele( n = `VerticalLayout` ns = `l`

            )->ele( n = `Calendar` ns = `u`
                )->a( n = `id`                v = `calendar1`
                )->a( n = `legend`            v = `legend1`
                )->a( n = `intervalSelection` v = `true`
                )->a( n = `specialDates`      v = client->_bind(
                                                      val                = t_special1
                                                      omit_initial_paths = VALUE #( ( `COLOR` )
                                                                                    ( `SECONDARY_TYPE` )
                                                                                    ( `TOOLTIP` ) ) )

                )->ele( n = `specialDates` ns = `u`
                    )->tag( n = `DateTypeRange` ns = `u`
                        )->a( n = `startDate`     v = `{ path: 'START_DATE', formatter: 'Formatter.DateAbapDateToDateObject' }`
                        " a special date with no end is a single day. That used to
                        " need a ternary guard, because an empty string through
                        " DateCreateObject is an Invalid Date that
                        " Month._checkDateEnabled throws on;
                        " DateAbapDateToDateObject answers null for a non-date, so
                        " the plain binding is enough (app 220's lesson)
                        )->a( n = `endDate`       v = |\{ path: 'END_DATE', formatter: 'Formatter.DateAbapDateToDateObject' \}|
                        )->a( n = `type`          v = `{TYPE}`
                        " An enum property REFUSES an empty string - `"" is of type
                        " string, expected sap.ui.unified.CalendarDayType` - and the
                        " template is evaluated with no row the moment the toggle
                        " CLEARS the table. Same shape as endDate above: fall back to
                        " the enum's own default rather than let `` reach the control.
                        " Found by the e2e interaction (the second press), 2026-08-16.
                        )->a( n = `secondaryType` v = `{= ${SECONDARY_TYPE} ? ${SECONDARY_TYPE} : 'None' }`
                        )->a( n = `tooltip`       v = `{TOOLTIP}`
                        )->a( n = `color`         v = `{COLOR}`

                )->end(
            )->end(

            )->ele( n = `CalendarLegend` ns = `u`
                )->a( n = `id`    v = `legend1`
                )->a( n = `items` v = client->_bind( t_legend1 )

                )->ele( n = `items` ns = `u`
                    )->tag( n = `CalendarLegendItem` ns = `u`
                        )->a( n = `type` v = `{TYPE}`
                        )->a( n = `text` v = `{TEXT}`

                )->end(
            )->end(

            )->ele( n = `Calendar` ns = `u`
                )->a( n = `id`           v = `calendar2`
                )->a( n = `legend`       v = `legend2`
                )->a( n = `specialDates` v = client->_bind(
                                                 val                = t_special2
                                                 omit_initial_paths = VALUE #( ( `COLOR` )
                                                                               ( `SECONDARY_TYPE` )
                                                                               ( `TOOLTIP` ) ) )

                )->ele( n = `specialDates` ns = `u`
                    )->tag( n = `DateTypeRange` ns = `u`
                        )->a( n = `startDate`     v = `{ path: 'START_DATE', formatter: 'Formatter.DateAbapDateToDateObject' }`
                        " a special date with no end is a single day. That used to
                        " need a ternary guard, because an empty string through
                        " DateCreateObject is an Invalid Date that
                        " Month._checkDateEnabled throws on;
                        " DateAbapDateToDateObject answers null for a non-date, so
                        " the plain binding is enough (app 220's lesson)
                        )->a( n = `endDate`       v = |\{ path: 'END_DATE', formatter: 'Formatter.DateAbapDateToDateObject' \}|
                        )->a( n = `type`          v = `{TYPE}`
                        " An enum property REFUSES an empty string - `"" is of type
                        " string, expected sap.ui.unified.CalendarDayType` - and the
                        " template is evaluated with no row the moment the toggle
                        " CLEARS the table. Same shape as endDate above: fall back to
                        " the enum's own default rather than let `` reach the control.
                        " Found by the e2e interaction (the second press), 2026-08-16.
                        )->a( n = `secondaryType` v = `{= ${SECONDARY_TYPE} ? ${SECONDARY_TYPE} : 'None' }`
                        )->a( n = `tooltip`       v = `{TOOLTIP}`
                        )->a( n = `color`         v = `{COLOR}`

                )->end(
            )->end(

            )->ele( n = `CalendarLegend` ns = `u`
                )->a( n = `id`            v = `legend2`
                )->a( n = `standardItems` v = `Today`
                )->a( n = `items`         v = client->_bind( t_legend2 )

                )->ele( n = `items` ns = `u`
                    )->tag( n = `CalendarLegendItem` ns = `u`
                        )->a( n = `type` v = `{TYPE}`
                        )->a( n = `text` v = `{TEXT}`

                )->end(
            )->end(

            )->tag( `ToggleButton`
                )->a( n = `text`    v = `Special Days`
                )->a( n = `pressed` v = client->_bind( pressed )
                )->a( n = `press`   v = client->_event( val = `SHOW_SPECIAL_DAYS` arg = `${$parameters>/pressed}` ) ).

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

  ENDMETHOD.


  METHOD on_event.

    IF client->get_event( ) = `SHOW_SPECIAL_DAYS`.
      " handleShowSpecialDays: the pressed state adds the special dates and the
      " legend items to both calendars, the released state destroys them again -
      " here the four bound tables are filled and cleared instead
      pressed = client->get_event_arg( ).
      IF pressed = abap_true.
        special_days_fill( ).
      ELSE.
        t_special1 = VALUE #( ).
        t_special2 = VALUE #( ).
        t_legend1 = VALUE #( ).
        t_legend2 = VALUE #( ).
      ENDIF.
    ENDIF.

  ENDMETHOD.


  METHOD special_days_fill.

    " the original walks a reference date through the CURRENT month with
    " setDate( n ), so every date below is day n of the current month
    " The special dates are ABAP DATS strings read through
    " Formatter.DateAbapDateToDateObject, which builds the Date from the parsed
    " parts - LOCAL midnight, which is what sap.ui.unified reads back
    " (CalendarDate.fromLocalJSDate). They were ISO date-only strings through
    " Formatter.DateCreateObject until 2026-08-21, and `new Date('yyyy-mm-dd')`
    " is UTC midnight, so west of Greenwich every marked day landed one day
    " early. Same defect and same fix as apps 220 and 017.
    DATA(prefix) = |{ sy-datum+0(4) }{ sy-datum+4(2) }|.

    t_special1 = VALUE #( ).
    t_special2 = VALUE #( ).
    t_legend1 = VALUE #( ).
    t_legend2 = VALUE #( ).
    DO 10 TIMES.
      DATA(i)    = sy-index.
      DATA(type) = |Type{ i WIDTH = 2 ALIGN = RIGHT PAD = '0' }|.
      DATA(text) = |Placeholder { i }|.
      DATA(day)  = |{ prefix }{ i WIDTH = 2 ALIGN = RIGHT PAD = '0' }|.

      t_special1 = VALUE #( BASE t_special1 ( start_date = day type = type tooltip = text ) ).
      t_special2 = VALUE #( BASE t_special2 ( start_date = day type = type tooltip = text ) ).
      t_legend1  = VALUE #( BASE t_legend1 ( type = type text = text ) ).
      t_legend2  = VALUE #( BASE t_legend2 ( type = type text = text ) ).
    ENDDO.

    t_special1 = VALUE #( BASE t_special1
      ( start_date = |{ prefix }12| type = `Type11` color = `#ff0000` )
      ( start_date = |{ prefix }13| type = `Type11` color = `#ff69b4` )
      ( start_date = |{ prefix }11| end_date = |{ prefix }21| type = `NonWorking` )
      ( start_date = |{ prefix }25| type = `Working` ) ).

    t_special2 = VALUE #( BASE t_special2
      ( start_date = |{ prefix }12| type = `Type11` color = `#ff0000` )
      ( start_date = |{ prefix }13| type = `Type11` color = `#add8e6` )
      ( start_date = |{ prefix }22| type = `Type03` secondary_type = `NonWorking` )
      ( start_date = |{ prefix }24| type = `Working` )
      ( start_date = |{ prefix }24| type = `Type03` ) ).

  ENDMETHOD.

ENDCLASS.