UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,448 lines (1,209 loc) 124 kB
/*! * UI development toolkit for HTML5 (OpenUI5) * (c) Copyright 2009-2022 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/ui/core/Control', 'sap/ui/base/ManagedObjectObserver', 'sap/ui/unified/library', 'sap/ui/unified/calendar/CalendarUtils', 'sap/ui/unified/calendar/CalendarDate', 'sap/ui/unified/DateRange', 'sap/ui/unified/CalendarAppointment', 'sap/ui/unified/CalendarRow', 'sap/ui/unified/CalendarRowRenderer', 'sap/ui/unified/CalendarDateInterval', 'sap/ui/unified/CalendarWeekInterval', 'sap/ui/unified/CalendarOneMonthInterval', 'sap/ui/Device', 'sap/ui/core/Element', 'sap/ui/core/Renderer', 'sap/ui/core/ResizeHandler', 'sap/ui/core/Item', 'sap/ui/core/dnd/DragInfo', 'sap/ui/core/dnd/DropInfo', 'sap/ui/core/dnd/DragDropInfo', 'sap/m/Select', 'sap/m/Button', 'sap/m/Toolbar', 'sap/m/Table', 'sap/m/Column', 'sap/m/ColumnListItem', 'sap/m/ColumnListItemRenderer', 'sap/m/StandardListItem', 'sap/m/StandardListItemRenderer', 'sap/m/PlanningCalendarRow', 'sap/m/PlanningCalendarRenderer', 'sap/m/library', "sap/base/util/deepEqual", "sap/base/Log", "sap/ui/thirdparty/jquery", // jQuery Plugin "control" "sap/ui/dom/jquery/control" ], function( Control, ManagedObjectObserver, unifiedLibrary, CalendarUtils, CalendarDate, DateRange, CalendarAppointment, CalendarRow, CalendarRowRenderer, CalendarDateInterval, CalendarWeekInterval, CalendarOneMonthInterval, Device, Element, Renderer, ResizeHandler, Item, DragInfo, DropInfo, DragDropInfo, Select, Button, Toolbar, Table, Column, ColumnListItem, ColumnListItemRenderer, StandardListItem, StandardListItemRenderer, PlanningCalendarRow, PlanningCalendarRenderer, library, deepEqual, Log, jQuery ) { "use strict"; // 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.ButtonType var ButtonType = library.ButtonType; // 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; 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 <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 a 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. * * <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.60.39 * * @constructor * @public * @since 1.34 * @alias sap.m.PlanningCalendar * @see {@link fiori:https://experience.sap.com/fiori-design-web/planning-calendar/ Planning Calendar} * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel */ 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 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. */ 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. * @since 1.38.0 */ appointmentsReducedHeight : {type : "boolean", group : "Appearance", defaultValue : false}, /** * 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 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 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 41. * * 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 limitations 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} }, 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> If one day is assigned to more than one type, only the first type will be used. */ 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"}, /** * Hidden, for internal use only. */ table : {type : "sap.m.Table", multiple : false, visibility : "hidden"} }, 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 JavaScript date object. */ startDate : {type : "object"}, /** * Interval end date as a 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[]"} } }, /** * <code>startDate</code> was changed while navigating in the <code>PlanningCalendar</code>. * The new value can be obtained using the <code>sap.m.PlanningCalendar#getStartDate()</code> method. */ startDateChange : {}, /** * <code>viewKey</code> was changed by user interaction. */ viewChange : {}, /** * Fires when a row header is clicked. * @since 1.46.0 */ rowHeaderClick: { /** * The row user clicked on. */ 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; } } }); //List of private properties controlling different intervals var INTERVAL_CTR_REFERENCES = ["_oTimeInterval", "_oDateInterval", "_oMonthInterval", "_oWeekInterval", "_oOneMonthInterval"], //Holds metadata of the different interval instances that should be created. INTERVAL_METADATA = {}; INTERVAL_METADATA[CalendarIntervalType.Day] = { sInstanceName: "_oDateInterval", sIdSuffix: "-DateInt", oClass: CalendarDateInterval }; INTERVAL_METADATA[CalendarIntervalType.Week] = { sInstanceName: "_oWeekInterval", sIdSuffix: "-WeekInt", oClass: CalendarWeekInterval }; INTERVAL_METADATA[CalendarIntervalType.OneMonth] = { sInstanceName: "_oOneMonthInterval", sIdSuffix: "-OneMonthInt", oClass: CalendarOneMonthInterval }; //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" }; // CalendarTimeInterval, CalendarDateInterval, CalendarMonthInterval represent all 5 existing intervals. // That is because the two left intervals (CalendarOneMonthInterval and CalendarWeekInterval) // inherit CalendarDateInterval one. var aIntervalRepresentatives = ["sap.ui.unified.CalendarTimeInterval", "sap.ui.unified.CalendarDateInterval", "sap.ui.unified.CalendarMonthInterval"]; var CalendarHeader = Control.extend("CalendarHeader", { metadata : { aggregations: { "toolbar" : {type: "sap.m.Toolbar", multiple: false}, "allCheckBox" : {type: "sap.m.CheckBox", multiple: false} } }, renderer : function(oRm, oHeader) { oRm.write("<div"); oRm.writeControlData(oHeader); oRm.addClass("sapMPlanCalHead"); oRm.writeClasses(); oRm.write(">"); var oToolbar = oHeader.getToolbar(); if (oToolbar) { oRm.renderControl(oToolbar); } var oAllCB = oHeader.getAllCheckBox(); if (oAllCB) { oRm.renderControl(oAllCB); } oRm.write("</div>"); } }); PlanningCalendar.prototype.init = function(){ 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")) { this._iSize = 1; this._iSizeScreen = 1; }else { this._iSize = 2; this._iSizeScreen = 2; } this._oRB = sap.ui.getCore().getLibraryResourceBundle("sap.m"); var sId = this.getId(); this._oIntervalTypeSelect = new Select(sId + "-IntType", {maxWidth: "15rem", ariaLabelledBy: sId + "-SelDescr"}); this._oIntervalTypeSelect.attachEvent("change", changeIntervalType, this); this._oTodayButton = new Button(sId + "-Today", { text: this._oRB.getText("PLANNINGCALENDAR_TODAY"), type: ButtonType.Transparent }); this._oTodayButton.attachEvent("press", this._handleTodayPress, this); this._oHeaderToolbar = new Toolbar(sId + "-HeaderToolbar", { design: ToolbarDesign.Transparent, content: [this._oIntervalTypeSelect, this._oTodayButton] }); this._oCalendarHeader = new CalendarHeader(sId + "-CalHead", { toolbar: this._oHeaderToolbar }); this._oInfoToolbar = new Toolbar(sId + "-InfoToolbar", { height: "auto", design: ToolbarDesign.Transparent, content: [this._oCalendarHeader, this._oTimeInterval] }); 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: sId + "-Descr" }); oTable.attachEvent("selectionChange", handleTableSelectionChange, this); oTable.addDelegate({ onBeforeRendering: function () { if (this._rowHeaderClickEvent) { this._rowHeaderClickEvent.off(); } }, onAfterRendering: function () { this._rowHeaderClickEvent = oTable.$().find(".sapMPlanCalRowHead > div.sapMLIB").click(function (oEvent) { var oRowHeader = jQuery(oEvent.currentTarget).control(0), oRow = getRow(oRowHeader.getParent()); this.fireRowHeaderClick({row: oRow}); }.bind(this)); } }, false, this); this.setAggregation("table", oTable, true); this.setStartDate(new Date()); this._resizeProxy = jQuery.proxy(handleResize, this); this._fnCustomSortedAppointments = undefined; //transfers a custom appointments sorter function to the CalendarRow }; 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(); } if (this.getToolbarContent().length == 0 && this._oToolbar) { this._oToolbar.destroy(); this._oToolbar = undefined; } // Remove event listener for rowHeaderClick event if (this._rowHeaderClickEvent) { this._rowHeaderClickEvent.off(); this._rowHeaderClickEvent = null; } }; PlanningCalendar.prototype.onBeforeRendering = function(){ this._bBeforeRendering = true; if ((!this._oTimeInterval && !this._oDateInterval && !this._oMonthInterval && !this._oWeekInterval && !this._oOneMonthInterval) || this._bCheckView) { // init intervalType settings if default is used this.setViewKey(this.getViewKey()); this._bCheckView = undefined; } updateSelectItems.call(this); if (this._sUpdateCurrentTime) { clearTimeout(this._sUpdateCurrentTime); this._sUpdateCurrentTime = undefined; } this._updateTodayButtonState(); Device.orientation.detachHandler(this._updateStickyHeader, this); this._bBeforeRendering = undefined; }; 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("button"); // set new aria role } }, this); } 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; }; PlanningCalendar.prototype._setAriaRole = function (oInterval) { if (this.hasListeners("intervalSelect")) { oInterval._setAriaRole("button"); // set new aria role } else { oInterval._setAriaRole("gridcell"); // set new aria role } }; /** * Handles the enabled/disabled state of the Today button * based on the visibility of the current date. * @private */ PlanningCalendar.prototype._updateTodayButtonState = function() { if (this._oTodayButton) { this._oTodayButton.setEnabled(!this._dateMatchesVisibleRange(new Date(), this.getViewKey())); } }; /** * Verifies if the given date matches the range of given view, * based on the visibility of the current date. * @param {Date} oDateTime the given date * @param {string} sViewKey the key of a view * @returns {boolean} if the date is in the visible range * @private */ PlanningCalendar.prototype._dateMatchesVisibleRange = function(oDateTime, sViewKey) { var oView = this._getView(sViewKey, !this._bBeforeRendering); if (!oView) { return false; } var sIntervalType = oView.getIntervalType(), oIntervalMetadata = INTERVAL_METADATA[sIntervalType], oInterval = oIntervalMetadata ? this[oIntervalMetadata.sInstanceName] : null, bResult = false; if (oInterval && oInterval._dateMatchesVisibleRange) { bResult = oInterval._dateMatchesVisibleRange(oDateTime); } return bResult; }; PlanningCalendar.prototype.onAfterRendering = function(oEvent){ // check if size is right and adopt it if necessary // also it calls _updateStickyHeader function and in case of stickyHeader property set to true // all needed classes will be updated oEvent.size = {width: this.getDomRef().offsetWidth}; handleResize.call(this, oEvent, true); if (!this._sResizeListener) { this._sResizeListener = ResizeHandler.register(this, this._resizeProxy); } if (Device.system.phone && this.getStickyHeader()) { Device.orientation.attachHandler(this._updateStickyHeader, this); } this._updateCurrentTimeVisualization(false); // CalendarRow sets visualization onAfterRendering if (this.getHeight()) { var $Table = this.getDomRef().querySelector("table"); // Table height is the PlanningCalendar height minus the height of the toolbars var sStyle = this.$().height() - this._oInfoToolbar.$().height() - this._oToolbar.$().height() + "px"; $Table.style.height = sStyle; } }; /** * Sets the given date as start date. The current date is used as default. * Depending on the current view the start date may be adjusted (for example, the week view shows always the first weekday * of the same week as the given date). * @param {Date} oStartDate the date to set as <code>sap.m.PlanningCalendar</code> <code>startDate</code>. May be changed(adjusted) if * property <code>startDate</code> is adjusted. See remark about week view above. * @returns {sap.m.PlanningCalendar} <code>this</code> to allow method chaining * @public */ PlanningCalendar.prototype.setStartDate = function(oStartDate){ var oFirstDateOfWeek, oFirstDateOfMonth; if (!oStartDate) { //set default value oStartDate = new Date(); } else { CalendarUtils._checkJSDateObject(oStartDate); } if (this.getViewKey() === PlanningCalendarBuiltInView.Week) { /* Calculate the first week date for the given oStartDate. Have in mind that the oStartDate is the date that * the user sees in the UI, thus - local one. As CalendarUtils.getFirstDateOfWeek works with UTC dates (this * is because the dates are timezone irrelevant), it should be called with the local datetime values presented * as UTC ones(e.g. if oStartDate is 21 Dec 1981, 13:00 GMT+02:00, it will be converted to 21 Dec 1981, 13:00 GMT+00:00) */ oFirstDateOfWeek = CalendarUtils.getFirstDateOfWeek(CalendarUtils._createUniversalUTCDate(oStartDate, undefined, true)); //CalendarUtils.getFirstDateOfWeek works with UTC based date values, restore the result back in local timezone. oStartDate.setTime(CalendarUtils._createLocalDate(oFirstDateOfWeek, true).getTime()); } if (this.getViewKey() === PlanningCalendarBuiltInView.OneMonth || this.getViewKey() === PlanningCalendarBuiltInView.Month) { /* * Have in mind that the oStartDate is the date that the user sees in the UI, thus - local one. As * CalendarUtils.getFirstDateOfMonth works with UTC dates (this is because the dates are timezone irrelevant), * it should be called with the local datetime values presented as UTC ones. */ oFirstDateOfMonth = CalendarUtils.getFirstDateOfMonth(CalendarUtils._createUniversalUTCDate(oStartDate, undefined, true)); //CalendarUtils.getFirstDateOfMonth works with UTC based date values, restore the result back in local timezone. oStartDate.setTime(CalendarUtils._createLocalDate(oFirstDateOfMonth, true).getTime()); } if (deepEqual(oStartDate, this.getStartDate())) { /* Logically this _updateTodayButtonState should not be needed, because if the date didn't change, there is no need to update the button's state (the last state is correct). Still, as setStartDate can be called just by changing a view, where the startDate may remains the same, we need to make sure the today button is up-to date, as it depends on the view type*/ this._updateTodayButtonState(); return this; } var iYear = oStartDate.getFullYear(); CalendarUtils._checkYearInValidRange(iYear); var oMinDate = this.getMinDate(); if (oMinDate && oMinDate.getTime() > oStartDate.getTime()) { Log.warning("StartDate < minDate -> StartDate set to minDate", this); oStartDate = new Date(oMinDate.getTime()); } else { var oMaxDate = this.getMaxDate(); if (oMaxDate && oMaxDate.getTime() < oStartDate.getTime()) { Log.warning("StartDate > maxDate -> StartDate set to minDate", this); if (oMinDate) { oStartDate = new Date(oMinDate.getTime()); } else { oStartDate = new Date(1, 0, 1); oStartDate.setFullYear(1); } } } this.setProperty("startDate", oStartDate, true); INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef].setStartDate(new Date(oStartDate.getTime())); // use new date object } }, this); this._setRowsStartDate(new Date(oStartDate.getTime())); if (this.getViewKey() === PlanningCalendarBuiltInView.Week || this.getViewKey() === PlanningCalendarBuiltInView.OneMonth) { this._updateTodayButtonState(); } if (this.getDomRef()) { // only set timer, CalendarRow will be rerendered, so no update needed here this._updateCurrentTimeVisualization(false); } return this; }; PlanningCalendar.prototype.setMinDate = function(oDate){ if (deepEqual(oDate, this.getMinDate())) { return this; } var oMaxDate = this.getMaxDate(); if (oDate) { CalendarUtils._checkJSDateObject(oDate); var iYear = oDate.getFullYear(); CalendarUtils._checkYearInValidRange(iYear); this.setProperty("minDate", oDate, true); this._bNoStartDateChange = true; // set the start date after all calendars are updated INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef].setMinDate(new Date(oDate.getTime())); // use new date object } }, this); if (oMaxDate && oMaxDate.getTime() < oDate.getTime()) { Log.warning("minDate > maxDate -> maxDate set to end of the month", this); oMaxDate = new Date(oDate.getTime()); oMaxDate.setMonth(oMaxDate.getMonth() + 1, 0); oMaxDate.setHours(23); oMaxDate.setMinutes(59); oMaxDate.setSeconds(59); oMaxDate.setMilliseconds(0); this.setMaxDate(oMaxDate); } this._bNoStartDateChange = undefined; var oStartDate = this.getStartDate(); if (oStartDate && oStartDate.getTime() < oDate.getTime()) { Log.warning("StartDate < minDate -> StartDate set to minDate", this); oStartDate = new Date(oDate.getTime()); this.setStartDate(oStartDate); } } else { this.setProperty("minDate", undefined, true); INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef].setMinDate(); } }, this); } var oToday = new Date(); if (oDate && oToday.getTime() < oDate.getTime()) { this._oTodayButton.setVisible(false); } else if (!oMaxDate || oToday.getTime() < oMaxDate.getTime()) { this._oTodayButton.setVisible(true); } return this; }; PlanningCalendar.prototype.setMaxDate = function(oDate){ if (deepEqual(oDate, this.getMaxDate())) { return this; } var oMinDate = this.getMinDate(); if (oDate) { CalendarUtils._checkJSDateObject(oDate); var iYear = oDate.getFullYear(); CalendarUtils._checkYearInValidRange(iYear); this.setProperty("maxDate", oDate, true); this._bNoStartDateChange = true; // set the start date after all calendars are updated INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef].setMaxDate(new Date(oDate.getTime())); // use new date object } }, this); if (oMinDate && oMinDate.getTime() > oDate.getTime()) { Log.warning("maxDate < minDate -> maxDate set to begin of the month", this); oMinDate = new Date(oDate.getTime()); oMinDate.setDate(1); oMinDate.setHours(0); oMinDate.setMinutes(0); oMinDate.setSeconds(0); oMinDate.setMilliseconds(0); this.setMinDate(oMinDate); } this._bNoStartDateChange = undefined; var oStartDate = this.getStartDate(); if (oStartDate && oStartDate.getTime() > oDate.getTime()) { Log.warning("StartDate > maxDate -> StartDate set to minDate", this); if (oMinDate) { oStartDate = new Date(oMinDate.getTime()); } else { oStartDate = new Date(1, 0, 1); oStartDate.setFullYear(1); } this.setStartDate(oStartDate); } } else { this.setProperty("maxDate", undefined, true); INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef].setMaxDate(); } }, this); } var oToday = new Date(); if (oDate && oToday.getTime() > oDate.getTime()) { this._oTodayButton.setVisible(false); } else if (!oMinDate || oToday.getTime() > oMinDate.getTime()) { this._oTodayButton.setVisible(true); } return this; }; PlanningCalendar.prototype.removeIntervalInstanceFromInfoToolbar = function () { var aInfoToolbarContent = this._oInfoToolbar.getContent(); aInfoToolbarContent.forEach(function (oControl) { if (oControl.isA(aIntervalRepresentatives)) { this._oInfoToolbar.removeContent(oControl); } }.bind(this)); }; PlanningCalendar.prototype.setViewKey = function(sKey){ var oInterval, oOldStartDate, oIntervalMetadata, sOldViewKey = this.getViewKey(), oSelectedDate; this.setProperty("viewKey", sKey, true); this._oIntervalTypeSelect.setSelectedKey(sKey); this.removeIntervalInstanceFromInfoToolbar(); if (sKey === PlanningCalendarBuiltInView.Week || sKey === PlanningCalendarBuiltInView.OneMonth || sKey === PlanningCalendarBuiltInView.Month) { oOldStartDate = this.getStartDate(); this.setStartDate(new Date(oOldStartDate.getTime())); //make sure the start date is aligned according to the week/month rules if (oOldStartDate.getTime() !== this.getStartDate().getTime()) { this.fireStartDateChange(); } } var oStartDate = this.getStartDate(); var oMinDate = this.getMinDate(); var oMaxDate = this.getMaxDate(); var oView = this._getView(sKey, !this._bBeforeRendering); if (!oView) { this._bCheckView = true; this.invalidate(); // view not exist now, maybe added later, so rerender } else { var sIntervalType = oView.getIntervalType(); var iIntervals = this._getIntervals(oView); this._bCheckView = false; // no additional check needed switch (sIntervalType) { case CalendarIntervalType.Hour: if (!this._oTimeInterval) { this._oTimeInterval = new sap.ui.unified.CalendarTimeInterval(this.getId() + "-TimeInt", { startDate: new Date(oStartDate.getTime()), // use new date object items: iIntervals, pickerPopup: true, legend: this.getLegend() }); this._setAriaRole(this._oTimeInterval); this._oTimeInterval.attachEvent("startDateChange", this._handleStartDateChange, this); this._oTimeInterval.attachEvent("select", this._handleCalendarSelect, this); this._oTimeInterval._oPlanningCalendar = this; this._oTimeInterval.getSpecialDates = function(){ return this._oPlanningCalendar.getSpecialDates(); }; if (oMinDate) { this._oTimeInterval.setMinDate(new Date(oMinDate.getTime())); } if (oMaxDate) { this._oTimeInterval.setMaxDate(new Date(oMaxDate.getTime())); } }else if (this._oTimeInterval.getItems() != iIntervals) { this._oTimeInterval.setItems(iIntervals); } this._insertInterval(this._oTimeInterval); break; case CalendarIntervalType.Day: case CalendarIntervalType.Week: case CalendarIntervalType.OneMonth: //Date, Week and OneMonth intervals share the same object artifacts oIntervalMetadata = INTERVAL_METADATA[sIntervalType]; oInterval = this[oIntervalMetadata.sInstanceName]; if (!oInterval) { oInterval = new oIntervalMetadata.oClass(this.getId() + oIntervalMetadata.sIdSuffix, { startDate: new Date(oStartDate.getTime()), // use new date object days: iIntervals, showDayNamesLine: this.getShowDayNamesLine(), pickerPopup: true, legend: this.getLegend(), showWeekNumbers: this.getShowWeekNumbers() }); this._setAriaRole(oInterval); oInterval.attachEvent("startDateChange", this._handleStartDateChange, this); oInterval.attachEvent("select", this._handleCalendarSelect, this); if (sKey === PlanningCalendarBuiltInView.OneMonth) { oInterval._setRowsStartDate = this._setRowsStartDate.bind(this); } oInterval._oPlanningCalendar = this; oInterval.getSpecialDates = function(){ return this._oPlanningCalendar.getSpecialDates(); }; if (oMinDate) { oInterval.setMinDate(new Date(oMinDate.getTime())); } if (oMaxDate) { oInterval.setMaxDate(new Date(oMaxDate.getTime())); } } else if (oInterval.getDays() !== iIntervals) { oInterval.setDays(iIntervals); } this._insertInterval(oInterval); this[oIntervalMetadata.sInstanceName] = oInterval; break; case CalendarIntervalType.Month: if (!this._oMonthInterval) { this._oMonthInterval = new sap.ui.unified.CalendarMonthInterval(this.getId() + "-MonthInt", { startDate: new Date(oStartDate.getTime()), // use new date object months: iIntervals, pickerPopup: true, legend: this.getLegend() }); this._setAriaRole(this._oMonthInterval); this._oMonthInterval.attachEvent("startDateChange", this._handleStartDateChange, this); this._oMonthInterval.attachEvent("select", this._handleCalendarSelect, this); this._oMonthInterval._oPlanningCalendar = this; this._oMonthInterval.getSpecialDates = function(){ return this._oPlanningCalendar.getSpecialDates(); }; if (oMinDate) { this._oMonthInterval.setMinDate(new Date(oMinDate.getTime())); } if (oMaxDate) { this._oMonthInterval.setMaxDate(new Date(oMaxDate.getTime())); } } else if (this._oMonthInterval.setMonths() != iIntervals) { this._oMonthInterval.setMonths(iIntervals); } this._insertInterval(this._oMonthInterval); break; default: throw new Error("Unknown IntervalType: " + sIntervalType + "; " + this); } var aRows = this.getRows(); for (var i = 0; i < aRows.length; i++) { var oRow = aRows[i]; var oTimeline = getRowTimeline(oRow); oTimeline.setIntervalType(sIntervalType); oTimeline.setIntervals(iIntervals); oTimeline.setShowSubIntervals(oView.getShowSubIntervals()); } if (this.getDomRef()) { // only set timer, CalendarRow will be re-rendered, so no update needed here this._updateCurrentTimeVisualization(false); adaptCalHeaderForWeekNumbers.call(this, this.getShowWeekNumbers(), this._viewAllowsWeekNumbers(sKey)); adaptCalHeaderForDayNamesLine.call(this, this.getShowDayNamesLine(), !!oInterval); } } if (this._oOneMonthInterval && sKey === PlanningCalendarBuiltInView.OneMonth) { this._oOneMonthInterval._setDisplayMode(this._iSize); this._oOneMonthInterval._adjustSelectedDate(CalendarDate.fromLocalJSDate(oOldStartDate)); if (this._iSize < 2) { this._setRowsStartDate(oOldStartDate); } } else if (this._oOneMonthInterval && sOldViewKey === PlanningCalendarBuiltInView.OneMonth && this._oOneMonthInterval.getSelectedDates().length) { oSelectedDate = this._oOneMonthInterval.getSelectedDates()[0].getStartDate(); if (oSelectedDate) { this.setStartDate(oSelectedDate); } } this._updateTodayButtonState(); return this; }; /** * Inserts the needed interval to the right position in the toolbar of the PlanningCalendar. * When the screen is big, the interval should be placed at the end. * Else - after(below) the calendar header. * @param {object} oInterval The interval to be placed in the toolbar * @private */ PlanningCalendar.prototype._insertInterval = function (oInterval) { if (this._iSizeScreen > 1) { // place the interval at the end. this._oInfoToolbar.addContent(oInterval); } else { // place the interval after the calendar header. this._oInfoToolbar.insertContent(oInterval, 1); } }; /** * Determines if the week numbers are visible for a given view. * @param {string} sViewKey The view key * @returns {boolean} true if the week numbers are allowed for the current view * @private */ PlanningCalendar.prototype._viewAllowsWeekNumbers = function(sViewKey) { var sIntervalType = this._getView(sViewKey).getIntervalType(), oIntervalMetadata = INTERVAL_METADATA[sIntervalType]; return !!oIntervalMetadata && !!oIntervalMetadata.oClass.prototype.setShowWeekNumbers; }; /** * Determines if the day names line is allowed for a given view. * @param {string} sViewKey The view key * @returns {boolean} true if the day names line is allowed for the current view * @private */ PlanningCalendar.prototype._viewAllowsDayNamesLine = function(sViewKey) { var sIntervalType = this._getView(sViewKey).getIntervalType(), oIntervalMetadata = INTERVAL_METADATA[sIntervalType]; return !!oIntervalMetadata && !!oIntervalMetadata.oClass.prototype.setShowDayNamesLine; }; /** * Returns the interval for a given view. * @param {string} sViewKey Key of a view * @returns {*} Interval instance in the passed view, if it is already created and has metadata, otherwise returns undefined. * @private */ PlanningCalendar.prototype._getIntervalInstanceByViewKey = function(sViewKey) { var sIntervalType = this._getView(sViewKey).getIntervalType(), oIntervalMetadata = INTERVAL_METADATA[sIntervalType], oInterval; if (oIntervalMetadata) { oInterval = this[oIntervalMetadata.sInstanceName]; } return oInterval; }; PlanningCalendar.prototype.setShowWeekNumbers = function (bValue) { this.setProperty("showWeekNumbers", bValue, true); this._getViews().forEach(function(oView) { var sViewKey = oView.getKey(), bViewAllowsWeekNumbers = this._viewAllowsWeekNumbers(sViewKey), oInterval = this._getIntervalInstanceByViewKey(sViewKey); if (oInterval && bViewAllowsWeekNumbers) { this._getIntervalInstanceByViewKey(sViewKey).setShowWeekNumbers(bValue); } //update the pc header classes if needed if (this.getDomRef() && this.getViewKey() === sViewKey) { adaptCalHeaderForWeekNumbers.call(this, bValue, bViewAllowsWeekNumbers); } }, this); return this; }; PlanningCalendar.prototype.setShowIntervalHeaders = function(bShowIntervalHeaders){ this.setProperty("showIntervalHeaders", bShowIntervalHeaders, true); var aRows = this.getRows(); for (var i = 0; i < aRows.length; i++) { var oRow = aRows[i]; getRowTimeline(oRow).setShowIntervalHeaders(bShowIntervalHeaders); } return this; }; PlanningCalendar.prototype.setShowEmptyIntervalHeaders = function(bShowEmptyIntervalHeaders){ this.setProperty("showEmptyIntervalHeaders", bShowEmptyIntervalHeaders, true); var aRows = this.getRows(); for (var i = 0; i < aRows.length; i++) { var oRow = aRows[i]; getRowTimeline(oRow).setShowEmptyIntervalHeaders(bShowEmptyIntervalHeaders); } return this; }; PlanningCalendar.prototype.setGroupAppointmentsMode = function (bGroupAppointmentsMode) { this.setProperty("groupAppointmentsMode", bGroupAppointmentsMode, true); var aRows = this.getRows(); for (var i = 0; i < aRows.length; i++) { var oRow = aRows[i]; getRowTimeline(oRow).setGroupAppointmentsMode(bGroupAppointmentsMode); } return this; }; PlanningCalendar.prototype.setAppointmentsReducedHeight = function(bAppointmentsReducedHeight){ this.setProperty("appointmentsReducedHeight", bAppointmentsReducedHeight, true); var aRows = this.getRows(); for (var i = 0; i < aRows.length; i++) { var oRow = aRows[i]; getRowTimeline(oRow).setAppointmentsReducedHeight(bAppointmentsReducedHeight); } return this; }; PlanningCalendar.prototype.setAppointmentsVisualization = function(sAppointmentsVisualization){ this.setProperty("appointmentsVisualization", sAppointmentsVisualization, true); var aRows = this.getRows(); for (var i = 0; i < aRows.length; i++) { var oRow = aRows[i]; getRowTimeline(oRow).setAppointmentsVisualization(sAppointmentsVisualization); } return this; }; PlanningCalendar.prototype.setShowRowHeaders = function(bShowRowHeaders){ // set header column to invisible as each row is a ColumnListItem with two columns // removing the column would need to change every row this.setProperty("showRowHeaders", bShowRowHeaders, true); var oTable = this.getAggregation("table"); oTable.getColumns()[0].setVisible(bShowRowHeaders); this._toggleAppointmentsColumnPopinState(bShowRowHeaders); this.$().toggleClass("sapMPlanCalNoHead", !bShowRowHeaders); positionSelectAllCheckBox.call(this); setSelectionMode.call(this); return this; }; PlanningCalendar.prototype.setShowDayNamesLine = function(bShowDayNamesLine){ var intervalMetadata, sInstanceName, oCalDateInterval, bRendered = !!this.getDomRef(), sCurrentViewKey = this.getViewKey(); for (intervalMetadata in INTERVAL_METADATA) { sInstanceName = INTERVAL_METADATA[intervalMetadata].sInstanceName; if (this[sInstanceName]) { oCalDateInterval = this[sInstanceName]; oCalDateInterval.setShowDayNamesLine(bShowDayNamesLine); if (bRendered && intervalMetadata === sCurrentViewKey) { adaptCalHeaderForDayNamesLine.call(this, bShowDayNamesLine, true); } } } return this.setProperty("showDayNamesLine", bShowDayNamesLine, false); }; /** * Sets the stickyHeader property. * @override * @public * @param {boolean} bStick Whether the header area will remain visible (fixed on top) * @returns {sap.m.PlanningCalendar} this pointer for chaining */ PlanningCalendar.prototype.setStickyHeader = function(bStick) { if (this.getStickyHeader() === bStick) { return this; } this.setProperty("stickyHeader", bStick, true