UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,388 lines (1,198 loc) 185 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ //Provides control sap.m.PlanningCalendar. sap.ui.define([ "sap/base/i18n/Formatting", "sap/base/i18n/Localization", 'sap/m/delegate/DateNavigation', 'sap/ui/core/Control', 'sap/ui/base/ManagedObjectObserver', "sap/ui/core/Lib", 'sap/ui/unified/library', 'sap/ui/unified/calendar/CalendarUtils', 'sap/ui/unified/calendar/CalendarDate', 'sap/ui/unified/calendar/DatesRow', 'sap/ui/unified/calendar/OneMonthDatesRow', 'sap/ui/unified/calendar/MonthsRow', 'sap/ui/unified/calendar/TimesRow', 'sap/ui/unified/calendar/WeeksRow', 'sap/ui/unified/DateRange', 'sap/ui/unified/DateTypeRange', 'sap/ui/unified/CalendarAppointment', 'sap/ui/unified/CalendarRow', 'sap/ui/unified/CalendarRowRenderer', 'sap/ui/Device', 'sap/ui/core/Element', 'sap/ui/core/Renderer', 'sap/ui/core/ResizeHandler', 'sap/ui/core/InvisibleText', 'sap/ui/core/dnd/DragInfo', 'sap/ui/core/dnd/DropInfo', 'sap/ui/core/dnd/DragDropInfo', 'sap/ui/core/format/DateFormat', 'sap/base/i18n/date/CalendarType', 'sap/base/i18n/date/CalendarWeekNumbering', 'sap/ui/core/date/CalendarUtils', 'sap/ui/core/Locale', "sap/ui/core/date/UI5Date", "sap/ui/events/KeyCodes", 'sap/m/Avatar', 'sap/m/Toolbar', 'sap/m/Table', 'sap/m/Column', 'sap/m/ColumnListItem', 'sap/m/ColumnListItemRenderer', 'sap/m/SegmentedButtonItem', 'sap/m/StandardListItem', 'sap/m/PlanningCalendarHeader', 'sap/m/PlanningCalendarRenderer', 'sap/m/PlanningCalendarView', 'sap/m/CheckBox', 'sap/m/library', "sap/base/util/deepEqual", "sap/base/Log", "sap/m/List", "sap/ui/thirdparty/jquery", // jQuery Plugin "control" "sap/ui/dom/jquery/control" ], function( Formatting, Localization, DateNavigation, Control, ManagedObjectObserver, Library, unifiedLibrary, CalendarUtils, CalendarDate, DatesRow, OneMonthDatesRow, MonthsRow, TimesRow, WeeksRow, DateRange, DateTypeRange, CalendarAppointment, CalendarRow, CalendarRowRenderer, Device, Element, Renderer, ResizeHandler, InvisibleText, DragInfo, DropInfo, DragDropInfo, DateFormat, _CalendarType, // indirectly used for properties `primaryCalendarType` and `secondaryCalendarType` _CalendarWeekNumbering, // indirectly required for property `calendarWeekNumbering` CalendarDateUtils, Locale, UI5Date, KeyCodes, Avatar, Toolbar, Table, Column, ColumnListItem, ColumnListItemRenderer, SegmentedButtonItem, StandardListItem, PlanningCalendarHeader, PlanningCalendarRenderer, PlanningCalendarView, CheckBox, library, deepEqual, Log, List, jQuery ) { "use strict"; // shortcut for sap.m.Sticky var Sticky = library.Sticky; // shortcut for sap.ui.unified.CalendarDayType var CalendarDayType = unifiedLibrary.CalendarDayType; // shortcut for sap.m.ListMode var ListMode = library.ListMode; // shortcut for sap.m.ToolbarDesign var ToolbarDesign = library.ToolbarDesign; // shortcut for sap.m.PlanningCalendarBuiltInView var PlanningCalendarBuiltInView = library.PlanningCalendarBuiltInView; // shortcut for sap.m.ScreenSize var ScreenSize = library.ScreenSize; // shortcut for sap.ui.unified.CalendarAppointmentVisualization var CalendarAppointmentVisualization = unifiedLibrary.CalendarAppointmentVisualization; // shortcut for sap.ui.unified.GroupAppointmentsMode var GroupAppointmentsMode = unifiedLibrary.GroupAppointmentsMode; // shortcut for sap.ui.unified.CalendarIntervalType var CalendarIntervalType = unifiedLibrary.CalendarIntervalType; // shortcut for sap.ui.unified.CalendarAppointmentHeight var CalendarAppointmentHeight = unifiedLibrary.CalendarAppointmentHeight; // shortcut for sap.ui.unified.CalendarAppointmentRoundWidth var CalendarAppointmentRoundWidth = unifiedLibrary.CalendarAppointmentRoundWidth; var AvatarShape = library.AvatarShape; var DRAG_DROP_CONFIG_NAME = "DragDropConfig"; var RESIZE_CONFIG_NAME = "ResizeConfig"; var CREATE_CONFIG_NAME = "CreateConfig"; var LISTITEM_SUFFIX = "-CLI"; /** * Constructor for a new <code>PlanningCalendar</code>. * * @param {string} [sID] ID for the new control, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new control * * @class * Displays rows with appointments for different entities (such as persons or teams) for the selected time interval. * * <h3>Overview</h3> * * You can use the <code>PlanningCalendar</code> to represent a calendar containing multiple rows with * appointments, where each row represents a different person. * * You can configure different time-interval views that the user can switch between, such as hours or days, and even * a whole week/month. The available navigation allows the user to select a specific interval using a picker, or * move to the previous/next interval using arrows. * * <b>Note:</b> The application developer should add dependency to <code>sap.ui.unified</code> library * on application level to ensure that the library is loaded before the module dependencies will be required. * The <code>PlanningCalendar</code> uses parts of the <code>sap.ui.unified</code> library. * This library will be loaded after the <code>PlanningCalendar</code>, if it wasn't loaded first. * This could lead to CSP compliance issues and adds an additional waiting time when a <code>PlanningCalendar</code> is used for the first time. * To prevent this, apps that use the <code>PlanningCalendar</code> should also load the * <code>sap.ui.unified</code> library in advance. * * <h3>Usage</h3> * * The <code>PlanningCalendar</code> has the following structure from top to bottom: * * <ul> * <li>A toolbar where you can add your own buttons or other controls using the <code>toolbarContent</code> aggregation.</li> * <li>A header containing a drop-down menu for selecting the {@link sap.m.PlanningCalendarView PlanningCalendarViews}, * and navigation for moving through the intervals using arrows or selecting a specific interval with a picker. * Custom views can be configured using the <code>views</code> aggregation. If not configured, the following set of default * built-in views is available - Hours, Days, 1 Week, 1 Month, and Months. Setting a custom view(s) replaces the built-in ones.</li> * <li>The rows of the <code>PlanningCalendar</code> that contain the assigned appointments. * They can be configured with the <code>rows</code> aggregation, which is of type * {@link sap.m.PlanningCalendarRow PlanningCalendarRow}.</li> * </ul> * * Since 1.48 the empty space in the cell that is below an appointment can be removed by adding * the <code>sapUiCalendarAppFitVertically</code> CSS class to the <code>PlanningCalendar</code>. * Please note that it should be used only for a <code>PlanningCalendar</code> with one appointment per day * for a row that doesn't have interval headers set. * * Since 1.44 alternating row colors can be suppressed by adding the <code>sapMPlanCalSuppressAlternatingRowColors</code> * CSS class to the <code>PlanningCalendar</code>. * * <h3>Responsive behavior</h3> * * You can define the number of displayed intervals based on the size of the <code>PlanningCalendar</code> using the * {@link sap.m.PlanningCalendarView PlanningCalendarView}'s properties. * * @extends sap.ui.core.Control * @version 1.146.0 * * @constructor * @public * @since 1.34 * @alias sap.m.PlanningCalendar * @see {@link fiori:https://experience.sap.com/fiori-design-web/planning-calendar/ Planning Calendar} */ var PlanningCalendar = Control.extend("sap.m.PlanningCalendar", /** @lends sap.m.PlanningCalendar.prototype */ { metadata : { library : "sap.m", properties : { /** * Determines the start date of the row, as a UI5Date or JavaScript Date object. The current date is used as default. */ startDate : {type : "object", group : "Data"}, /** * Defines the key of the <code>PlanningCalendarView</code> used for the output. * * <b>Note:</b> The default value is set <code>Hour</code>. If you are using your own views, the keys of these * views should be used instead. */ viewKey : {type : "string", group : "Appearance", defaultValue : CalendarIntervalType.Hour}, /** * Determines whether only a single row can be selected. */ singleSelection : {type : "boolean", group : "Misc", defaultValue : true}, /** * Specifies the width of the <code>PlanningCalendar</code>. */ width : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null}, /** * Specifies the height of the <code>PlanningCalendar</code>. * <b>Note:</b> If the set height is less than the displayed content, it will not be applied */ height : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null}, /** * Determines whether the assigned interval headers are displayed. You can assign them using the * <code>intervalHeaders</code> aggregation of the {@link sap.m.PlanningCalendarRow PlanningCalendarRow}. * * <b>Note:</b> If you set both <code>showIntervalHeaders</code> and <code>showEmptyIntervalHeaders</code> * properties to <code>true</code>, the space (at the top of the intervals) where the assigned interval * headers appear, will remain visible even if no interval headers are assigned. */ showIntervalHeaders : {type : "boolean", group : "Appearance", defaultValue : true}, /** * Determines whether the space (at the top of the intervals), where the assigned interval headers appear, should remain * visible even when no interval headers are present in the visible time frame. If set to <code>false</code>, this * space would collapse/disappear when no interval headers are assigned. * * <b>Note:</b> This property takes effect, only if <code>showIntervalHeaders</code> is also set to <code>true</code>. * @since 1.38.0 */ showEmptyIntervalHeaders : {type : "boolean", group : "Appearance", defaultValue : true}, /** * Determines whether the column containing the headers of the {@link sap.m.PlanningCalendarRow PlanningCalendarRows} * is displayed. */ showRowHeaders : {type : "boolean", group : "Appearance", defaultValue : true}, /** * Defines the text that is displayed when no {@link sap.m.PlanningCalendarRow PlanningCalendarRows} are assigned. * <b>Note:</b> If both <code>noDataText</code> property and <code>noData</code> aggregation are provided, the <code>noData</code> aggregation takes priority. * If the <code>noData</code> aggregation is undefined or set to null, the <code>noDataText</code> property is used instead. */ noDataText : {type : "string", group : "Misc", defaultValue : null}, /** * Defines the mode in which the overlapping appointments are displayed. * * <b>Note:</b> This property takes effect, only if the <code>intervalType</code> of the current calendar view * is set to <code>sap.ui.unified.CalendarIntervalType.Month</code>. On phone devices this property is ignored, * and the default value is applied. * @since 1.48.0 */ groupAppointmentsMode : {type : "sap.ui.unified.GroupAppointmentsMode", group : "Appearance", defaultValue : GroupAppointmentsMode.Collapsed}, /** * Determines whether the appointments that have only title without text are rendered with smaller height. * * <b>Note:</b> On phone devices this property is ignored, appointments are always rendered in full height * to facilitate touching. * @deprecated Since version 1.119. Please use the <code>appointmentHeight</code> with value "Automatic" property instead. * @since 1.38.0 */ appointmentsReducedHeight : {type : "boolean", group : "Appearance", defaultValue : false, deprecated: true}, /** * Determines the different possible sizes for appointments. * @since 1.81.0 */ appointmentHeight: { type: "sap.ui.unified.CalendarAppointmentHeight", group: "Appearance", defaultValue: CalendarAppointmentHeight.Regular }, /** * Defines rounding of the width <code>CalendarAppoinment</code> * <b>Note:</b> This property is applied, when the calendar interval type is Day and the view shows more than 20 days * @since 1.81.0 */ appointmentRoundWidth: { type: "sap.ui.unified.CalendarAppointmentRoundWidth", group: "Appearance", defaultValue: CalendarAppointmentRoundWidth.None}, /** * Determines how the appointments are visualized depending on the used theme. * @since 1.40.0 */ appointmentsVisualization : {type : "sap.ui.unified.CalendarAppointmentVisualization", group : "Appearance", defaultValue : CalendarAppointmentVisualization.Standard}, /** * Defines the minimum date that can be displayed and selected in the <code>PlanningCalendar</code>. * This must be a UI5Date or JavaScript Date object. * * <b>Note:</b> If the <code>minDate</code> is set to be after the current <code>maxDate</code>, * the <code>maxDate</code> is set to the last date of the month in which the <code>minDate</code> belongs. * @since 1.38.0 */ minDate : {type : "object", group : "Misc", defaultValue : null}, /** * Defines the maximum date that can be displayed and selected in the <code>PlanningCalendar</code>. * This must be a UI5Date or JavaScript Date object. * * <b>Note:</b> If the <code>maxDate</code> is set to be before the current <code>minDate</code>, * the <code>minDate</code> is set to the first date of the month in which the <code>maxDate</code> belongs. * @since 1.38.0 */ maxDate : {type : "object", group : "Misc", defaultValue : null}, /** * Determines whether the day names are displayed in a separate line or inside the single days. * @since 1.50 */ showDayNamesLine : {type : "boolean", group : "Appearance", defaultValue : false}, /** * Determines if the week numbers are displayed. * @since 1.52 */ showWeekNumbers : {type : "boolean", group : "Appearance", defaultValue : false}, /** * Defines the list of predefined views as an array. * The views should be specified by their keys. * * The default predefined views and their keys are available at * {@link sap.m.PlanningCalendarBuiltInView}. * * <b>Note:</b> If set, all specified views will be displayed along * with any custom views (if available). If not set and no custom * views are available, all default views will be displayed. * If not set and there are any custom views available, only the * custom views will be displayed. * @since 1.50 */ builtInViews : {type : "string[]", group : "Appearance", defaultValue : []}, /** * Determines whether the header area will remain visible (fixed on top) when the rest of the content is scrolled out of view. * * The sticky header behavior is automatically disabled on phones in landscape mode for better visibility of the content. * * <b>Note:</b> There is limited browser support, hence the API is in experimental state. * Browsers that currently support this feature are Chrome (desktop and mobile), Safari (desktop and mobile) and Edge. * * There are also some known issues with respect to the scrolling behavior and focus handling. A few are given below: * * When the PlanningCalendar is placed in certain layout containers, for example the <code>GridLayout</code> control, * the column headers do not fix at the top of the viewport. Similar behavior is also observed with the <code>ObjectPage</code> control. * * This API should not be used in production environment. * *<b>Note:</b> The <code>stickyHeader</code> of the <code>PlanningCalendar</code> uses the <code>sticky</code> property of <code>sap.m.Table</code>. * Therefore, all features and restrictions of the property in <code>sap.m.Table</code> apply to the <code>PlanningCalendar</code> as well. * @since 1.54 */ stickyHeader : {type : "boolean", group : "Appearance", defaultValue : false}, /** * If set, the first day of the displayed week is this day. Valid values are 0 to 6 starting on Sunday. * If there is no valid value set, the default of the used locale is used. * * Note: this property will only have effect in the weekly – based views of the PlanningCalendar – Week view, * and OneMonth view (on small devices). * * @since 1.94 */ firstDayOfWeek : {type : "int", group : "Appearance", defaultValue : -1}, /** * If set, the calendar week numbering is used for display. * If not set, the calendar week numbering of the global configuration is used. * @since 1.110.0 */ calendarWeekNumbering : { type : "sap.base.i18n.date.CalendarWeekNumbering", group : "Appearance", defaultValue: null}, /** * If set, the calendar type is used for display. * If not set, the calendar type of the global configuration is used. * @since 1.108.0 */ primaryCalendarType : {type : "sap.base.i18n.date.CalendarType", group : "Appearance"}, /** * If set, the days are also represented in this calendar type. * If not set, the dates are only represented in the primary calendar type. * Note: The second calendar type won't be represented in the DOM when this property is not set explicitly. * @since 1.109.0 */ secondaryCalendarType : {type : "sap.base.i18n.date.CalendarType", group : "Appearance"}, /** * Determines whether the selection of multiple appointments is enabled. * * Note: selection of multiple appointments is possible using CTRL key regardless of the value of this property. * * @since 1.97 */ multipleAppointmentsSelection : {type : "boolean", group : "Data", defaultValue : false}, /** * Defines the shape of the <code>Avatar</code>. */ iconShape: {type: "sap.m.AvatarShape", group: "Appearance", defaultValue: AvatarShape.Circle} }, aggregations : { /** * Rows of the <code>PlanningCalendar</code>. */ rows : {type : "sap.m.PlanningCalendarRow", multiple : true, singularName : "row"}, /** * Views of the <code>PlanningCalendar</code>. * * <b>Note:</b> If not set, all the default views are available. Their keys are defined in * {@link sap.ui.unified.CalendarIntervalType}. */ views : {type : "sap.m.PlanningCalendarView", multiple : true, singularName : "view"}, /** * Special days in the header calendar visualized as date range with a type. * * <b>Note:</b> In case there are multiple <code>sap.ui.unified.DateTypeRange</code> instances given for a single date, * only the first <code>sap.ui.unified.DateTypeRange</code> instance will be used. * For example, using the following sample, the 1st of November will be displayed as a working day of type "Type10": * * * <pre> * new DateTypeRange({ * startDate: UI5Date.getInstance(2023, 10, 1), * type: CalendarDayType.Type10, * }), * new DateTypeRange({ * startDate: UI5Date.getInstance(2023, 10, 1), * type: CalendarDayType.NonWorking * }) * </pre> * * If you want the first of November to be displayed as a non-working day and also as "Type10," the following should be done: * <pre> * new DateTypeRange({ * startDate: UI5Date.getInstance(2023, 10, 1), * type: CalendarDayType.Type10, * secondaryType: CalendarDayType.NonWorking * }) * </pre> * * You can use only one of the following types for a given date: <code>sap.ui.unified.CalendarDayType.NonWorking</code>, * <code>sap.ui.unified.CalendarDayType.Working</code> or <code>sap.ui.unified.CalendarDayType.None</code>. * Assigning more than one of these values in combination for the same date will lead to unpredictable results. * */ specialDates : {type : "sap.ui.unified.DateTypeRange", multiple : true, singularName : "specialDate"}, /** * The content of the toolbar. */ toolbarContent : { type : "sap.ui.core.Control", multiple : true, singularName : "toolbarContent", forwarding : { getter : "_getHeader", aggregation : "actions" } }, /** * Hidden, for internal use only. */ table : {type : "sap.m.Table", multiple : false, visibility : "hidden"}, /** * Hidden, for internal use only. */ header : {type : "sap.ui.core.Control", multiple : false, visibility : "hidden"}, /** * Defines the custom visualization if there is no data available. * <b>Note:</b> If both <code>noDataText</code> property and <code>noData</code> aggregation are provided, the <code>noData</code> aggregation takes priority. * If the <code>noData</code> aggregation is undefined or set to null, the <code>noDataText</code> property is used instead. * @since 1.125.0 */ noData : {type: "sap.ui.core.Control", multiple: false, altTypes: ["string"], forwarding: { idSuffix: "-Table", aggregation: "noData" }} }, associations: { /** * Association to controls / IDs which label this control (see WAI-ARIA attribute aria-labelledby). * @since 1.40.0 */ ariaLabelledBy: { type: "sap.ui.core.Control", multiple: true, singularName: "ariaLabelledBy" }, /** * Association to the <code>CalendarLegend</code> explaining the colors of the <code>Appointments</code>. * * <b>Note:</b> The legend does not have to be rendered but must exist, and all required types must be assigned. * @since 1.40.0 */ legend: { type: "sap.ui.unified.CalendarLegend", multiple: false} }, events : { /** * Fired if an appointment is selected. */ appointmentSelect : { parameters : { /** * The selected appointment. */ appointment : {type : "sap.ui.unified.CalendarAppointment"}, /** * The selected appointments in case a group appointment is selected. */ appointments : {type : "sap.ui.unified.CalendarAppointment[]"}, /** * If set, the appointment was selected using multiple selection (e.g. Shift + single mouse click), * meaning more than the current appointment could be selected. */ multiSelect : {type : "boolean"}, /** * Gives the ID of the DOM element of the clicked appointment * @since 1.50.0 */ domRefId: {type: "string"} } }, /** * Fired if an interval was selected in the calendar header or in the row. */ intervalSelect : { parameters : { /** * Start date of the selected interval, as a UI5Date or JavaScript Date object. */ startDate : {type : "object"}, /** * Interval end date as a UI5Date or JavaScript Date object. * @since 1.38.0 */ endDate : {type : "object"}, /** * If set, the selected interval is a subinterval. * @since 1.38.0 */ subInterval : {type : "boolean"}, /** * Row of the selected interval. * @since 1.38.0 */ row : {type : "sap.m.PlanningCalendarRow"} } }, /** * Fires when row selection is changed. */ rowSelectionChange : { parameters : { /** * Array of rows whose selection has changed. */ rows : {type : "sap.m.PlanningCalendarRow[]"} } }, /** * Fired when the <code>startDate</code> property was changed while navigating in the <code>PlanningCalendar</code>. * The new value can be obtained using the <code>sap.m.PlanningCalendar#getStartDate()</code> method. * <b>Note:</b> This event is fired in case when the <code>viewKey</code> property is changed, and as a result of * which the view requires a change in the <code>startDate</code> property. */ startDateChange : {}, /** * Fired when the <code>viewKey</code> property was changed by user interaction. */ viewChange : {}, /** * Fires when a row header is clicked. * @since 1.46.0 * @deprecated Since version 1.119, replaced by <code>rowHeaderPress</code> event */ rowHeaderClick: { parameters : { /** * The ID of the <code>PlanningCalendarRowHeader</code> of the selected appointment. * * <b>Note:</b> Intended to be used as an easy way to get an ID of a <code>PlanningCalendarRowHeader</code>. Do NOT use for modification. * * @since 1.73 */ headerId : {type : "string"}, /** * The row user clicked on. */ row : {type : "sap.m.PlanningCalendarRow"} } }, /** * Fires when a row header press is triggered with mouse click, SPACE or ENTER press. * @since 1.119.0 */ rowHeaderPress: { parameters : { /** * The ID of the <code>PlanningCalendarRowHeader</code> of the selected appointment. * * <b>Note:</b> Intended to be used as an easy way to get an ID of a <code>PlanningCalendarRowHeader</code>. Do NOT use for modification. * */ headerId : {type : "string"}, /** * The row user pressed. */ row : {type : "sap.m.PlanningCalendarRow"} } } }, designtime: "sap/m/designtime/PlanningCalendar.designtime" }, constructor: function(vId, mSettings) { Control.prototype.constructor.apply(this, arguments); if (typeof vId !== "string"){ mSettings = vId; } if (mSettings && typeof mSettings.customAppointmentsSorterCallback === "function") { this._fnCustomSortedAppointments = mSettings.customAppointmentsSorterCallback; } }, renderer: PlanningCalendarRenderer }); //List of private properties controlling different intervals var INTERVAL_CTR_REFERENCES = ["_oTimesRow", "_oDatesRow", "_oMonthsRow", "_oWeeksRow", "_oOneMonthsRow"], //Holds metadata of the different interval instances that should be created. INTERVAL_METADATA = {}; INTERVAL_METADATA[CalendarIntervalType.Day] = { sInstanceName: "_oDatesRow", sIdSuffix: "-DatesRow", oClass: DatesRow }; INTERVAL_METADATA[CalendarIntervalType.Week] = { sInstanceName: "_oWeeksRow", sIdSuffix: "-WeeksRow", oClass: DatesRow }; INTERVAL_METADATA[CalendarIntervalType.OneMonth] = { sInstanceName: "_oOneMonthsRow", sIdSuffix: "-OneMonthsRow", oClass: OneMonthDatesRow }; //Defines the minimum screen width for the appointments column (it is a popin column) var APP_COLUMN_MIN_SCREEN_WIDTH = ScreenSize.Desktop; //All supported built-in views var KEYS_FOR_ALL_BUILTIN_VIEWS = [ PlanningCalendarBuiltInView.Hour, PlanningCalendarBuiltInView.Day, PlanningCalendarBuiltInView.Month, PlanningCalendarBuiltInView.Week, PlanningCalendarBuiltInView.OneMonth]; var SCREEEN_BREAKPOINTS = { PHONE: "600", TABLET: "1024" }; // Holds the possible values for the "_currentPicker" property in the currentPicker association of the header var CURRENT_PICKERS = { MONTH: "month", // represents the "month" aggregation MONTH_PICKER: "monthPicker", // represents the "monthPicker" aggregation YEAR_PICKER: "yearPicker", // represents the "yearPicker" aggregation YEAR_RANGE_PICKER: "yearRangePicker" // represents the "yearRangePicker" aggregation }; var MONTH_DELEGATE = { onAfterRendering: function () { this.setProperty("_currentPicker", CURRENT_PICKERS.MONTH); this.removeDelegate(MONTH_DELEGATE); } }; var MONTH_PICKER_DELEGATE = { onAfterRendering: function () { this.setProperty("_currentPicker", CURRENT_PICKERS.MONTH_PICKER); this.removeDelegate(MONTH_PICKER_DELEGATE); } }; var YEAR_PICKER_DELEGATE = { onAfterRendering: function () { this.setProperty("_currentPicker", CURRENT_PICKERS.YEAR_PICKER); this.removeDelegate(YEAR_PICKER_DELEGATE); } }; var aIntervalRepresentatives = [ "sap.ui.unified.calendar.TimesRow", "sap.ui.unified.calendar.DatesRow", "sap.ui.unified.calendar.MonthsRow", "sap.ui.unified.calendar.OneMonthDatesRow", "sap.ui.unified.calendar.WeeksRow" ]; var CalendarHeader = Control.extend("sap.m._PlanningCalendarInternalHeader", { metadata : { properties : { showWeekNumbers : {type : "boolean", group : "Appearance", defaultValue : false} }, aggregations: { "toolbar" : {type: "sap.m.Toolbar", multiple: false}, "allCheckBox" : {type: "sap.m.CheckBox", multiple: false} } }, renderer : { apiVersion: 2, render: function(oRm, oHeader) { oRm.openStart("div", oHeader); oRm.class("sapMPlanCalHead"); oRm.openEnd(); var oToolbar = oHeader.getToolbar(); if (oToolbar) { oRm.renderControl(oToolbar); } var oAllCB = oHeader.getAllCheckBox(); if (oAllCB) { oRm.renderControl(oAllCB); } oRm.close("div"); } } }); PlanningCalendar.prototype.init = function(){ this._dateNav = new DateNavigation(); this._iBreakPointTablet = Device.media._predefinedRangeSets[Device.media.RANGESETS.SAP_STANDARD_EXTENDED].points[0]; this._iBreakPointDesktop = Device.media._predefinedRangeSets[Device.media.RANGESETS.SAP_STANDARD_EXTENDED].points[1]; this._iBreakPointLargeDesktop = Device.media._predefinedRangeSets[Device.media.RANGESETS.SAP_STANDARD_EXTENDED].points[2]; if (Device.system.phone || jQuery('html').hasClass("sapUiMedia-Std-Phone")) { this._iSize = 0; this._iSizeScreen = 0; } else if ((Device.system.tablet || jQuery('html').hasClass("sapUiMedia-Std-Tablet")) && !(Device.system.desktop || jQuery('html').hasClass("sapUiMedia-Std-Desktop"))){ this._iSize = 1; this._iSizeScreen = 1; } else { this._iSize = 2; this._iSizeScreen = 2; } this.addStyleClass("sapMSize" + this._iSize); this.setAggregation("header", this._createHeader()); this._attachHeaderEvents(); this._oRB = Library.getResourceBundleFor("sap.m"); var sId = this.getId(); this._oIntervalTypeSelect = this._getHeader()._getOrCreateViewSwitch(); this._oIntervalTypeSelect.attachEvent("selectionChange", changeIntervalType, this); this._oTodayButton = this._getHeader()._getTodayButton(); this._oCalendarHeader = new CalendarHeader(sId + "-CalHead", {}); this._oCalendarHeader.isRelative = this.isRelative.bind(this); this._oCalendarHeader._getRelativeInfo = this._getRelativeInfo.bind(this); this._oInfoToolbar = new Toolbar(sId + "-InfoToolbar", { height: "auto", design: ToolbarDesign.Transparent, content: [this._oCalendarHeader, this._oTimesRow], ariaLabelledBy: InvisibleText.getStaticId("sap.m", "PC_INTERVAL_TOOLBAR") }); this._oInfoToolbar.addStyleClass("sapMPlanCalInfoToolbar"); var oTable = new Table(sId + "-Table", { sticky: [], // set sticky property to an empty array this correspondents to PlanningCalendar stickyHeader = false infoToolbar: this._oInfoToolbar, mode: ListMode.SingleSelectMaster, columns: [ new Column({ styleClass: "sapMPlanCalRowHead" }), new Column({ width: "80%", styleClass: "sapMPlanCalAppRow", minScreenWidth: APP_COLUMN_MIN_SCREEN_WIDTH, demandPopin: true }) ], ariaLabelledBy: `${this.getId()}-Descr` }); oTable.attachEvent("selectionChange", handleTableSelectionChange, this); oTable.addDelegate({ onBeforeRendering: function () { if (this._rowHeaderPressEventMouse) { this._rowHeaderPressEventMouse.off(); } if (this._rowHeaderPressEventKeyboard) { this._rowHeaderPressEventKeyboard.off(); } }, onAfterRendering: function () { if (this.hasListeners("rowHeaderPress")) { this._addRowHeaderDescription(); } const oRowHeader = oTable.$().find(".sapMPlanCalRowHead"); this._rowHeaderPressEventMouse = oRowHeader.on("click", function (oEvent) { const oRowListItem = Element.closestTo(oEvent.currentTarget), oRow = getRow(oRowListItem), sRowHeaderId = oRowListItem.getAggregation("cells")[0].getId(); /** * @deprecated As of version 1.119 */ this.fireRowHeaderClick({headerId: sRowHeaderId, row: oRow}); this.fireRowHeaderPress({headerId: sRowHeaderId, row: oRow}); }.bind(this)); this._rowHeaderPressEventKeyboard = oRowHeader.on("keydown", function (oEvent) { if (oEvent.which === KeyCodes.SPACE || oEvent.which === KeyCodes.ENTER) { oEvent.preventDefault(); var oRowListItem = Element.closestTo(oEvent.currentTarget), oRow = getRow(oRowListItem), sRowHeaderId = oRowListItem.getAggregation("cells")[0].getId(); this.fireRowHeaderPress({headerId: sRowHeaderId, row: oRow}); } }.bind(this)); this._adjustColumnHeadersTopOffset(); } }, false, this); oTable.getStickyFocusOffset = getStickyFocusOffset.bind(this); // Override setNavigationItems to exclude the popin column header (.sapMTblItemNav) from keyboard navigation // In PlanningCalendar context, this element serves no interactive purpose and creates a confusing "ghost" tab stop var fnOriginalSetNavigationItems = oTable.setNavigationItems.bind(oTable); oTable.setNavigationItems = function(oItemNavigation) { fnOriginalSetNavigationItems(oItemNavigation); // Remove .sapMTblItemNav elements from item navigation as they are not meaningful in PlanningCalendar var aItemDomRefs = oItemNavigation.getItemDomRefs().filter(function(oDomRef) { return !oDomRef.classList.contains("sapMTblItemNav"); }); oItemNavigation.setItemDomRefs(aItemDomRefs); }; this.setAggregation("table", oTable, true); this.setStartDate(UI5Date.getInstance()); this._resizeProxy = handleResize.bind(this); this._fnCustomSortedAppointments = undefined; //transfers a custom appointments sorter function to the CalendarRow this.iWidth = 0; }; PlanningCalendar.prototype.exit = function(){ if (this._sResizeListener) { ResizeHandler.deregister(this._sResizeListener); this._sResizeListener = undefined; } Device.orientation.detachHandler(this._updateStickyHeader, this); if (this._sUpdateCurrentTime) { clearTimeout(this._sUpdateCurrentTime); this._sUpdateCurrentTime = undefined; } // remove ColumnListItems from table to not destroy them with table but from parent PlanningCalendarRow var oTable = this.getAggregation("table"); oTable.removeAllItems(); // destroy also currently not used controls INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef]._oPlanningCalendar = undefined; this[sControlRef].destroy(); this[sControlRef] = undefined; } }, this); if (this._oViews) { for (var sKey in this._oViews) { this._oViews[sKey].destroy(); } } if (this._oSelectAllCheckBox) { this._oSelectAllCheckBox.destroy(); } // Remove event listener for rowHeaderPress event if (this._rowHeaderPressEventMouse) { this._rowHeaderPressEventMouse.off(); this._rowHeaderPressEventMouse = null; } // Remove event listener for rowHeaderPress event if (this._rowHeaderPressEventKeyboard) { this._rowHeaderPressEventKeyboard.off(); this._rowHeaderPressEventKeyboard = null; } if (this._oCalendarWeeks) { this._oCalendarWeeks.destroy(); this._oCalendarWeeks = null; } }; PlanningCalendar.prototype.onBeforeRendering = function(){ this._bBeforeRendering = true; this._updateHeader(); // init interval if default one is used this._adjustViewKey(); updateSelectItems.call(this); if (this._sUpdateCurrentTime) { clearTimeout(this._sUpdateCurrentTime); this._sUpdateCurrentTime = undefined; } this._updatePickerSelection(); this._updateHeaderButtons(); Device.orientation.detachHandler(this._updateStickyHeader, this); this._bBeforeRendering = undefined; this._toggleStickyClasses(); updateSelectedRows.call(this); this._setProperties(); }; PlanningCalendar.prototype._setProperties = function() { const bMultiSelect = this.getMultipleAppointmentsSelection(), sIconShape = this.getIconShape(), oStartDate = this.getStartDate(), sAppointmentHeight = this.getAppointmentHeight(), sAppointmentRoundWidth = this.getAppointmentRoundWidth(), bShowIntervalHeaders = this.getShowIntervalHeaders(), bShowEmptyIntervalHeaders = this.getShowEmptyIntervalHeaders(), sAppointmentsVisualization = this.getAppointmentsVisualization(), oGroupAppointmentsMode = this.getGroupAppointmentsMode(), /** * @deprecated Since version 1.119. */ bAppointmentsReducedHeight = this.getAppointmentsReducedHeight(), vLegend = this.getLegend(); this.getRows().forEach(function (oRow) { const oCurrentRowHeader = getRowHeader(oRow), oRowTimeline = getRowTimeline(oRow); if (oCurrentRowHeader.getAvatar) { oCurrentRowHeader.getAvatar().setDisplayShape(sIconShape); } oRowTimeline.setMultipleAppointmentsSelection(bMultiSelect); oRowTimeline.setStartDate(oStartDate); oRowTimeline.setAppointmentHeight(sAppointmentHeight); oRowTimeline.setAppointmentRoundWidth(sAppointmentRoundWidth); oRowTimeline.setShowIntervalHeaders(bShowIntervalHeaders); oRowTimeline.setShowEmptyIntervalHeaders(bShowEmptyIntervalHeaders); oRowTimeline.setAppointmentsVisualization(sAppointmentsVisualization); oRowTimeline.setGroupAppointmentsMode(oGroupAppointmentsMode); /** * @deprecated Since version 1.119. */ oRowTimeline.setAppointmentsReducedHeight(bAppointmentsReducedHeight); oRowTimeline.setLegend(vLegend); }); this._setPrimaryCalendarType(); this._setSecondaryCalendarType(); this._setFirstDayOfWeek(); this._setCalendarWeekNumbering(); this._setShowWeekNumbers(); this._setShowRowHeaders(); this._setShowDayNamesLine(); this._setStickyHeader(); this._setNoDataText(); this._setLegend(vLegend); }; PlanningCalendar.prototype._bindAggregation = function(sName, oBindingInfo) { if (sName === "rows") { addBindingListener(oBindingInfo, "dataRequested", this._onBindingDataRequestedListener.bind(this)); addBindingListener(oBindingInfo, "dataReceived", this._onBindingDataReceivedListener.bind(this)); } Control.prototype._bindAggregation.call(this, sName, oBindingInfo); }; PlanningCalendar.prototype._onBindingDataRequestedListener = function(oEvent) { this.getAggregation("table").setBusy(true, "listUl"); }; PlanningCalendar.prototype._onBindingDataReceivedListener = function(oEvent) { this.getAggregation("table").setBusy(false, "listUl"); }; PlanningCalendar.prototype._updateHeader = function () { this._getHeader().setProperty("_primaryCalendarType", this._getPrimaryCalendarType()); if (this._getSecondaryCalendarType()) { this.setShowDayNamesLine(true); this._getHeader().setProperty("_secondaryCalendarType", this.getSecondaryCalendarType()); } return this; }; PlanningCalendar.prototype.attachEvent = function (eventId, data, functionToCall, listener) { Control.prototype.attachEvent.call(this, eventId, data, functionToCall, listener); if (this.hasListeners("intervalSelect")) { INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef]._setAriaRole("columnheader"); // set new aria role } }, this); } else if (this.hasListeners("rowHeaderPress") && this.getDomRef()) { this._addRowHeaderDescription(); } return this; }; PlanningCalendar.prototype.detachEvent = function (eventId, functionToCall, listener) { Control.prototype.detachEvent.call(this, eventId, functionToCall, listener); if (!this.hasListeners("intervalSelect")) { INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef]._setAriaRole("gridcell"); // set new aria role } }, this); } return this; }; /** * Creates the header and adds proper <code>ariaLabelledBy</code> references on its toolbars. * * @returns {sap.m.PlanningCalendarHeader} The created header * @private */ PlanningCalendar.prototype._createHeader = function () { var oHeader = new PlanningCalendarHeader(this.getId() + "-Header", { calendarWeekNumbering: this.getCalendarWeekNumbering() }); oHeader._getRelativeInfo = this._getRelativeInfo.bind(this); oHeader.getAggregation("_actionsToolbar") .addAriaLabelledBy(InvisibleText.getStaticId("sap.m", "PC_FUNCTIONS_TOOLBAR")); oHeader.getAggregation("_navigationToolbar") .addAriaLabelledBy(InvisibleText.getStaticId("sap.m", "PC_INTERVAL_SELECTION_TOOLBAR")); return oHeader; }; /** * Attaches handlers to the events in the _header aggregation. * * @returns {this} this for method chaining * @private */ PlanningCalendar.prototype._attachHeaderEvents = function () { var oHeader = this._getHeader(); oHeader.attachEvent("pressPrevious", this._handlePressArrow, this); oHeader.attachEvent("pressToday", this._handleTodayPress, this); oHeader.attachEvent("pressNext", this._handlePressArrow, this); oHeader.attachEvent("dateSelect", this._handleDateSelect, this); oHeader.attachEvent("_titleChange", this._handleTitleChange, this); return this; }; PlanningCalendar.prototype._handleTitleChange = function (oEvent) { const oTitle = oEvent.getParameter("title"); const sTitleId = oTitle?.getId() || ""; this.getDomRef()?.setAttribute("aria-labelledby", sTitleId); this._resetTableLabelledBy(oTitle); }; /** * Handler for the pressPrevious and pressNext events in the _header aggregation. * @param {object} oEvent The triggered event * @private */ PlanningCalendar.prototype._handlePressArrow = function (oEvent) { this._applyArrowsLogic(oEvent.getId() === "pressPrevious"); }; /** * Logic for moving the selected time range in the control via the navigation arrows. * @param {boolean} bBackwards Whether the left arrow is pressed * @private */ PlanningCalendar.prototype._applyArrowsLogic = function(bBackwards) { if (bBackwards) { this._dateNav.previous(this._getPrimaryCalendarType()); } else { this._dateNav.next(this._getPrimaryCalendarType()); } var oRow = this._getRowInstanceByViewKey(this.getViewKey()); this.setStartDate(this._dateNav.getStart()); oRow.displayDate(this._dateNav.getCurrent()); this._updatePickerSelection(); this.fireStartDateChange(); }; PlanningCalendar.prototype._addRowHeaderDescription = function() { const aPlanningCalendarRowListItems = this.getAggregation("table").getItems(); aPlanningCalendarRowListItems.forEach(function(oRowListItem) { const oRowId = oRowListItem.getTimeline().getAssociation("row"), oRow = Element.getElementById(oRowId), sRowDesctiption = oRow.getRowHeaderDescription() || this._oRB.getText("PLANNING_CALENDAR_ROW_HEADER_DESCRIPTION"); oRowListItem.getDomRef().querySelector(".sapMPlanCalRowHead").setAttribute("aria-description", sRowDesctiption); }, this); }; /** * Creates and formats the strings to be displayed in the picker button from the _header aggregation. * If the date part of the start and end range of the showed view are different, the string contains * info about the current date. Otherwise, the result string shows info about a date range. * @returns {Object} The concatenated strings to be displayed * @private */ PlanningCalendar.prototype._formatPickerText = function () { var oRangeDates = this._getFirstAndLastRangeDate(), oStartDate = CalendarUtils._createLocalDate(oRangeDates.oStartDate, true), oEndDate = CalendarUtils._createLocalDate(oRangeDates.oEndDate, true), sViewKey = this.getViewKey(), sViewType = CalendarIntervalType[sViewKey] ? CalendarIntervalType[sViewKey] : this._getView(sViewKey).getIntervalType(), bRTL = Localization.getRTL(), oDateFormat, sResult, sBeginningResult, sEndResult, oDateFormatInSecType, sBeginnigDateResultInSecType, sEndResultInSecType, sResultInSecType; if (this._getSecondaryCalendarType()) { oDateFormatInSecType = DateFormat.getDateInstance({ format: "yMMMMd", calendarType: this.getSecondaryCalendarType() }); } switch (sViewType) { case CalendarIntervalType.Hour: oDateFormat = DateFormat.getDateInstance({format: "yMMMMd", calendarType: this._getPrimaryCalendarType()}); sBeginningResult = oDateFormat.format(oStartDate); if (oStartDate.getDate() !== oEndDate.getDate()) { sEndResult = oDateFormat.format(oEndDate); } if (this._getSecondaryCalendarType()){ sBeginnigDateResultInSecType = oDateFormatInSecType.format(oStartDate); if (oStartDate.getDate() !== oEndDate.getDate()) { sEndResultInSecType = oDateFormatInSecType.format(oEndDate); } } break; case CalendarIntervalType.Day: case CalendarIntervalType.Week: var fnIntervalLabelFormatter = this._getRelativeInfo().intervalLabelFormatter; if (this.isRelative()) { var iBeginning = this.calcIntervalOffset(this.getStartDate()); var iEnding = this.calcIntervalOffset(this.getEndDate()) - this.calcIntervalOffset(this.getStartDate()); sBeginningResult = fnIntervalLabelFormatter ? fnIntervalLabelFormatter(iBeginning) : iBeginning; var iEndingParameter = this._getRelativeInfo().iIntervalSize === 1 ? iBeginning + iEnding : iBeginning + iEnding - 1; sEndResult = fnIntervalLabelFormatter ? fnIntervalLabelFormatter(iEndingParameter) : iBeginning + iEnding; } else { oDateFormat = DateFormat.getDateInstance({ format: "yMMMMd", calendarType: this._getPrimaryCalendarType() }); sBeginningResult = oDateFormat.format(oStartDate); sEndResult = oDateFormat.format(oEndDate); if (this._getSecondaryCalendarType()) { sBeginnigDateResultInSecType = oDateFormatInSecType.format(oStartDate); sEndResultInSecType = oDateFormatInSecType.format(oEndDate); } } break; case CalendarIntervalType.OneMonth: case "OneMonth": oDateFormat = DateFormat.getDateInstance({format: "yMMMM", calendarType: this._getPrimaryCalendarType()}); sBeginningResult = oDateFormat.format(oStartDate); if (this._getSecondaryCalendarType()) { oDateFormatInSecType = DateFormat.getDateInstance({format: "yMMMM", calendarType: this.getSecondaryCalendarType()}); sBeginnigDateResultInSecType = oDateFormatInSecType.format(oStartDate); } break; case CalendarIntervalType.Month: oDateFormat = DateFormat.getDateInstance({format: "y", calendarType: this._getPrimaryCalendarType()}); sBeginningResult = oDateFormat.format(oStartDate); if (this._getSecondaryCalendarType()) { oDateFormatInSecType = DateFormat.getDateInstance({format: "y", calendarType: this.getSecondaryCalendarType()}); sBeginnigDateResultInSecType = oDateFormatInSecType.format(oStartDate); } if (oStartDate.getFullYear() !== oEndDate.getFullYear()) { sEndResult = oDateFormat.format(oEndDate); if (this._getSecondaryCalendarType()) { sEndResultInSecType = oDateFormatInSecType.format(oEndDate); } } if (sBeginningResult === sEndResult) { sEndResult = undefined; } break; default: throw new Error("Unknown IntervalType: " + sViewKey + "; " + this); } if (!bRTL) { sResult = sBeginningResult; sResultInSecType = sBeginnigDateResultInSecType; if (sEndResult) { sResult += " - " + sEndResult; } if (this._getSecondaryCalendarType() && sEndResultInSecType) { sResultInSecType += " - " + sEndResultInSecType; } } else { if (sEndResult) { sResult = sEndResult + " - " + sBeginningResult; } else { sResult = sBeginningResult; } if (this._getSecondaryCalendarType() && sEndResultInSecType) { sResultInSecType = sBeginnigDateResultInSecType + " - " + sEndResultInSecType; } else if (this._getSecondaryCalendarType()) { sResultInSecType = sBeginnigDateResultInSecType; } } return { primaryType: sResult, secondaryType: sResultInSecType }; }; PlanningCalendar.prototype._getPrimaryCalendarType = function() { return this.getProperty("primaryCalendarType") || Formatting.getCalendarType(); }; /** * Calculates the first and the last date of the range to be displayed. The size of the range depends on the * currently selected view. * @returns {object} Two properties containing the first and the last date from the range * @private */ PlanningCalendar.prototype._getFirstAndLastRangeDate = function () { var oStartDate = this.getStartDate(), oMinDate = this.getMinDate(), oStartRange = oStartDate, oUniStartDate, oUniEndDate; if (oMinDate && oMinDate.getTime() > oStartDate.getTime() && this._isWeekOrOneMonthView(this.getViewKey())) { oStartRange = oMinDate; } oUniStartDate = CalendarUtils._createUniversalUTCDate(oStartRange, this._getPrimaryCalendarType(), true); oUniEndDate = CalendarUtils._createUniversalUTCDate(this._dateNav.getEnd(), this._getPrimaryCalendarType(), true); return { oStartDate: oUniStartDate, oEndDate: oUniEndDate }; }; /** * Getter for header aggregation. * * @returns {sap.m.PlanningCalendarHeader} The header object * @private */ PlanningCalendar.prototype._getHeader = function () { return this.getAggregation("header"); }; /** * Applies or removes sticky class based on <code>stickyHeader</code>'s value. * * @returns {this} <code>this</code> for chaining * @private */ PlanningCalendar.prototype._toggleStickyClasses = function () { this.toggleStyleClass("sapMPCSticky", this.getStickyHeader()); return this; }; /** * Makes sure that the column headers are offset in such a way, that they are positioned right * after the navigation