UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,395 lines (1,198 loc) 57.8 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.SinglePlanningCalendarMonthGrid. sap.ui.define([ 'sap/base/i18n/Formatting', 'sap/base/i18n/date/CalendarType', 'sap/base/i18n/date/CalendarWeekNumbering', 'sap/ui/core/Control', 'sap/ui/core/format/DateFormat', 'sap/ui/unified/calendar/CalendarDate', 'sap/ui/unified/calendar/CalendarUtils', 'sap/ui/unified/DateTypeRange', 'sap/ui/unified/library', 'sap/ui/core/LocaleData', 'sap/ui/core/Locale', 'sap/ui/core/delegate/ItemNavigation', 'sap/ui/core/dnd/DragDropInfo', 'sap/ui/core/CustomData', 'sap/ui/events/KeyCodes', 'sap/base/Log', 'sap/ui/core/Core', './Link', './library', './PlanningCalendarLegend', './SinglePlanningCalendarMonthGridRenderer', 'sap/ui/thirdparty/jquery', 'sap/ui/core/InvisibleMessage', 'sap/ui/core/library', 'sap/ui/core/date/CalendarUtils', 'sap/ui/core/date/UI5Date', 'sap/ui/unified/DateRange' ], function ( Formatting, CalendarType, _CalendarWeekNumbering, // type of `calendarWeekNumbering` Control, DateFormat, CalendarDate, CalendarUtils, DateTypeRange, unifiedLibrary, LocaleData, Locale, ItemNavigation, DragDropInfo, CustomData, KeyCodes, Log, Core, Link, library, PlanningCalendarLegend, SinglePlanningCalendarMonthGridRenderer, jQuery, InvisibleMessage, coreLibrary, CalendarDateUtils, UI5Date, DateRange ) { "use strict"; var APP_HEIGHT_COMPACT = 1.5625; //rem var CELL_HEADER_HEIGHT_COMPACT = 1.5; //rem var APP_HEIGHT_COZY = 2.125; //rem var CELL_HEADER_HEIGHT_COZY = 1.75; //rem var InvisibleMessageMode = coreLibrary.InvisibleMessageMode; var SinglePlanningCalendarSelectionMode = library.SinglePlanningCalendarSelectionMode; var LinkAccessibleRole = library.LinkAccessibleRole; /** * Constructor for a new <code>SinglePlanningCalendarMonthGrid</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 a grid for the <code>SinglePlanningCalendarMonthView</code> in which appointments are rendered. * * <h3>Overview</h3> * * The <code>SinglePlanningCalendarMonthGrid</code> has the following structure: * * <ul> * <li>The grid shows one whole month.</li> * <li>Each cell in the grid represents a single day.</li> * <li>Each row represents a week.</li> * <li>Appointments are displayed as a list inside each day. * They are sorted by their start dates. * Their widths do not show their durations.</li> * </ul> * * @extends sap.ui.core.Control * * @author SAP SE * @version 1.146.0 * * @constructor * @private * @alias sap.m.SinglePlanningCalendarMonthGrid */ var SinglePlanningCalendarMonthGrid = Control.extend("sap.m.SinglePlanningCalendarMonthGrid", /** @lends sap.m.SinglePlanningCalendarMonthGrid.prototype */ { metadata: { library: "sap.m", properties: { /** * Determines the start date of the grid, as a UI5Date or JavaScript Date object. It is considered as a local date. * The time part will be ignored. The current date is used as default. */ startDate: { type: "object", group: "Data" }, /** * Determines whether the appointments in the grid are draggable. * * The drag and drop interaction is visualized by a placeholder highlighting the area where the * appointment can be dropped by the user. */ enableAppointmentsDragAndDrop: { type: "boolean", group: "Misc", 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. * * @since 1.98 */ 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. * Note: This property should not be used with firstDayOfWeek property. * @since 1.110.0 */ calendarWeekNumbering : { type : "sap.base.i18n.date.CalendarWeekNumbering", group : "Appearance", defaultValue: null}, /** * Determines whether more than one day will be selectable. * <b>Note:</b> selecting more than one day is possible with a combination of <code>Ctrl + mouse click</code> */ dateSelectionMode: { type: "sap.m.SinglePlanningCalendarSelectionMode", group: "Behavior", defaultValue: SinglePlanningCalendarSelectionMode.SingleSelect } }, aggregations: { /** * The appointments to be displayed in the grid. Appointments outside the visible time frame are not rendered. * Appointments longer than a day are displayed in all of the affected days. */ appointments: { type: "sap.ui.unified.CalendarAppointment", multiple: true, singularName: "appointment", dnd: { draggable: true }}, /** * Special days in the header visualized as a date range with 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" }, _appsPlaceholders: { type: "sap.m.SinglePlanningCalendarMonthGrid._internal.IntervalPlaceholder", multiple: true, visibility: "hidden", dnd: { droppable: true } }, /** * Dates or date ranges for selected dates. * * To set a single date (instead of a range), set only the <code>startDate</code> property * of the {@link sap.ui.unified.DateRange} class. */ selectedDates : {type : "sap.ui.unified.DateRange", multiple : true, singularName : "selectedDate"} }, dnd: true, associations: { /** * Association to the <code>PlanningCalendarLegend</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. */ legend: { type: "sap.m.PlanningCalendarLegend", multiple: false } }, events: { /** * Fired when a grid cell is pressed. */ cellPress: { parameters: { /** * The start date as a UI5Date or JavaScript Date object of the focused grid cell. */ startDate: { type: "object" }, /** * The end date as a UI5Date or JavaScript Date object of the focused grid cell. */ endDate: { type: "object" }, /** * The original browser event. */ originalEvent: {type: "object"} } }, /** * Fired when the week number selection changes. If <code>dateSelectionMode</code> is <code>SinglePlanningCalendarSelectionMode.Multiselect</code>, clicking on the week number will select the corresponding week. * If the week has already been selected, clicking the week number will deselect it. * * @since 1.123 */ weekNumberPress : { parameters: { /** * Тhe number of the pressed calendar week. */ weekNumber: {type: "int"} } }, /** * Fired when the selected dates change. * The default behavior can be prevented using the <code>preventDefault</code> method. * * <b>Note:</b> If the event is prevented, the changes in the aggregation <code>selectedDates</code> will be canceled and it will revert to its previous state. * @since 1.123 */ selectedDatesChange : { allowPreventDefault: true, parameters: { /** * The array of all selected days. */ selectedDates: {type: "sap.ui.unified.DateRange[]"} } }, /** * Fired when a 'more' button is pressed. * <b>Note:</b> The 'more' button appears when multiple appointments * exist and the available space is not sufficient to display all of them. */ moreLinkPress: { parameters: { /** * The date as a UI5Date or JavaScript Date object of the cell with the * pressed more link. */ date: {type: "object"}, /** * The link that has been triggered */ sourceLink: {type: "sap.m.Link"} } }, /** * Fired if an appointment is dropped. */ appointmentDrop: { parameters: { /** * The dropped appointment. */ appointment: { type: "sap.ui.unified.CalendarAppointment" }, /** * Start date of the dropped appointment as a UI5Date or JavaScript Date object. */ startDate: { type: "object" }, /** * End date of the dropped appointment as a UI5Date or JavaScript Date object. */ endDate: { type: "object" }, /** * Determines whether the drop type is to copy the appointment (when <code>true</code>) or to move it. */ copy: { type: "boolean" } } }, /** * Fired when the selected state of an appointment is changed. * @since 1.72 */ appointmentSelect: { parameters: { /** * The appointment on which the event was triggered. */ appointment: {type: "sap.ui.unified.CalendarAppointment"}, /** * All appointments with changed selected state. */ appointments : {type : "sap.ui.unified.CalendarAppointment[]"}, /** * The original browser event. */ originalEvent: {type: "object"} } } } }, renderer: SinglePlanningCalendarMonthGridRenderer }); SinglePlanningCalendarMonthGrid.prototype.init = function() { const iCellsInView = 42; this._aLinks = []; this._handleMorePress = this._handleMorePress.bind(this); this._oDateFormat = DateFormat.getDateTimeInstance({ pattern: "YYYYMMdd" }); this._oFormatAriaApp = DateFormat.getDateTimeInstance({ pattern: "EEEE, MMMM d, yyyy 'at' " + this._getCoreLocaleData().getTimePattern("medium") }); this._oFormatAriaFullDayCell = DateFormat.getDateTimeInstance({ pattern: "EEEE, MMMM d, yyyy" }); this.setStartDate(UI5Date.getInstance()); this._configureAppointmentsDragAndDrop(); this._oUnifiedRB = sap.ui.getCore().getLibraryResourceBundle("sap.ui.unified"); this._aMoreCountPerDay = []; this._aMoreCountPerDay.length = iCellsInView; this._aMoreCountPerDay.fill(0); this._oSelectionStartDate = null; this._bReversiveSelection = false; }; SinglePlanningCalendarMonthGrid.prototype._getDateColumn = function(aCells, oNextDate, iColumns) { let iCol = 0; for (let i = 0; i < aCells.length; i++) { if (aCells[i] && oNextDate.isSame(aCells[i])) { break; } iCol++; if (iCol === iColumns) { iCol = 0; } } return iCol; }; SinglePlanningCalendarMonthGrid.prototype._getDateRow = function(aCells, oDate, iColumns) { let iRow = 0; for (let i = 0; i < aCells.length; i++) { if (aCells[i] && oDate.isSame(aCells[i])) { iRow = Math.floor(i / iColumns); break; } iRow += 1; if (iRow === iColumns - 1) { iRow = 0; } } return iRow; }; SinglePlanningCalendarMonthGrid.prototype._getRowEndIndex = function (aDays, iCellIndex, iColumns) { let iRowEndIndex = 0; for (let i = 1; i < aDays.length; i++) { if (iCellIndex <= i * iColumns - 1) { iRowEndIndex = i * iColumns - 1; break; } } return iRowEndIndex; }; SinglePlanningCalendarMonthGrid.prototype.exit = function() { if (this._oItemNavigation) { this.removeDelegate(this._oItemNavigation); this._oItemNavigation.destroy(); delete this._oItemNavigation; } this._aLinks.forEach((oLink) => oLink.destroy()); delete this._aLinks; }; SinglePlanningCalendarMonthGrid.prototype.onBeforeRendering = function() { const oStartDate = this.getStartDate(), aCells = this._getCells(); this._iStartDayOffset = aCells.indexOf(aCells.find((day) => day.getDate() === 1)); this._oAppointmentsToRender = this._calculateAppointmentsNodes(oStartDate); this._createAppointmentsDndPlaceholders(oStartDate); this._oInvisibleMessage = InvisibleMessage.getInstance(); if (this.getFirstDayOfWeek() !== -1 && this.getCalendarWeekNumbering()) { Log.warning("Both properties firstDayOfWeek and calendarWeekNumbering should not be used at the same time!"); } }; SinglePlanningCalendarMonthGrid.prototype._checkDateSelected = function(oDay) { var aSelectedDate = this.getAggregation("selectedDates"); var oRange; var oStartDate; var oEndDate; if (!aSelectedDate) { return false; } for (var i = 0; i < aSelectedDate.length; i++) { oRange = aSelectedDate[i]; oStartDate = oRange.getStartDate() && CalendarDate.fromLocalJSDate(oRange.getStartDate()); oEndDate = oRange.getEndDate() && CalendarDate.fromLocalJSDate(oRange.getEndDate()); if (oStartDate && oDay.isSame(oStartDate) || (oStartDate && oEndDate && oDay.isSameOrAfter(oStartDate) && oDay.isSameOrBefore(oEndDate))) { return true; } } return false; }; /** * Checks whether there are appointments related to a given grid cell * @param {sap.ui.unified.calendar.CalendarDate} oDay The date of the grid cell * @returns {boolean} Indicator if there are appointments realted to the grid cell */ SinglePlanningCalendarMonthGrid.prototype._doesContainAppointments = function(oDay) { return this.getAppointments().some((oAppointment) => { if (oAppointment.getStartDate() && oAppointment.getEndDate()) { const oStartDate = CalendarDate.fromLocalJSDate(oAppointment.getStartDate()); const oEndDate = CalendarDate.fromLocalJSDate(oAppointment.getEndDate()); return oDay.isSameOrAfter(oStartDate) && oDay.isSameOrBefore(oEndDate); } return false; }); }; SinglePlanningCalendarMonthGrid.prototype._getCellDescription = function () { return Core.getLibraryResourceBundle("sap.m").getText("SPC_CELL_DESCRIPTION"); }; SinglePlanningCalendarMonthGrid.prototype.onAfterRendering = function() { this._initItemNavigation(); this._aMoreCountPerDay.fill(0); }; SinglePlanningCalendarMonthGrid.prototype._getColumns = function() { return 7; }; SinglePlanningCalendarMonthGrid.prototype._getRows = function() { return 6; }; SinglePlanningCalendarMonthGrid.prototype._getDateFormatter = function() { return this._oDateFormat; }; SinglePlanningCalendarMonthGrid.prototype._getAppointmetsForADay = function(oDate) { return this._oAppointmentsToRender.filter(function(app) { return app.start.valueOf() === oDate.valueOf(); }); }; SinglePlanningCalendarMonthGrid.prototype._getPreviousAppointmetsForADay = function(oDate) { return this._oAppointmentsToRender.filter(function(app) { return app.start.valueOf() < oDate.valueOf() && app.end.valueOf() >= oDate.valueOf(); }).map(function(app) { // adjust their widths because we could render // them in several weeks separately var newApp = { data: app.data, start: app.start, end: app.end, len: app.len, level: app.level, width: app.width, _nextDay: app._nextDay }; newApp.width -= CalendarUtils._daysBetween(oDate, app.start); newApp.hasPrevious = true; return newApp; }, this); }; /** * Handles the <code>mouseup</code> event on the grid. * * @param {jQuery.Event} oEvent The event object */ SinglePlanningCalendarMonthGrid.prototype.onmouseup = function(oEvent) { if (oEvent.target.classList.contains("sapMSPCMonthWeekNumber")) { const iWeekNumber = Number(oEvent.target.textContent); this.fireWeekNumberPress({weekNumber: iWeekNumber}); if (SinglePlanningCalendarSelectionMode.SingleSelect === this.getDateSelectionMode()) { return; } this._bCurrentWeekSelection = true; } this._fireSelectionEvent(oEvent); }; /** * Handles the <code>mousedown</code> event on the grid. * * @param {jQuery.Event} oEvent The event object */ SinglePlanningCalendarMonthGrid.prototype.onmousedown = function(oEvent) { if (!oEvent.target.classList.contains("sapMSPCMonthWeekNumber")) { return; } const oFirstSiblingElement = oEvent.target.nextSibling.querySelectorAll(".sapMSPCMonthDay")[0]; const iIndex = this._aGridCells.indexOf(oFirstSiblingElement); this._oItemNavigation.focusItem(iIndex); }; SinglePlanningCalendarMonthGrid.prototype._addSelectedDate = function(oDate) { var aSelectedDates = this.getAggregation("selectedDates"); var bDateExists = aSelectedDates && aSelectedDates.some(function(oRange) { return CalendarDate.fromLocalJSDate(oRange.getStartDate()).isSame(CalendarDate.fromLocalJSDate(oDate)); }); if (!bDateExists) { this.addAggregation("selectedDates", new DateRange({ startDate: UI5Date.getInstance(oDate) })); } }; SinglePlanningCalendarMonthGrid.prototype._rangeSelection = function(oStartDate, oEndDate) { var oCurrentDate = UI5Date.getInstance(oStartDate), iDaysInRange = 0, iDaysSelected = 0, bPositiveSelection = true, bTargetContainsSelectedClass = false, bShouldToggleMarkCell, oTarget, oCurrentDateUTCTimestamp; while (oCurrentDate.getTime() < oEndDate.getTime()) { iDaysInRange++; if (this._checkDateSelected(CalendarDate.fromLocalJSDate(oCurrentDate))) { iDaysSelected++; } oCurrentDate.setDate(oCurrentDate.getDate() + 1); } if (iDaysSelected === iDaysInRange) { bPositiveSelection = false; } if (!this._bCurrentWeekSelection && !bPositiveSelection && oStartDate.getTime() < oEndDate.getTime()) { if (this._bReversiveSelection) { oStartDate.setDate(oStartDate.getDate() + 1); } else { oEndDate.setDate(oEndDate.getDate() - 1); } } oCurrentDate = UI5Date.getInstance(oStartDate); while (oCurrentDate.getTime() < oEndDate.getTime()) { oCurrentDateUTCTimestamp = Date.UTC(oCurrentDate.getFullYear(), oCurrentDate.getMonth(), oCurrentDate.getDate()); oTarget = document.querySelector('[sap-ui-date="' + oCurrentDateUTCTimestamp + '"]'); if (oTarget) { bTargetContainsSelectedClass = oTarget.classList.contains("sapMSPCMonthDaySelected"); bShouldToggleMarkCell = (bPositiveSelection && !bTargetContainsSelectedClass) || (!bPositiveSelection && bTargetContainsSelectedClass); if (bShouldToggleMarkCell) { this._toggleMarkCell(oTarget, oCurrentDate); } } else if (bPositiveSelection) { this._addSelectedDate(oCurrentDate); } oCurrentDate.setDate(oCurrentDate.getDate() + 1); } this._bCurrentWeekSelection = false; }; /** * Handles the <code>keydown</code> event when any key is pressed. * * @param {jQuery.Event} oEvent The event object. */ SinglePlanningCalendarMonthGrid.prototype.onkeydown = function(oEvent) { const bArrowNavigation = oEvent.which === KeyCodes.ARROW_UP || oEvent.which === KeyCodes.ARROW_DOWN || oEvent.which === KeyCodes.ARROW_LEFT || oEvent.which === KeyCodes.ARROW_RIGHT; const bMultiDateSelection = SinglePlanningCalendarSelectionMode.MultiSelect === this.getDateSelectionMode(); if (bArrowNavigation && !oEvent.shiftKey && !bMultiDateSelection) { return; } if (oEvent.which === KeyCodes.SPACE || oEvent.which === KeyCodes.ENTER || bArrowNavigation) { if ((oEvent.which === KeyCodes.SPACE || oEvent.which === KeyCodes.ENTER) && !oEvent.shiftKey) { this._fireSelectionEvent(oEvent); } else if ((oEvent.which === KeyCodes.SPACE || oEvent.which === KeyCodes.ENTER) && oEvent.shiftKey && bMultiDateSelection) { this._bCurrentWeekSelection = true; this._fireSelectionEvent(oEvent); } else if (bArrowNavigation && oEvent.shiftKey && bMultiDateSelection) { this._bMultiDateSelectWithArrow = true; this._fireSelectionEvent(oEvent); } var oControl = this._findSrcControl(oEvent); if (oControl && oControl.isA("sap.ui.unified.CalendarAppointment") && !oControl.getSelected()) { this._oInvisibleMessage.announce(this._oUnifiedRB.getText("APPOINTMENT_UNSELECTED"), InvisibleMessageMode.Polite); } // Prevent scrolling oEvent.preventDefault(); } }; /** * @returns {boolean} true if there are any selected appointments * @private */ SinglePlanningCalendarMonthGrid.prototype._hasSelectedAppointments = function() { return this.getAppointments().some((oAppointment) => oAppointment.getSelected()); }; SinglePlanningCalendarMonthGrid.prototype._isSelectAppointment = function (oEvent) { return oEvent.target.classList.contains("sapUiCalendarRowApps") || (oEvent.target.parentElement && oEvent.target.parentElement.classList.contains("sapUiCalendarRowApps")); }; SinglePlanningCalendarMonthGrid.prototype._findSelectedAppointment = function (oTarget) { // data-sap-ui-related - This is a relation to appointment object. // This is the connection between the DOM Element and the Object representing an appointment. var oParentElement = oTarget.parentElement, sAppointmentId; if (oParentElement.classList.contains("sapUiCalendarRowApps")) { sAppointmentId = oParentElement.getAttribute("data-sap-ui-related") || oParentElement.id; } else { sAppointmentId = oTarget.getAttribute("data-sap-ui-related") || oTarget.id; } // finding the appointment return this.getAppointments().find(function (oAppointment) { return oAppointment.sId === sAppointmentId; }); }; SinglePlanningCalendarMonthGrid.prototype._findSelectedRow = function (oEvent) { var oSelectedCell = this._findSelectCell(oEvent.target), oRow = oSelectedCell.parentElement; if (!oRow || oRow.classList.contains("sapMSPCMonthDays")) { return oEvent.srcControl; } }; SinglePlanningCalendarMonthGrid.prototype._findSelectCell = function (target) { if (target.classList.contains("sapMSPCMonthDayNumber") || target.classList.contains("specialDateIndicator")){ return target.parentElement; } return target; }; SinglePlanningCalendarMonthGrid.prototype._findSrcControl = function (oEvent) { var oTarget = oEvent.target; if (this._isSelectAppointment(oEvent)) { return this._findSelectedAppointment(oTarget); } return this._findSelectedRow(oEvent); }; SinglePlanningCalendarMonthGrid.prototype._handleMultiDateSelection = function (oTarget, oStartDate, oEndDate, oEvent, bShiftSelection) { const aOldSelectedDateState = this.getAggregation("selectedDates"); const bMultiDateSelection = SinglePlanningCalendarSelectionMode.MultiSelect === this.getDateSelectionMode() && !bShiftSelection; const bSingleDateSelect = !this._bMultiDateSelectWithArrow && !this._bCurrentWeekSelection && !bShiftSelection; const bShiftClickSelection = bShiftSelection && !!this._oSelectionStartDate; const bWeekSelection = this._bCurrentWeekSelection && bMultiDateSelection; this._bReversiveSelection = false; // swap dates if necessary if (oStartDate.getTime() > oEndDate.getTime()) { [oStartDate, oEndDate] = [oEndDate, oStartDate]; this._oSelectionStartDate = UI5Date.getInstance(oStartDate); this._bReversiveSelection = true; } else { this._oSelectionStartDate = UI5Date.getInstance(oEndDate); } oEndDate.setDate(oEndDate.getDate() + 1); if (((oEvent.which === KeyCodes.SPACE || oEvent.which === KeyCodes.ENTER) && !oEvent.shiftKey) || (!bMultiDateSelection && !(oEvent.metaKey || oEvent.ctrlKey || bShiftClickSelection))) { this.removeAllAggregation("selectedDates"); } if (bSingleDateSelect && !bShiftSelection) { this._toggleMarkCell(oTarget, oStartDate); } else if (this._bMultiDateSelectWithArrow){ this._bMultiDateSelectWithArrow = false; var oDate = UI5Date.getInstance(CalendarDate.fromLocalJSDate(oStartDate)); switch (oEvent.which) { case KeyCodes.ARROW_UP: oDate.setDate(oDate.getDate() - 7); break; case KeyCodes.ARROW_DOWN: oDate.setDate(oDate.getDate() + 7); break; case KeyCodes.ARROW_LEFT: oDate.setDate(oDate.getDate() - 1); break; case KeyCodes.ARROW_RIGHT: oDate.setDate(oDate.getDate() + 1); break; default: break; } oTarget = document.querySelector('[sap-ui-date="' + oDate.getTime() + '"]'); oStartDate = UI5Date.getInstance(oDate.getTime()); oStartDate = UI5Date.getInstance(oDate.getUTCFullYear(), oStartDate.getUTCMonth(), oStartDate.getUTCDate()); this._oSelectionStartDate = UI5Date.getInstance(oStartDate); this._toggleMarkCell(oTarget, oStartDate); } else if (bWeekSelection){ this._oSelectionStartDate = null; var iStartDate = oStartDate.getDate(), oWeekConfigurationValues = CalendarDateUtils.getWeekConfigurationValues(this.getCalendarWeekNumbering(), new Locale(new Locale(Formatting.getLanguageTag()).toString())), iAPIFirstDayOfWeek = this.getFirstDayOfWeek(), iFirstDayOfWeek, iWeekStartDate; if (iAPIFirstDayOfWeek < 0 || iAPIFirstDayOfWeek > 6) { if (oWeekConfigurationValues) { iFirstDayOfWeek = oWeekConfigurationValues.firstDayOfWeek; } else { var oLocaleData = this._getCoreLocaleData(); iFirstDayOfWeek = oLocaleData.getFirstDayOfWeek(); } } else { iFirstDayOfWeek = iAPIFirstDayOfWeek; } iWeekStartDate = iStartDate - oStartDate.getDay() + iFirstDayOfWeek; if (iWeekStartDate > iStartDate) { iWeekStartDate -= 7; } oStartDate.setDate(iWeekStartDate); oEndDate = UI5Date.getInstance(oStartDate); oEndDate.setDate(oStartDate.getDate() + 7); this._rangeSelection(oStartDate, oEndDate); } else if (bShiftSelection) { this._rangeSelection(oStartDate, oEndDate); } this._fireSelectionChange(aOldSelectedDateState); }; SinglePlanningCalendarMonthGrid.prototype._fireSelectionChange = function (aOldSelectedDateState) { const bExecuteDefault = this.fireSelectedDatesChange({ selectedDates: this.getAggregation("selectedDates") }); if (!bExecuteDefault) { this.removeAllAggregation("selectedDates"); aOldSelectedDateState.forEach((oRange) => this.addAggregation("selectedDates", oRange)); } }; /** * Helper function handling <code>keydown</code> or <code>tap</code> event on the grid. * * @param {jQuery.Event} oEvent The event object. */ SinglePlanningCalendarMonthGrid.prototype._fireSelectionEvent = function (oEvent) { const oSrcControl = this._findSrcControl(oEvent), oTarget = oEvent.target, oSelectedCell = this._findSelectCell(oEvent.target), bIsCell = oTarget && !!oSelectedCell, bIsLink = oTarget && oTarget.classList.contains("sapMLnk"), bWeekNumberSelect = oTarget && oTarget.classList.contains("sapMSPCMonthWeekNumber"); if ((oSrcControl && oSrcControl.isA("sap.m.SinglePlanningCalendarMonthGrid") && bIsCell && !bIsLink) || bWeekNumberSelect) { this._lastPressedAppointment = undefined; this._fireGridCellSelectionEvent(oEvent, bWeekNumberSelect); // deselect all appointments if (this._hasSelectedAppointments()) { this.fireAppointmentSelect({ appointment: undefined, appointments: this._toggleAppointmentSelection(undefined, true), originalEvent: oEvent.originalEvent }); } } else if (oSrcControl && oSrcControl.isA("sap.ui.unified.CalendarAppointment")) { this._lastPressedAppointment = oSrcControl; const bCtrlKeyOrMetaKey = oEvent.ctrlKey || oEvent.metaKey; this._fireAppointmentSelection(oTarget, oSrcControl, bCtrlKeyOrMetaKey, oEvent); } }; SinglePlanningCalendarMonthGrid.prototype._fireAppointmentSelection = function (oTarget, oSrcControl, bCtrlKeyOrMetaKey, oEvent) { // add suffix in appointment if (oTarget.parentElement && oTarget.parentElement.getAttribute("id")) { var sTargetId = oTarget.parentElement.getAttribute("id"); // data-sap-ui-related - This is a relation to appointment object. // This is the connection between the DOM Element and the Object representing an appointment. var sBaseIDPart = oTarget.parentElement.getAttribute("data-sap-ui-related"); var sSuffix = sTargetId.replace(sBaseIDPart + "-", ""); oSrcControl._setAppointmentPartSuffix(sSuffix); } this.fireAppointmentSelect({ appointment: oSrcControl, appointments: this._toggleAppointmentSelection(oSrcControl, !bCtrlKeyOrMetaKey), originalEvent: oEvent.originalEvent }); }; SinglePlanningCalendarMonthGrid.prototype._fireGridCellSelectionEvent = function (oEvent, bWeekNumberSelect) { const oSelectedCell = bWeekNumberSelect ? oEvent.target.nextSibling.querySelectorAll(".sapMSPCMonthDay")[0] : this._findSelectCell(oEvent.target); const iTimestamp = parseInt(oSelectedCell.getAttribute("sap-ui-date")); let oStartDate = UI5Date.getInstance(iTimestamp); oStartDate = UI5Date.getInstance(oStartDate.getUTCFullYear(), oStartDate.getUTCMonth(), oStartDate.getUTCDate()); let oEndDate = UI5Date.getInstance(oStartDate); let bShiftSelection = false; if (oEvent.ctrlKey || oEvent.metaKey || bWeekNumberSelect || this.getDateSelectionMode() === SinglePlanningCalendarSelectionMode.MultiSelect) { if (this._oSelectionStartDate && oEvent.type !== "keydown" && oEvent.shiftKey && !bWeekNumberSelect) { // Shift selection in multiple mode oEndDate = UI5Date.getInstance(oStartDate); oStartDate = UI5Date.getInstance(this._oSelectionStartDate); bShiftSelection = true; } else { // Ctrl+click in single mode and click in multiple mode, Shift+Arrow in multiple mode this._oSelectionStartDate = UI5Date.getInstance(oStartDate); } } else if (!oEvent.shiftKey || !this._oSelectionStartDate) { // Single click in single mode this._oSelectionStartDate = UI5Date.getInstance(oStartDate); } else { // Shift selection in single mode oEndDate = UI5Date.getInstance(oStartDate); oStartDate = UI5Date.getInstance(this._oSelectionStartDate); bShiftSelection = true; } this._handleMultiDateSelection(oSelectedCell, oStartDate, oEndDate, oEvent, bShiftSelection); // eslint-disable-next-line no-unused-expressions !bWeekNumberSelect && this.fireEvent("cellPress", {startDate: oStartDate, endDate: oEndDate, originalEvent: oEvent.originalEvent}); }; SinglePlanningCalendarMonthGrid.prototype._toggleMarkCell = function (oTarget, oDay) { if (oTarget && !oTarget.classList.contains("sapMSPCMonthDaySelected")) { this._addSelectedDate(oDay); } else { var aSelectedDates = this.getAggregation("selectedDates"); if (!aSelectedDates) { return; } for (var i = 0; i < aSelectedDates.length; i++){ var oSelectStartDate = aSelectedDates[i].getStartDate(); if (CalendarDate.fromLocalJSDate(oSelectStartDate).isSame(CalendarDate.fromLocalJSDate(oDay))) { this.removeAggregation("selectedDates", i); break; } } } }; /** * Selects or deselects an appointment that is passed as a parameter. If it is selected, it is going to be * deselected and vice versa. If modifier keys are pressed - the previously selected appointments will be * preserved. * * @param {sap.ui.unified.CalendarAppointment} oAppointment The appointment to be selected/deselected. * @param {boolean} [bRemoveOldSelection=false] If true, previously selected appointments will be deselected. * @returns {array} Array of the appointments with changed selected state * @private */ SinglePlanningCalendarMonthGrid.prototype._toggleAppointmentSelection = function (oAppointment, bRemoveOldSelection) { var aChangedApps = [], aAppointments, iAppointmentsLength, i; if (bRemoveOldSelection) { aAppointments = this.getAppointments(); for (i = 0, iAppointmentsLength = aAppointments.length; i < iAppointmentsLength; i++) { // Deselecting all selected appointments if a grid cell is focused or // all selected appointments different than the currently focused appointment if ( (!oAppointment || aAppointments[i].getId() !== oAppointment.getId()) && aAppointments[i].getSelected()) { aAppointments[i].setProperty("selected", false); aChangedApps.push(aAppointments[i]); } } } if (oAppointment) { oAppointment.setProperty("selected", !oAppointment.getSelected()); aChangedApps.push(oAppointment); } return aChangedApps; }; SinglePlanningCalendarMonthGrid.prototype._getMoreLink = function(iAppointmentsCount, oCalendarDate, iCellIndex, sMoreLinkDescId) { var sMore = Core .getLibraryResourceBundle("sap.m") .getText("SPC_MORE_LINK", [iAppointmentsCount.toString()]), oLink = new Link({ accessibleRole: LinkAccessibleRole.Button, ariaLabelledBy: [sMoreLinkDescId], text: sMore, press: this._handleMorePress }).addCustomData(new CustomData({ key: "date", value: oCalendarDate.valueOf().toString(), writeToDom: true })); if (this._aLinks[iCellIndex]) { this._aLinks[iCellIndex].destroy(); } this._aLinks[iCellIndex] = oLink; return oLink; }; SinglePlanningCalendarMonthGrid.prototype._getMoreLinkOnAfterRenderingDelegate = function (oLink) { return { onAfterRendering: function() { const oLinkDomRef = oLink.getDomRef(); const sDescriptionId = oLinkDomRef.getAttribute("aria-labelledby").split(" ")[0]; oLinkDomRef.setAttribute("aria-labelledby", sDescriptionId); } }; }; SinglePlanningCalendarMonthGrid.prototype._getMoreLinkDescription = function (iAppointmentsCount, oCalendarDate) { const sFormattedString = this._oFormatAriaFullDayCell.format(oCalendarDate); const oBundle = Core.getLibraryResourceBundle("sap.m"); return iAppointmentsCount === 1 ? oBundle.getText("SPC_MORE_LINK_ONE_APPOINTMENT", [sFormattedString]) : oBundle.getText("SPC_MORE_LINK_MULTIPLE_APPOINTMENTS", [iAppointmentsCount.toString(), sFormattedString]); }; SinglePlanningCalendarMonthGrid.prototype._handleMorePress = function(oEvent) { var iTimestamp = parseInt(oEvent.getSource().getCustomData()[0].getValue()), oDate = UI5Date.getInstance(iTimestamp); oDate = UI5Date.getInstance(oDate.getUTCFullYear(), oDate.getUTCMonth(), oDate.getUTCDate()); this.fireEvent("moreLinkPress", { date: oDate, sourceLink: oEvent.getSource() }); }; SinglePlanningCalendarMonthGrid.prototype._getCoreLocaleData = function() { var sLocale = Core.getConfiguration().getFormatSettings().getFormatLocale().toString(), oLocale = new Locale(sLocale); return LocaleData.getInstance(oLocale); }; SinglePlanningCalendarMonthGrid.prototype._getCells = function() { return this._getVisibleDays(this.getStartDate()); }; SinglePlanningCalendarMonthGrid.prototype._getVerticalLabels = function() { var aDays = this._getVisibleDays(this.getStartDate()), iColumns = this._getColumns(), aResult = [], sLocale = new Locale(Formatting.getLanguageTag()).toString(), oStartDate = new CalendarDate(this.getStartDate().getFullYear(), this.getStartDate().getMonth(), this.getStartDate().getDate()).toUTCJSDate(), iWeekNumber; for (var i = 0; i < this._getRows(); i++) { var oDateFormat = DateFormat.getInstance({pattern: "w", calendarType: CalendarType.Gregorian, calendarWeekNumbering: this.getCalendarWeekNumbering()}, new Locale(sLocale)); var iFirstWeekDayInMonth = aDays[i * iColumns].toUTCJSDate(); if (iFirstWeekDayInMonth < oStartDate) { iFirstWeekDayInMonth = oStartDate; } iWeekNumber = Number(oDateFormat.format(iFirstWeekDayInMonth, true)); aResult.push(iWeekNumber); } return aResult; }; SinglePlanningCalendarMonthGrid.prototype._getVisibleDays = function(oStartDate) { var oCalStartDate, oDay, oCalDate, iDaysOldMonth, oFirstDay, iFirstDayOfWeek, aVisibleDays = []; // If date passed generate days for new start date else return the current one if (!oStartDate) { return aVisibleDays; } iFirstDayOfWeek = this._getFirstDayOfWeek(); oCalStartDate = CalendarDate.fromLocalJSDate(oStartDate); // determine weekday of first day in month oFirstDay = new CalendarDate(oCalStartDate); oFirstDay.setDate(1); iDaysOldMonth = oFirstDay.getDay() - iFirstDayOfWeek; if (iDaysOldMonth < 0) { iDaysOldMonth = 7 + iDaysOldMonth; } if (iDaysOldMonth > 0) { // determine first day for display oFirstDay.setDate(1 - iDaysOldMonth); } oDay = new CalendarDate(oFirstDay); for (var i = 0; i < this._getColumns() * this._getRows(); i++) { oCalDate = new CalendarDate(oDay); aVisibleDays.push(oCalDate); oDay.setDate(oDay.getDate() + 1); } return aVisibleDays; }; SinglePlanningCalendarMonthGrid.prototype._getFirstDayOfWeek = function() { var oWeekConfigurationValues, oLocaleData; if (this.getFirstDayOfWeek() < 0 || this.getFirstDayOfWeek() > 6) { oWeekConfigurationValues = CalendarDateUtils.getWeekConfigurationValues( this.getCalendarWeekNumbering(), new Locale(new Locale(Formatting.getLanguageTag()).toString()) ); if (oWeekConfigurationValues) { return oWeekConfigurationValues.firstDayOfWeek; } else { oLocaleData = this._getCoreLocaleData(); return oLocaleData.getFirstDayOfWeek(); } } else { return this.getFirstDayOfWeek(); } }; SinglePlanningCalendarMonthGrid.prototype._getAppointmentsToRender = function() { return this._oAppointmentsToRender; }; SinglePlanningCalendarMonthGrid.prototype._getFirstAndLastVisibleDates = function() { const aVisibleDays = this._getVisibleDays(this.getStartDate()); return { oStartDate: aVisibleDays[0].toLocalJSDate(), oEndDate: aVisibleDays[aVisibleDays.length - 1].toLocalJSDate() }; }; SinglePlanningCalendarMonthGrid.prototype._calculateAppointmentsNodes = function(oStartDate) { var aVisibleDays = this._getVisibleDays(oStartDate), oFirstVisibleDay = aVisibleDays[0], oLastVisibleDay = aVisibleDays[aVisibleDays.length - 1], // We do not need appointments without start and end dates aApps = this.getAppointments().filter(function(app) { var bValid = app.getStartDate() && app.getEndDate(); if (!bValid) { Log.warning("Appointment " + app.getId() + " has no start or no end date. It is ignored."); } return bValid; // Map to a structure ready for calculations }).map(function(app) { var oStart = CalendarDate.fromLocalJSDate(app.getStartDate()), oEnd = CalendarDate.fromLocalJSDate(app.getEndDate()); return { data: app, start: oStart, end: oEnd, len: CalendarUtils._daysBetween(oEnd, oStart) }; // Get only the visible appointments }).filter(function(app) { return CalendarUtils._isBetween(app.start, oFirstVisibleDay, oLastVisibleDay, true) // app starts in the current view port || CalendarUtils._isBetween(app.end, oFirstVisibleDay, oLastVisibleDay, true) // app ends in the current view port || (CalendarUtils._isBetween(oFirstVisibleDay, app.start, oLastVisibleDay, true) // app starts before the view port... && CalendarUtils._isBetween(oLastVisibleDay, oFirstVisibleDay, app.end,true)); // ...and ends after the view port // Sort by start date }).sort(function compare(a, b) { return a.data.getStartDate().getTime() - b.data.getStartDate().getTime(); }), // Array of taken levels per visible day aVisibleDaysLevels = [], aDays = this._getVisibleDays(oStartDate), iMaxAppointmentsRendered = this._getMaxAppointments(), iNextFreeDayIndex, iNextFreeLevelIndex, aFreeDayAndLevelIndexes, bNextLevelIsWithinBounds, bHasSpaceToRender, oApp, iAppointmentStartIndex, iAppStartIndex, iAppointmentEndIndex, iFirstFreeIndex; for (let i = 0; i < aVisibleDays.length; i++) { aVisibleDaysLevels.push([]); } // Each appointment gets its width and level for (let i = 0; i < aApps.length; i++) { oApp = aApps[i]; iAppointmentStartIndex = CalendarUtils._daysBetween(oApp.start, aVisibleDays[0]); iAppointmentEndIndex = iAppointmentStartIndex + oApp.len; iAppStartIndex = this._findStartDateIndex(aDays, oApp, this._iStartDayOffset); // If appointment is out of bounds, set it in bounds iAppointmentStartIndex = iAppointmentStartIndex > 0 ? iAppointmentStartIndex : 0; iAppointmentEndIndex = iAppointmentEndIndex < aVisibleDays.length ? iAppointmentEndIndex : aVisibleDays.length - 1; oApp.width = oApp.len + 1; // Find the first level that is not taken for the start date of the appointment iFirstFreeIndex = aVisibleDaysLevels[iAppointmentStartIndex].indexOf(true); if (iFirstFreeIndex === -1) { iFirstFreeIndex = aVisibleDaysLevels[iAppointmentStartIndex].length; } // Rendered position of appointment in day aFreeDayAndLevelIndexes = this._findNextFreeDayAndLevel(oApp, aVisibleDaysLevels, iAppointmentStartIndex, iFirstFreeIndex); iNextFreeDayIndex = aFreeDayAndLevelIndexes.freeDayIndex; iNextFreeLevelIndex = aFreeDayAndLevelIndexes.freeLevelIndex; oApp.level = iNextFreeLevelIndex; oApp._nextDay = iNextFreeDayIndex; // Adjust the taken levels for all days of the current appointment if (oApp.len && iNextFreeDayIndex > iAppStartIndex) { oApp._overflows = true; } else { oApp._overflows = false; if (oApp._nextDay > -1) { oApp._nextDay = iAppointmentStartIndex; } } if (oApp._nextDay > -1) { bHasSpaceToRender = true; } if (bHasSpaceToRender && oApp._overflows && oApp.len && oApp.level < iMaxAppointmentsRendered) { oApp._nextDayLevel = iNextFreeLevelIndex; oApp._nextDay = iNextFreeDayIndex; if (bHasSpaceToRender) { for (let q = 0; q <= oApp.width; q++) { bNextLevelIsWithinBounds = iNextFreeDayIndex + q <= iAppointmentEndIndex; if (bNextLevelIsWithinBounds) { aVisibleDaysLevels[iNextFreeDayIndex + q][iNextFreeLevelIndex] = true; } } } for (let q = this._findStartDateIndex(aDays, oApp, this._iStartDayOffset); q < iNextFreeDayIndex; q++) { if (q > -1) { this._aMoreCountPerDay[q] += 1; } } } else { aFreeDayAndLevelIndexes = this._findNextFreeDayAndLevel(oApp, aVisibleDaysLevels, iAppointmentStartIndex, iFirstFreeIndex); iFirstFreeIndex = aFreeDayAndLevelIndexes.freeLevelIndex; oApp.level = iFirstFreeIndex; for (let j = iAppointmentStartIndex; j <= iAppointmentEndIndex; j++) { aVisibleDaysLevels[j][iFirstFreeIndex] = true; if (!oApp._nextDay && oApp._nextDay !== 0 || iFirstFreeIndex >= iMaxAppointmentsRendered - 1) { this._aMoreCountPerDay[j] += 1; } } } } this._aAppsLevelsPerDay = aVisibleDaysLevels; return aApps; }; SinglePlanningCalendarMonthGrid.prototype._findStartDateIndex = function (aDays, oApp) { const oDayInDays = aDays.find( (oDay) => oDay.isSame(oApp.start) ); return aDays.indexOf(oDayInDays); }; SinglePlanningCalendarMonthGrid.prototype._findNextFreeDayAndLevel = function (oApp, aVisibleDaysLevels, iAppointmentStartIndex, iDefaultLevel) { const iMaxAppointmentLevels = this._isCompact() ? 3 : 2; let iNextFreeLevelIndex = iDefaultLevel, iNextFreeDayIndex, aDayLevels, bHasFreeFirstOrSecondLevel, bHasFreeThirdLevel, bLevelIsFree; for (let i = 0; i < oApp.width; i++) { aDayLevels = aVisibleDaysLevels[iAppointmentStartIndex + i]; if (!aDayLevels) { break; } bHasFreeFirstOrSecondLevel = aDayLevels && !aDayLevels[0] || !aDayLevels[1]; bHasFreeThirdLevel = aDayLevels && !aDayLevels[2] && this._isCompact(); if (bHasFreeFirstOrSecondLevel || bHasFreeThirdLevel) { iNextFreeDayIndex = iAppointmentStartIndex + i; for (let j = 0; j < iMaxAppointmentLevels; j++) { bLevelIsFree = !aDayLevels[j]; if (bLevelIsFree) { iNextFreeDayIndex = iAppointmentStartIndex + i; iNextFreeLevelIndex = j; break; } } break; } else { iNextFreeLevelIndex = aDayLevels.length; } } return { freeDayIndex: iNextFreeDayIndex, freeLevelIndex: iNextFreeLevelIndex }; }; SinglePlanningCalendarMonthGrid.prototype._getMoreCountPerCell = function(iCellIndex) { var aLevelsForADay = this._aAppsLevelsPerDay[iCellIndex]; var iMaxAppointmentsRendered = this._getMaxAppointments(); var iMoreCount = 0; if (aLevelsForADay.length < iMaxAppointmentsRendered) { return 0; } // Count the number of hidden appointments for (var i = iMaxAppointmentsRendered - 1; i < aLevelsForADay.length; i++){ if (!aLevelsForADay[i]) { iMoreCount++; } } return iMoreCount; }; SinglePlanningCalendarMonthGrid.prototype._configureAppointmentsDragAndDrop = function() { this.addDragDropConfig(new DragDropInfo({ sourceAggregation: "appointments", targetAggregation: "_appsPlaceholders", dragStart: function(oEvent) { if (!this.getEnableAppointmentsDragAndDrop()) { oEvent.preventDefault(); return false; } var fnHandleAppsOverlay = function() { var $Overlay = jQuery(".sapMSinglePCOverlay"); setTimeout(function() { $Overlay.addClass("sapMSinglePCOverlayDragging"); }); jQuery(document).one("dragend", function() { $Overlay.removeClass("sapMSinglePCOverlayDragging"); }); }; fnHandleAppsOverlay(); }.bind(this), dragEnter: function(oEvent) { var oDragSession = oEvent.getParameter("dragSession"), fnAlignIndicator = function() { var $Indicator = jQuery(oDragSession.getIndicator()); $Indicator.css("min-height", oDragSession.getDropControl().$().outerHeight()); $Indicator.css("min-width", oDragSession.getDropControl().$().outerWidth()); }; if (!oDragSession.getIndicator()) { setTimeout(fnAlignIndicator, 0); } else { fnAlignIndicator(); } }, drop: function(oEvent) { var oDragSession = oEvent.getParameter("dragSession"), oAppointment = oDragSession.getDragControl(), oPlaceholder = oDragSession.getDropControl(), oCellCalStartDate = oPlaceholder.getDate(), oAppCalStartDate = CalendarDate.fromLocalJSDate(oAppointment.getStartDate()), oAppCalEndDate = CalendarDate.fromLocalJSDate(oAppointment.getEndDate()), iOffset = CalendarUtils._daysBetween(oCellCalStartDate, oAppCalStartDate), oStartDate = new CalendarDate(oAppCalStartDate), oEndDate = new CalendarDate(oAppCalEndDate), oBrowserEvent = oEvent.getParameter("browserEvent"), bCopy = (oBrowserEvent.metaKey || oBrowserEvent.ctrlKey); oStartDate.setDate(oStartDate.getDate() + iOffset); oEndDate.setDate(oEndDate.getDate() + iOffset); this.$().find(".sapMSinglePCOverlay").removeClass("sapMSinglePCOverlayDragging"); if (oAppCalStartDate.valueOf() === oCellCalStartDate.valueOf()) { return; } this.fireAppointmentDrop({ appointment: oAppointment, startDate: oStartDate.toLocalJSDate(), endDate: oEndDate.toLocalJSDate(), copy: bCopy }); }.bind(this) })); }; SinglePlanningCalendarMonthGrid.prototype._initItemNavigation = function() { // Collect the dom references of the items var oRootRef = this.getDomRef(); this._aGridCells = this.$().find(".sapMSPCMonthDay").toArray(); // Initialize the delegate and apply it to the control (only once) if (!this._oItemNavigation) { this._oItemNavigation = new ItemNavigation(); this.addDelegate(this._oItemNavigation); this._oItemNavigation.attachEvent(ItemNavigation.Events.BorderReached, this._itemNavigationBorderReached, this); } // After each rendering the delegate needs to be initialized as well // Set the root dom node that surrounds the items this._oItemNavigation.setRootDomRef(oRootRef); // Set the array of dom nodes representing the items this._oItemNavigation.setItemDomRefs(this._aGridCells); // Turn off the cycling this._oItemNavigation.setCycling(false); //this way we do not hijack the browser back/forward navigation this._oItemNavigation.setDisabledModifiers({ sapnext: ["alt", "me