UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,316 lines (1,133 loc) 90.7 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides control sap.m.SinglePlanningCalendarGrid. sap.ui.define([ './SinglePlanningCalendarUtilities', './library', 'sap/ui/unified/DateRange', 'sap/ui/core/Control', 'sap/ui/core/LocaleData', 'sap/ui/core/Locale', 'sap/ui/core/InvisibleText', 'sap/ui/core/format/DateFormat', 'sap/ui/core/format/TimezoneUtil', 'sap/ui/core/Core', 'sap/ui/core/date/UniversalDate', 'sap/ui/core/dnd/DragDropInfo', 'sap/ui/unified/library', 'sap/ui/unified/calendar/DatesRow', 'sap/ui/unified/calendar/CalendarDate', 'sap/ui/unified/calendar/CalendarUtils', 'sap/ui/unified/DateTypeRange', 'sap/ui/events/KeyCodes', './SinglePlanningCalendarGridRenderer', 'sap/ui/core/delegate/ItemNavigation', "sap/ui/thirdparty/jquery", './PlanningCalendarLegend', 'sap/ui/core/InvisibleMessage', 'sap/ui/core/library', "sap/ui/core/date/CalendarUtils", "sap/ui/core/Configuration", "sap/ui/core/date/UI5Date" ], function ( SinglePlanningCalendarUtilities, library, DateRange, Control, LocaleData, Locale, InvisibleText, DateFormat, TimezoneUtil, Core, UniversalDate, DragDropInfo, unifiedLibrary, DatesRow, CalendarDate, CalendarUtils, DateTypeRange, KeyCodes, SinglePlanningCalendarGridRenderer, ItemNavigation, jQuery, PlanningCalendarLegend, InvisibleMessage, coreLibrary, CalendarDateUtils, Configuration, UI5Date ) { "use strict"; var ROW_HEIGHT_COZY = 4.3125, // Unit in rem, equals 69px with default font size ROW_HEIGHT_COMPACT = 3, // Unit in rem, equals 48px with default font size BLOCKER_ROW_HEIGHT_COZY = 2.125, // Unit in rem, equals 34px with default font size BLOCKER_ROW_HEIGHT_COMPACT = 1.5625, // Unit in rem, equals 25px with default font size HALF_HOUR_MS = 3600000 / 2, ONE_MIN_MS = 60 * 1000, // Day view only - indicates the special dates // 3px height the marker itself + 2x2px on its top and bottom both on cozy & compact DAY_MARKER_HEIGHT = 0.4375, // Unit in rem, equals 7px with default font size FIRST_HOUR_OF_DAY = 0, LAST_HOUR_OF_DAY = 24, InvisibleMessageMode = coreLibrary.InvisibleMessageMode, CalendarType = coreLibrary.CalendarType, SinglePlanningCalendarSelectionMode = library.SinglePlanningCalendarSelectionMode; /** * Constructor for a new SinglePlanningCalendarGrid. * * @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 in which appointments of the {@link sap.m.SinglePlanningCalendar} are rendered. * * <h3>Overview</h3> * * <b>Note:</b> The <code>PlanningCalendarGrid</code> uses parts of the <code>sap.ui.unified</code> library. * This library will be loaded after the <code>PlanningCalendarGrid</code>, if it wasn't previously loaded. * This could lead to a waiting time when a <code>PlanningCalendarGrid</code> is used for the first time. * To prevent this, apps using the <code>PlanningCalendarGrid</code> must also load the * <code>sap.ui.unified</code> library. * * The <code>PlanningCalendarGrid</code> has the following structure: * * <ul> * <li>Each column in the grid represents a single entity of the view type. For example in the week view one * column represents a week day.</li> * <li>Each row represents an hour from each day.</li> * <li>There are also appointments displayed across the grid. To display an all-day appointment, the * appointment must start at 00:00 and end on any day in the future in 00:00h.</li> * </ul> * * @extends sap.ui.core.Control * * @author SAP SE * @version 1.117.4 * * @constructor * @private * @since 1.61 * @alias sap.m.SinglePlanningCalendarGrid */ var SinglePlanningCalendarGrid = Control.extend("sap.m.SinglePlanningCalendarGrid", /** @lends sap.m.SinglePlanningCalendarGrid.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 the start hour of the grid to be shown if the <code>fullDay</code> property is set to * <code>false</code>. Otherwise the previous hours are displayed as non-working. The passed hour is * considered as 24-hour based. */ startHour: {type: "int", group: "Data", defaultValue: 0}, /** * Determines the end hour of the grid to be shown if the <code>fullDay</code> property is set to * <code>false</code>. Otherwise the next hours are displayed as non-working. The passed hour is * considered as 24-hour based. */ endHour: {type: "int", group: "Data", defaultValue: 24}, /** * Determines if all of the hours in a day are displayed. If set to <code>false</code>, the hours shown are * between the <code>startHour</code> and <code>endHour</code>. */ fullDay: {type: "boolean", group: "Data", defaultValue: true}, /** * 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. * * @since 1.64 */ enableAppointmentsDragAndDrop: { type: "boolean", group: "Misc", defaultValue: false }, /** * Determines whether the appointments are resizable. * * The resize interaction is visualized by making the appointment transparent. * * The appointment snaps on every interval * of 30 minutes. After the resize is finished, the {@link #event:appointmentResize appointmentResize} event is fired, containing * the new start and end UI5Date or JavaScript Date objects. * * @since 1.65 */ enableAppointmentsResize: { type: "boolean", group: "Misc", defaultValue: false }, /** * Determines whether the appointments can be created by dragging on empty cells. * * See {@link #property:enableAppointmentsResize enableAppointmentsResize} for the specific points for events snapping * * @since 1.65 */ enableAppointmentsCreate: { type: "boolean", group: "Misc", defaultValue: false }, /** * Determines scale factor for the appointments. * * Acceptable range is from 1 to 6. * @since 1.99 */ scaleFactor: {type: "float", group: "Data", 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.ui.core.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, will be displayed in all of the affected days. * An appointment which starts at 00:00 and ends in 00:00 on any day in the future is displayed as an all-day * appointment. */ 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> If one day is assigned to more than one type, only the first type is used. * @since 1.66 */ specialDates : {type : "sap.ui.unified.DateTypeRange", multiple : true, singularName : "specialDate"}, /** * Hidden, for internal use only. * The date row which shows the header of the columns in the <code>SinglePlanningCalendarGrid</code>. * * @private */ _columnHeaders: {type: "sap.ui.unified.calendar.DatesRow", multiple: false, visibility: "hidden"}, _intervalPlaceholders : {type : "sap.m.SinglePlanningCalendarGrid._internal.IntervalPlaceholder", multiple : true, visibility : "hidden", dnd : {droppable: true}}, _blockersPlaceholders : {type : "sap.m.SinglePlanningCalendarGrid._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 controls / IDs which label this control (see WAI-ARIA attribute aria-labelledby). * * <b>Note</b> These labels are also assigned to the appointments. */ ariaLabelledBy: {type: "sap.ui.core.Control", multiple: true, singularName: "ariaLabelledBy"}, /** * 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. * @since 1.66.0 */ legend: { type: "sap.m.PlanningCalendarLegend", multiple: false} }, events: { /** * Fired when the selected state of an appointment is changed. */ appointmentSelect: { parameters: { /** * The appointment on which the event was triggered. */ appointment: {type: "sap.ui.unified.CalendarAppointment"}, /** * All appointments with changed selected state. * @since 1.67.0 */ appointments : {type : "sap.ui.unified.CalendarAppointment[]"} } }, /** * Fired if an appointment is dropped. * @since 1.64 */ 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"}, /** * Dropped appointment end date as a UI5Date or JavaScript Date object. */ endDate : {type : "object"}, /** * The drop type. If true - it's "Copy", if false - it's "Move". */ copy : {type : "boolean"} } }, /** * Fired if an appointment is resized. * @since 1.65 */ appointmentResize: { parameters: { /** * The resized appointment. */ appointment: { type: "sap.ui.unified.CalendarAppointment" }, /** * Start date of the dropped appointment, as a UI5Date or JavaScript Date object. */ startDate: { type: "object" }, /** * Dropped appointment end date as a UI5Date or JavaScript Date object. */ endDate: { type: "object" } } }, /** * Fired if an appointment is created. * @since 1.65 */ appointmentCreate: { parameters: { /** * Start date of the created appointment, as a UI5Date or JavaScript Date object. */ startDate: {type: "object"}, /** * End date of the created appointment, as a UI5Date or JavaScript Date object. */ endDate: {type: "object"} } }, /** * Fired when a grid cell is pressed. * @since 1.65 */ 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"} } } } }, renderer: SinglePlanningCalendarGridRenderer }); SinglePlanningCalendarGrid.prototype.init = function () { var oStartDate = UI5Date.getInstance(), oDatesRow = new DatesRow(this.getId() + "-columnHeaders", { showDayNamesLine: false, showWeekNumbers: false, singleSelection: false, startDate: oStartDate, calendarWeekNumbering: this.getCalendarWeekNumbering() }).addStyleClass("sapMSinglePCColumnHeader"), iDelay = (60 - oStartDate.getSeconds()) * 1000, sTimePattern = this._getCoreLocaleData().getTimePattern("medium"); oDatesRow._setAriaRole("columnheader"); this.setAggregation("_columnHeaders", oDatesRow); this.setStartDate(oStartDate); this._setColumns(7); this._configureBlockersDragAndDrop(); this._configureAppointmentsDragAndDrop(); this._configureAppointmentsResize(); this._configureAppointmentsCreate(); this._oUnifiedRB = sap.ui.getCore().getLibraryResourceBundle("sap.ui.unified"); this._oFormatStartEndInfoAria = DateFormat.getDateTimeInstance({ pattern: "EEEE dd/MM/yyyy 'at' " + sTimePattern }); this._oFormatAriaFullDayCell = DateFormat.getDateTimeInstance({ pattern: "EEEE dd/MM/yyyy" }); this._oFormatYyyymmdd = DateFormat.getInstance({pattern: "yyyyMMdd", calendarType: CalendarType.Gregorian}); //the id of the SPC's legend if any this._sLegendId = undefined; setTimeout(this._updateRowHeaderAndNowMarker.bind(this), iDelay); }; SinglePlanningCalendarGrid.prototype.exit = function () { if (this._oItemNavigation) { this.removeDelegate(this._oItemNavigation); this._oItemNavigation.destroy(); delete this._oItemNavigation; } }; SinglePlanningCalendarGrid.prototype.onBeforeRendering = function () { var oAppointmentsMap = this._createAppointmentsMap(this.getAppointments()), oStartDate = this.getStartDate(), oCalStartDate = CalendarDate.fromLocalJSDate(oStartDate), iColumns = this._getColumns(); this._oVisibleAppointments = this._calculateVisibleAppointments(oAppointmentsMap.appointments, this.getStartDate(), iColumns); this._oAppointmentsToRender = this._calculateAppointmentsLevelsAndWidth(this._oVisibleAppointments); this._aVisibleBlockers = this._calculateVisibleBlockers(oAppointmentsMap.blockers, oCalStartDate, iColumns); this._oBlockersToRender = this._calculateBlockersLevelsAndWidth(this._aVisibleBlockers); if (this._iOldColumns !== iColumns || this._oOldStartDate !== oStartDate) { this._createBlockersDndPlaceholders(oStartDate, iColumns); this._createAppointmentsDndPlaceholders(oStartDate, iColumns); } this._oInvisibleMessage = InvisibleMessage.getInstance(); }; SinglePlanningCalendarGrid.prototype.setCalendarWeekNumbering = function (sCalendarWeekNumbering){ this.setProperty("calendarWeekNumbering",sCalendarWeekNumbering); var oDatesRow = this.getAggregation("_columnHeaders"); oDatesRow.setCalendarWeekNumbering(sCalendarWeekNumbering); return this; }; SinglePlanningCalendarGrid.prototype.onmousedown = function(oEvent) { var oClassList = oEvent.target.classList; this._isResizeHandleBottomMouseDownTarget = oClassList.contains("sapMSinglePCAppResizeHandleBottom"); this._isResizeHandleTopMouseDownTarget = oClassList.contains("sapMSinglePCAppResizeHandleTop"); }; SinglePlanningCalendarGrid.prototype._isResizingPerformed = function() { return this._isResizeHandleBottomMouseDownTarget || this._isResizeHandleTopMouseDownTarget; }; SinglePlanningCalendarGrid.prototype._configureBlockersDragAndDrop = function () { this.addDragDropConfig(new DragDropInfo({ sourceAggregation: "appointments", targetAggregation: "_blockersPlaceholders", 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"), oAppointment = oDragSession.getDragControl(), oDropTarget = oDragSession.getDropControl(), bIsFullDay = this.isAllDayAppointment(oAppointment.getStartDate(), oAppointment.getEndDate()), fnAlignIndicator = function () { var $Indicator = jQuery(oDragSession.getIndicator()), iAppHeight = oAppointment.$().outerHeight(), iAppWidth = oAppointment.$().outerWidth(), oGrid = oDropTarget.$().closest(".sapMSinglePCBlockersColumns").get(0).getBoundingClientRect(), oDropDim = oDropTarget.getDomRef().getBoundingClientRect(), iSubtractFromWidth = (oDropDim.left + iAppWidth) - (oGrid.left + oGrid.width); if (bIsFullDay) { $Indicator.css("min-height", iAppHeight); $Indicator.css("min-width", Math.min(iAppWidth, iAppWidth - iSubtractFromWidth)); } else { $Indicator.css("min-height", oDragSession.getDropControl().$().outerHeight()); $Indicator.css("min-width", oDragSession.getDropControl().$().outerWidth()); } }; if (!oDragSession.getIndicator()) { setTimeout(fnAlignIndicator, 0); } else { fnAlignIndicator(); } }.bind(this), drop: function (oEvent) { var oDragSession = oEvent.getParameter("dragSession"), oAppointment = oDragSession.getDragControl(), oPlaceholder = oDragSession.getDropControl(), oStartDate = oPlaceholder.getDate().getJSDate(), oEndDate, oBrowserEvent = oEvent.getParameter("browserEvent"), bCopy = (oBrowserEvent.metaKey || oBrowserEvent.ctrlKey), bIsFullDay = this.isAllDayAppointment(oAppointment.getStartDate(), oAppointment.getEndDate()); oEndDate = UI5Date.getInstance(oStartDate); if (bIsFullDay) { oEndDate.setMilliseconds(oAppointment.getEndDate().getTime() - oAppointment.getStartDate().getTime()); } this.$().find(".sapMSinglePCOverlay").removeClass("sapMSinglePCOverlayDragging"); if (bIsFullDay && oAppointment.getStartDate().getTime() === oStartDate.getTime()) { return; } this.fireAppointmentDrop({ appointment: oAppointment, startDate: oStartDate, endDate: oEndDate, copy: bCopy }); }.bind(this) })); }; SinglePlanningCalendarGrid.prototype._configureAppointmentsDragAndDrop = function () { this.addDragDropConfig(new DragDropInfo({ sourceAggregation: "appointments", targetAggregation: "_intervalPlaceholders", dragStart: function (oEvent) { if (!this.getEnableAppointmentsDragAndDrop() || this._isResizingPerformed()) { 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"), oAppointment = oDragSession.getDragControl(), oDropTarget = oDragSession.getDropControl(), bIsFullDay = this.isAllDayAppointment(oAppointment.getStartDate(), oAppointment.getEndDate()), fnAlignIndicator = function () { var $Indicator = jQuery(oDragSession.getIndicator()), iAppHeight = oAppointment.$().outerHeight(), oGrid = oDropTarget.$().closest(".sapMSinglePCColumn").get(0).getBoundingClientRect(), oDropDim = oDragSession.getDropControl().getDomRef().getBoundingClientRect(), iSubtractFromHeight = (oDropDim.top + iAppHeight) - (oGrid.top + oGrid.height); if (bIsFullDay) { $Indicator.css("min-height", 2 * oDragSession.getDropControl().$().outerHeight()); } else { $Indicator.css("min-height", Math.min(iAppHeight, iAppHeight - iSubtractFromHeight)); } }; if (!oDragSession.getIndicator()) { setTimeout(fnAlignIndicator, 0); } else { fnAlignIndicator(); } }.bind(this), drop: function (oEvent) { var oDragSession = oEvent.getParameter("dragSession"), oAppointment = oDragSession.getDragControl(), oPlaceholder = oDragSession.getDropControl(), oStartDate = oPlaceholder.getDate().getJSDate(), oEndDate, oBrowserEvent = oEvent.getParameter("browserEvent"), bCopy = (oBrowserEvent.metaKey || oBrowserEvent.ctrlKey), bIsFullDay = this.isAllDayAppointment(oAppointment.getStartDate(), oAppointment.getEndDate()); oEndDate = UI5Date.getInstance(oStartDate); if (bIsFullDay) { oEndDate.setHours(oEndDate.getHours() + 1); } else { oEndDate.setMilliseconds(oAppointment.getEndDate().getTime() - oAppointment.getStartDate().getTime()); } this.$().find(".sapMSinglePCOverlay").removeClass("sapMSinglePCOverlayDragging"); if (!bIsFullDay && oAppointment.getStartDate().getTime() === oStartDate.getTime()) { return; } this.fireAppointmentDrop({ appointment: oAppointment, startDate: oStartDate, endDate: oEndDate, copy: bCopy }); }.bind(this) })); }; SinglePlanningCalendarGrid.prototype._configureAppointmentsResize = function() { var oResizeConfig = new DragDropInfo({ sourceAggregation: "appointments", targetAggregation: "_intervalPlaceholders", /** * Fired when the user starts dragging an appointment. */ dragStart: function(oEvent) { if (!this.getEnableAppointmentsResize() || !this._isResizingPerformed()) { oEvent.preventDefault(); return; } var oDragSession = oEvent.getParameter("dragSession"), oDragControl = oDragSession.getDragControl(), oEventTarget = oEvent.getParameter("browserEvent") && oEvent.getParameter("browserEvent").target || null; oDragControl._sAppointmentPartSuffix = oEventTarget && oEventTarget.id ? oEventTarget.id.replace(oDragControl.getId() + "-", "") : ""; var $SPCGridOverlay = this.$().find(".sapMSinglePCOverlay"), $Indicator = jQuery(oDragSession.getIndicator()), $DraggedControl = oDragControl.$(); if (this._isResizeHandleBottomMouseDownTarget) { oDragSession.setComplexData("bottomHandle", "true"); } if (this._isResizeHandleTopMouseDownTarget) { oDragSession.setComplexData("topHandle", "true"); } $Indicator.addClass("sapUiDnDIndicatorHide"); setTimeout(function() { $SPCGridOverlay.addClass("sapMSinglePCOverlayDragging"); }, 0); jQuery(document).one("dragend", function() { var oAppointmentStartingBoundaries = oDragSession.getComplexData("appointmentStartingBoundaries"); $SPCGridOverlay.removeClass("sapMSinglePCOverlayDragging"); $Indicator.removeClass("sapUiDnDIndicatorHide"); $DraggedControl.css({ top: oAppointmentStartingBoundaries.top, height: oAppointmentStartingBoundaries.height, "z-index": "auto", opacity: 1 }); }); oEvent.getParameter("browserEvent").dataTransfer.setDragImage(getResizeGhost(), 0, 0); }.bind(this), /** * Fired when a dragged appointment enters a drop target. */ dragEnter: function(oEvent) { var oDragSession = oEvent.getParameter("dragSession"), oAppointmentRef = oDragSession.getDragControl().$().get(0), oDropTarget = oDragSession.getDropControl().getDomRef(), oAppointmentStartingBoundaries = oDragSession.getComplexData("appointmentStartingBoundaries"), fnHideIndicator = function() { var $Indicator = jQuery(oDragSession.getIndicator()); $Indicator.addClass("sapUiDnDIndicatorHide"); }, iTop, iBottom, iHeight, iVariableBoundaryY, mDraggedControlConfig; if (!oAppointmentStartingBoundaries) { oAppointmentStartingBoundaries = { top: oAppointmentRef.offsetTop, bottom: oAppointmentRef.offsetTop + oAppointmentRef.getBoundingClientRect().height, height: oAppointmentRef.getBoundingClientRect().height }; oDragSession.setComplexData("appointmentStartingBoundaries", oAppointmentStartingBoundaries); } iVariableBoundaryY = oDragSession.getData("bottomHandle") ? oAppointmentStartingBoundaries.top : oAppointmentStartingBoundaries.bottom; iTop = Math.min(iVariableBoundaryY, oDropTarget.offsetTop); iBottom = Math.max(iVariableBoundaryY, oDropTarget.offsetTop + oDropTarget.getBoundingClientRect().height); iHeight = iBottom - iTop; mDraggedControlConfig = { top: iTop, height: iHeight, "z-index": 1, opacity: 0.8 }; oDragSession.getDragControl().$().css(mDraggedControlConfig); if (!oDragSession.getIndicator()) { setTimeout(fnHideIndicator, 0); } else { fnHideIndicator(); } }, /** * Fired when an appointment is dropped. */ drop: function(oEvent) { var oDragSession = oEvent.getParameter("dragSession"), oAppointment = oDragSession.getDragControl(), iIndex = this.indexOfAggregation("_intervalPlaceholders", oDragSession.getDropControl()), oAppointmentStartingBoundaries = oDragSession.getComplexData("appointmentStartingBoundaries"), newPos; newPos = this._calcResizeNewHoursAppPos( oAppointment.getStartDate(), oAppointment.getEndDate(), iIndex, oDragSession.getComplexData("bottomHandle") ); this.$().find(".sapMSinglePCOverlay").removeClass("sapMSinglePCOverlayDragging"); jQuery(oDragSession.getIndicator()).removeClass("sapUiDnDIndicatorHide"); oAppointment.$().css({ top: oAppointmentStartingBoundaries.top, height: oAppointmentStartingBoundaries.height, "z-index": "auto", opacity: 1 }); if (oAppointment.getEndDate().getTime() === newPos.endDate.getTime() && oAppointment.getStartDate().getTime() === newPos.startDate.getTime()) { return; } this.fireAppointmentResize({ appointment: oAppointment, startDate: newPos.startDate, endDate: newPos.endDate }); setTimeout(function() {this.invalidate();}.bind(this), 0); }.bind(this) }); this.addDragDropConfig(oResizeConfig); }; SinglePlanningCalendarGrid.prototype._configureAppointmentsCreate = function () { this.addDragDropConfig(new DragDropInfo({ targetAggregation: "_intervalPlaceholders", dragStart: function (oEvent) { if (!this.getEnableAppointmentsCreate()) { oEvent.preventDefault(); return; } var oBrowserEvent = oEvent.getParameter("browserEvent"); var $SPCGridOverlay = this.$().find(".sapMSinglePCOverlay"); setTimeout(function () { $SPCGridOverlay.addClass("sapMSinglePCOverlayDragging"); }); jQuery(document).one("dragend", function () { $SPCGridOverlay.removeClass("sapMSinglePCOverlayDragging"); jQuery(".sapUiAppCreate").remove(); jQuery(".sapUiDnDDragging").removeClass("sapUiDnDDragging"); }); oBrowserEvent.dataTransfer.setDragImage(getResizeGhost(), 0, 0); var oGrid = oEvent.getParameter("target"), bIsRtl = Configuration.getRTL(), aIntervalPlaceholders = oGrid.getAggregation("_intervalPlaceholders"), oFirstIntervalRectangle = aIntervalPlaceholders[0].getDomRef().getBoundingClientRect(), iIntervalHeight = oFirstIntervalRectangle.height, iIntervalIndexOffset = Math.floor((oFirstIntervalRectangle.top - oGrid.getDomRef().getBoundingClientRect().top) / iIntervalHeight), oDragSession = oEvent.getParameter("dragSession"), iIndexInColumn = Math.floor(oBrowserEvent.offsetY / iIntervalHeight) - iIntervalIndexOffset, iIntervalIndex, oCurrentIntervalBoundingRectangle; if (this._iColumns === 1) { iIntervalIndex = iIndexInColumn; } else { var iHeaderWidthSize = bIsRtl ? 0 : this.getDomRef().querySelector(".sapMSinglePCRowHeaders").getClientRects()[0].width, iIntervalWidth = oGrid._aGridCells[0].getClientRects()[0].width, iColumnsFromStart = Math.floor(Math.floor((oBrowserEvent.offsetX - iHeaderWidthSize)) / iIntervalWidth), iIntervalsInColumn = aIntervalPlaceholders.length / this._iColumns; iIntervalIndex = iIndexInColumn + ((iColumnsFromStart) * iIntervalsInColumn); } if (iIntervalIndex < 0) { iIntervalIndex = 0; } oCurrentIntervalBoundingRectangle = aIntervalPlaceholders[iIntervalIndex].getDomRef().getBoundingClientRect(); oDragSession.setComplexData("startingRectsDropArea", {top: Math.ceil(iIndexInColumn * iIntervalHeight), left: oCurrentIntervalBoundingRectangle.left}); oDragSession.setComplexData("startingDropDate", aIntervalPlaceholders[iIntervalIndex].getDate()); }.bind(this), dragEnter: function (oEvent) { var oDragSession = oEvent.getParameter("dragSession"), oDropControl = oDragSession.getDropControl(), oDropDom = oDropControl.getDomRef(), iDropHeight = oDropDom.offsetHeight, iDropY = oDropDom.offsetTop, iStartingDropY = iDropY, iDropX = oDropDom.getBoundingClientRect().left, iStartingDropX = iDropX, oColumn = oDropControl.$().parents(".sapMSinglePCColumn").get(0), $createPlaceHolder = jQuery(".sapUiAppCreate"); if (!$createPlaceHolder.get(0)) { $createPlaceHolder = jQuery("<div></div>") .addClass("sapUiCalendarApp sapUiCalendarAppType01 sapUiAppCreate"); $createPlaceHolder.appendTo(oColumn); } jQuery(".sapUiDnDDragging").removeClass("sapUiDnDDragging"); if (!oDragSession.getComplexData("startingRectsDropArea")) { oDragSession.setComplexData("startingRectsDropArea", { top: iDropY, left: iDropX }); oDragSession.setComplexData("startingDropDate", oDropControl.getDate()); } else { iStartingDropY = oDragSession.getComplexData("startingRectsDropArea").top; iStartingDropX = oDragSession.getComplexData("startingRectsDropArea").left; } if (iDropX !== iStartingDropX) { oEvent.preventDefault(); return false; } // Dim the column oDropControl.$().closest(".sapMSinglePCColumn").find(".sapMSinglePCAppointments").addClass("sapUiDnDDragging"); $createPlaceHolder.css({ top: Math.min(iStartingDropY, iDropY) + 2, height: Math.abs(iStartingDropY - iDropY) + iDropHeight - 4, left: 3, right: 3, "z-index": 2 }); oDragSession.setIndicatorConfig({ display: "none" }); }, drop: function (oEvent) { var oDragSession = oEvent.getParameter("dragSession"), oDropControl = oDragSession.getDropControl(), iMillisecondsStep = (60 / (this.getScaleFactor() * 2)) * 60 * 1000, // calculating the duration of the appointment in milliseconds relative to the current scaleFactor oStartDate = oDragSession.getComplexData("startingDropDate").getTime(), oEndDate = oDropControl.getDate().getJSDate().getTime(), iStartTime = Math.min(oStartDate, oEndDate), iEndTime = Math.max(oStartDate, oEndDate) + iMillisecondsStep; this.fireAppointmentCreate({ startDate: UI5Date.getInstance(iStartTime), endDate: UI5Date.getInstance(iEndTime) }); jQuery(".sapUiAppCreate").remove(); jQuery(".sapUiDnDDragging").removeClass("sapUiDnDDragging"); }.bind(this) })); }; SinglePlanningCalendarGrid.prototype._calcResizeNewHoursAppPos = function(oAppStartDate, oAppEndDate, iIndex, bBottomHandle) { var iMinutesStep = (60 / (this.getScaleFactor() * 2)) * 60 * 1000, // calculating the duration of the appointment in milliseconds relative to the current scaleFactor iPlaceholderStartTime = this.getAggregation("_intervalPlaceholders")[iIndex].getDate().getTime(), iPlaceholderEndTime = iPlaceholderStartTime + iMinutesStep, iVariableBoundaryTime = bBottomHandle ? oAppStartDate.getTime() : oAppEndDate.getTime(), iStartTime = Math.min(iVariableBoundaryTime, iPlaceholderStartTime), iEndTime = Math.max(iVariableBoundaryTime, iPlaceholderEndTime); return { startDate: UI5Date.getInstance(iStartTime), endDate: UI5Date.getInstance(iEndTime) }; }; SinglePlanningCalendarGrid.prototype._adjustAppointmentsHeightforCompact = function (sDate, oColumnStartDateAndHour, oColumnEndDateAndHour, iColumn) { var oAppointment, oAppDomRef, oAppStartDate, oAppEndDate, iAppTop, iAppBottom, bAppStartIsOutsideVisibleStartHour, bAppEndIsOutsideVisibleEndHour, iRowHeight = this._getRowHeight(), iRow = 0, iVerticalPaddingBetweenAppointments = 0.125, iAppointmentBottomPadding = 0.125, iAppointmentTopPadding = 0.0625, iScaleFactor = this.getScaleFactor(), iDivider = 2 * iScaleFactor; if (this._oAppointmentsToRender[sDate]) { this._oAppointmentsToRender[sDate].oAppointmentsList.getIterator().forEach(function(oAppNode) { oAppointment = oAppNode.getData(); oAppDomRef = this.getDomRef().querySelector("#" + oAppointment.getId() + "-" + iColumn + "_" + iRow); oAppStartDate = oAppointment.getStartDate(); oAppEndDate = oAppointment.getEndDate(); bAppStartIsOutsideVisibleStartHour = oColumnStartDateAndHour.getTime() > oAppStartDate.getTime(); bAppEndIsOutsideVisibleEndHour = oColumnEndDateAndHour.getTime() < oAppEndDate.getTime(); iAppTop = bAppStartIsOutsideVisibleStartHour ? 0 : this._calculateTopPosition(oAppStartDate); iAppBottom = bAppEndIsOutsideVisibleEndHour ? 0 : this._calculateBottomPosition(oAppEndDate); oAppDomRef.style["top"] = iAppTop + "rem"; oAppDomRef.style["bottom"] = iAppBottom + "rem"; oAppDomRef.querySelector(".sapUiCalendarApp").style["minHeight"] = (iRowHeight - ((iVerticalPaddingBetweenAppointments + iAppointmentBottomPadding + iAppointmentTopPadding) * iScaleFactor)) / iDivider + "rem"; ++iRow; }.bind(this)); } }; SinglePlanningCalendarGrid.prototype._adjustBlockersHeightforCompact = function () { var iMaxLevel = this._getBlockersToRender().iMaxlevel, iContainerHeight = (iMaxLevel + 1) * this._getBlockerRowHeight(), // Day view has bigger height because of the day marker for special days iRecalculatedContHeight = this._getColumns() === 1 ? iContainerHeight + DAY_MARKER_HEIGHT : iContainerHeight, iBlockerRowHeight = this._getBlockerRowHeight(); if (iMaxLevel > 0) { // hackie thing to calculate the container witdth. When we have more than 1 line of blockers - we must add 0.1875rem in order to render the blockers visually in the container. iRecalculatedContHeight = iRecalculatedContHeight + 0.1875; } this.$().find(".sapMSinglePCBlockersColumns").css("height", iRecalculatedContHeight + "rem"); this._oBlockersToRender.oBlockersList.getIterator().forEach(function(oBlockerNode) { oBlockerNode.getData().$().css("top", (iBlockerRowHeight * oBlockerNode.level + 0.0625) + "rem"); }); }; /* * Calculates the blocker column's height in the day view because of the special dates. */ SinglePlanningCalendarGrid.prototype._adjustBlockersHeightforCozy = function () { var iMaxLevel = this._getBlockersToRender() && this._getBlockersToRender().iMaxlevel, iContainerHeight; if (this._getColumns() === 1) { iContainerHeight = (iMaxLevel + 1) * this._getBlockerRowHeight(); this.$().find(".sapMSinglePCBlockersColumns").css("height", (iContainerHeight + DAY_MARKER_HEIGHT) + "rem"); } }; SinglePlanningCalendarGrid.prototype._adjustRowHigth = function () { this.$().find(".sapMSinglePCRow").css("height", this._getRowHeight() + "rem"); }; SinglePlanningCalendarGrid.prototype.onAfterRendering = function () { var iColumns = this._getColumns(), oStartDate = this.getStartDate(), iRowHeight = this._getRowHeight(); if (iRowHeight === ROW_HEIGHT_COMPACT) { for (var i = 0; i < iColumns; i++) { var oColumnCalDate = new CalendarDate(oStartDate.getFullYear(), oStartDate.getMonth(), oStartDate.getDate() + i), sDate = this._getDateFormatter().format(oColumnCalDate.toLocalJSDate()), oColumnStartDateAndHour = new UniversalDate(oColumnCalDate.getYear(), oColumnCalDate.getMonth(), oColumnCalDate.getDate(), this._getVisibleStartHour()), oColumnEndDateAndHour = new UniversalDate(oColumnCalDate.getYear(), oColumnCalDate.getMonth(), oColumnCalDate.getDate(), this._getVisibleEndHour(), 59, 59); this._adjustAppointmentsHeightforCompact(sDate, oColumnStartDateAndHour, oColumnEndDateAndHour, i); } this._adjustBlockersHeightforCompact(); } else { this._adjustBlockersHeightforCozy(); } this._adjustRowHigth(); this._updateRowHeaderAndNowMarker(); _initItemNavigation.call(this); }; /** * Ensures that the focus is moved from an appointment to the correct cell from the visible grid area or * borderReached event is fired when the correct cell to focus is outside of the visible grid area * and removes the appointments selection. * * @param {object} oEvent The event object that is passed to the onsapup, onsapdown, onsapright, on sapleft handlers * @param {int} iDirection Number representing the key code of the pressed arrow from the keyboard * @private */ SinglePlanningCalendarGrid.prototype._appFocusHandler = function(oEvent, iDirection) { var oTarget = sap.ui.getCore().byId(oEvent.target.id) || this._findSrcControl(oEvent); if (oTarget && oTarget.isA("sap.ui.unified.CalendarAppointment")) { this.fireAppointmentSelect({ appointment: undefined, appointments: this._toggleAppointmentSelection(undefined, true) }); this._focusCellWithKeyboard(oTarget, iDirection); // Prevent scrolling oEvent.preventDefault(); } }; /** * Ensures that the borderReached event is fired when the focus is on a cell and border is reached. * * @param {object} oEvent The event object that is passed to the onsapup, onsapdown, onsapright, on sapleft handlers * @param {int} iDirection Number representing the key code of the pressed arrow from the keyboard * @private */ SinglePlanningCalendarGrid.prototype._cellFocusHandler = function(oEvent, iDirection) { var oGridCell = oEvent.target, oFormat = this._getDateFormatter(), oFocusedElement; if (oGridCell.classList.contains("sapMSinglePCRow") || oGridCell.classList.contains("sapMSinglePCBlockersColumn")) { oFocusedElement = oFormat.parse(oGridCell.getAttribute("data-sap-start-date")); if (this._isBorderReached(oFocusedElement, iDirection)) { this.fireEvent("borderReached", { startDate: oFocusedElement, next: iDirection === KeyCodes.ARROW_RIGHT, fullDay: oGridCell.classList.contains("sapMSinglePCBlockersColumn") }); } } }; SinglePlanningCalendarGrid.prototype.onsapup = function (oEvent) { this._appFocusHandler(oEvent, KeyCodes.ARROW_UP); }; SinglePlanningCalendarGrid.prototype.onsapdown = function (oEvent) { this._appFocusHandler(oEvent, KeyCodes.ARROW_DOWN); }; SinglePlanningCalendarGrid.prototype.onsapright = function (oEvent) { this._appFocusHandler(oEvent, KeyCodes.ARROW_RIGHT); this._cellFocusHandler(oEvent, KeyCodes.ARROW_RIGHT); }; SinglePlanningCalendarGrid.prototype.onsapleft = function (oEvent) { this._appFocusHandler(oEvent, KeyCodes.ARROW_LEFT); this._cellFocusHandler(oEvent, KeyCodes.ARROW_LEFT); }; SinglePlanningCalendarGrid.prototype.setStartDate = function (oStartDate) { this._oOldStartDate = this.getStartDate(); this.getAggregation("_columnHeaders").setStartDate(oStartDate); return this.setProperty("startDate", oStartDate); }; // This function gets called from CalendarAppointment.prototype.applyFocusInfo function // and therefore its only concern is the appointments focus in the SinglePlanningCalendarGrid SinglePlanningCalendarGrid.prototype.applyFocusInfo = function (oFocusInfo) { var aVisibleBlockers = this._getVisibleBlockers(), oVisibleAppointments = this._getVisibleAppointments(), aVisibleAppsKeys = Object.keys(oVisibleAppointments), aVisibleAppsInDay, i, j; // directly focus appointment part, if there is any selected if (this._sSelectedAppointment) { this._sSelectedAppointment.focus(); return this; } // Search amongst the visible blockers for (i = 0; i < aVisibleBlockers.length; ++i) { if (aVisibleBlockers[i].getId() === oFocusInfo.id) { aVisibleBlockers[i].focus(); return this; } } // Search amongst the visible appointments for (i = 0; i < aVisibleAppsKeys.length; ++i) { aVisibleAppsInDay = oVisibleAppointments[aVisibleAppsKeys[i]]; for (j = 0; j < aVisibleAppsInDay.length; ++j) { if (aVisibleAppsInDay[j].getId() === oFocusInfo.id) { aVisibleAppsInDay[j].focus(); return this; } } } return this; }; /** * Holds the selected appointments. If no appointments are selected, an empty array is returned. * * @returns {sap.ui.unified.CalendarAppointment[]} All selected appointments * @since 1.62 * @public */ SinglePlanningCalendarGrid.prototype.getSelectedAppointments = function() { return this.getAppointments().filter(function(oAppointment) { return oAppointment.getSelected(); }); }; SinglePlanningCalendarGrid.prototype.setDateSelectionMode = function (sSelectionMode){ this.setProperty("dateSelectionMode", sSelectionMode); return this; }; SinglePlanningCalendarGrid.prototype._isMultiDatesSelectionHeaderAllowed = function () { return SinglePlanningCalendarSelectionMode.MultiSelect === this.getDateSelectionMode(); }; /* * PRIVATE API */ /** * 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.m.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 */ SinglePlanningCalendarGrid.prototype._toggleAppointmentSelection = function (oAppointment, bRemoveOldSelection) { var aChangedApps = [], oAppointmentDomRef = oAppointment && oAppointment.getDomRef(), 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); this._sSelectedAppointment = oAppointment.getSelected() && oAppointmentDomRef ? oAppointment : undefined; } else { this._sSelectedAppointment = undefined; } return aChangedApps; }; /** * Checking when an arrow key is pressed from the keyboard weather the cell focus should go in * the next or previous day or week * * @param {Date} oFocusedElement The start date of the focused cell or of the visible part of the appointment * @param {int} iDirection The key the was pressed * @returns {boolean} Indicator if the gird border is reached * @private */ SinglePlanningCalendarGrid.prototype._isBorderReached = function(oFocusedElement, iDirection) { var oGridStartDate = CalendarDate.fromLocalJSDate(this.getStartDate()), oGridEndDate = new CalendarDate(oGridStartDate.getYear(), oGridStartDate.getMonth(), oGridStartDate.getDate() + this._getColumns() - 1), oTargetStartDate = CalendarDate.fromLocalJSDate(oFocusedElement), bLeft = iDirection === KeyCodes.ARROW_LEFT && oTargetStartDate.isSame(oGridStartDate), bRight = iDirection === KeyCodes.ARROW_RIGHT && oTargetStartDate.isSame(oGridEndDate); return bLeft || bRight; }; /** * Ensures that the focus is moved from an appointment to the correct cell from the visible grid area or * borderReached event is fired when the correct cell to focus is outside of the visible grid area. * * @param {sap.ui.unified.CalendarAppointment} oAppointment Appointment from which to start navigation * @param {int} iDirection Number representing the key code of the pressed arrow from the keyboard * @private */ SinglePlanningCalendarGrid.prototype._focusCellWithKeyboard = function (oAppointment, iDirection) { var bFullDayApp = this.isAllDayAppointment(oAppointment.getStartDate(), oAppointment.getEndDate()), oFormat = this._getDateFormatter(), oAppStartDate = UI5Date.getInstance(oAppointment.getStartDate().getFullYear(), oAppointment.getStartDate().getMonth(), oAppointment.getStartDate().getDate(), oAppointment.getStartDate().getHours()), oGridStartDate = UI5Date.getInstance(this.getStartDate().getFullYear(), this.getStartDate().getMonth(), this.getStartDate().getDate(), this.getStartDate().getHours()); // If an appointment start hour is before the visible grid start date // we will use the grid start date as the appointment start date. if (oAppStartDate < oGridStartDate) { oAppStartDate = oGridStartDate; } if (this._isBorderReached(oAppStartDate, iDirection)) { this.fireEvent("borderReached", { startDate: oAppStartDate, next: iDirection === KeyCodes.ARROW_RIGHT, fullDay: bFullDayApp }); return; } switch (iDirection) { case KeyCodes.ARROW_UP: if (!bFullDayApp) { oAppStartDate.setHours(oAppStartDate.getHours() - 1); } break; case KeyCodes.ARROW_DOWN: if (!bFullDayApp) { oAppStartDate.setHours(oAppStartDate.getHours() + 1); } break; case KeyCodes.ARROW_LEFT: oAppStartDate.setDate(oAppStartDate.getDate() - 1); break; case KeyCodes.ARROW_RIGHT: oAppStartDate.setDate(oAppStartDate.getDate() + 1); break; default: } if (bFullDayApp && iDirection !== KeyCodes.ARROW_DOWN) { jQuery("[data-sap-start-date='" + oFormat.format(oAppStartDate) + "'].sapMSinglePCBlockersColumn").trigger("focus"); } else { jQuery("[data-sap-start-date='" + oFormat.format(oAppStartDate) + "'].sapMSinglePCRow").trigger("focus"); } }; SinglePlanningCalendarGrid.prototype.onmouseup = function (oEvent) { var bMultiDateSelection = SinglePlanningCalendarSelectionMode.MultiSelect === this.getDateSelectionMode(); if (!bMultiDateSelection && !(oEvent.metaKey || oEvent.ctrlKey)) { this.removeAllSelectedDates(); } this._bMultiDateSelect = true; this._fireSelectionEvent(oEvent); }; /** * Handles the <code>tap</code> event on the grid. * * @param {jQuery.Event} oEvent The event object */ SinglePlanningCalendarGrid.prototype.ontap = function (oEvent) { this._fireSelectionEvent(oEvent); }; SinglePlanningCalendarGrid.prototype.removeAllSelectedDates = function(oEvent) { this.removeAllAggregation("selectedDates"); }; /** * Handles the <code>keyup</code> event. * * @param {jQuery.Event} oEvent The event object. */ SinglePlanningCalendarGrid.prototype.onkeyup = function(oEvent) { var bMultiDateSelection = SinglePlanningCalendarSelectionMode.MultiSelect === this.getDateSelectionMode(); if ((oEvent.which === KeyCodes.ARROW_LEFT || oEvent.which === KeyCodes.ARROW_RIGHT) && oEvent.shiftKey && bMultiDateSelection) { this._bMultiDateSelectWithArrow = true; } else if (oEvent.which === KeyCodes.SPACE && !oEvent.shiftKey && bMultiDateSelection) { this._bMultiDateSelect = true; } this._fireSelectionEvent(oEvent); // Prevent scrolling oEvent.preventDefault(); }; /** * Handles the <code>keydown</code> event when any key is pressed. * * @param {jQuery.Event} oEvent The event object. */ SinglePlanningCalendarGrid.prototype.onkeydown = function (oEvent) { var bMultiDateSelection = SinglePlanningCalendarSelectionMode.MultiSelect === this.getDateSelectionMode(); if (oEvent.which === KeyCodes.SPACE || oEvent.which === KeyCodes.ENTER || oEvent.which === KeyCodes.ARROW_LEFT || oEvent.which === KeyCodes.ARROW_RIGHT) { if (oEvent.which === KeyCodes.SPACE && oEvent.shiftKey && bMultiDateSelection) { this._bCurrentWeekSelection = true; } this._fireSelectionEvent(oEvent); var oControl = this._findSrcControl(oEvent); if (oContr