UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,446 lines (1,236 loc) 176 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.PlanningCalendar. sap.ui.define([ 'sap/m/delegate/DateNavigation', '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/calendar/DatesRow', 'sap/ui/unified/calendar/OneMonthDatesRow', 'sap/ui/unified/calendar/MonthsRow', 'sap/ui/unified/calendar/TimesRow', 'sap/ui/unified/DateRange', 'sap/ui/unified/DateTypeRange', 'sap/ui/unified/CalendarAppointment', 'sap/ui/unified/CalendarRow', 'sap/ui/unified/CalendarRowRenderer', 'sap/ui/Device', 'sap/ui/core/Core', 'sap/ui/core/Element', 'sap/ui/core/Renderer', 'sap/ui/core/ResizeHandler', 'sap/ui/core/InvisibleText', 'sap/ui/core/dnd/DragInfo', 'sap/ui/core/dnd/DropInfo', 'sap/ui/core/dnd/DragDropInfo', 'sap/ui/core/format/DateFormat', 'sap/ui/core/Configuration', 'sap/ui/core/date/CalendarWeekNumbering', 'sap/ui/core/date/CalendarUtils', 'sap/ui/core/Locale', "sap/ui/core/date/UI5Date", 'sap/m/Toolbar', 'sap/m/Table', 'sap/m/Column', 'sap/m/ColumnListItem', 'sap/m/ColumnListItemRenderer', 'sap/m/SegmentedButtonItem', 'sap/m/StandardListItem', 'sap/m/PlanningCalendarHeader', 'sap/m/PlanningCalendarRenderer', 'sap/m/PlanningCalendarView', 'sap/m/CheckBox', 'sap/m/library', "sap/base/util/deepEqual", "sap/base/Log", "sap/m/List", "sap/ui/thirdparty/jquery", "sap/ui/dom/jquery/control" // jQuery Plugin "control" ], function( DateNavigation, Control, ManagedObjectObserver, unifiedLibrary, CalendarUtils, CalendarDate, DatesRow, OneMonthDatesRow, MonthsRow, TimesRow, DateRange, DateTypeRange, CalendarAppointment, CalendarRow, CalendarRowRenderer, Device, Core, Element, Renderer, ResizeHandler, InvisibleText, DragInfo, DropInfo, DragDropInfo, DateFormat, Configuration, CalendarWeekNumbering, CalendarDateUtils, Locale, UI5Date, Toolbar, Table, Column, ColumnListItem, ColumnListItemRenderer, SegmentedButtonItem, StandardListItem, PlanningCalendarHeader, PlanningCalendarRenderer, PlanningCalendarView, CheckBox, library, deepEqual, Log, List, jQuery ) { "use strict"; // shortcut for sap.m.Sticky var Sticky = library.Sticky; // shortcut for sap.ui.unified.CalendarDayType var CalendarDayType = unifiedLibrary.CalendarDayType; // shortcut for sap.m.ListMode var ListMode = library.ListMode; // shortcut for sap.m.ToolbarDesign var ToolbarDesign = library.ToolbarDesign; // shortcut for sap.m.PlanningCalendarBuiltInView var PlanningCalendarBuiltInView = library.PlanningCalendarBuiltInView; // shortcut for sap.m.ScreenSize var ScreenSize = library.ScreenSize; // shortcut for sap.ui.unified.CalendarAppointmentVisualization var CalendarAppointmentVisualization = unifiedLibrary.CalendarAppointmentVisualization; // shortcut for sap.ui.unified.GroupAppointmentsMode var GroupAppointmentsMode = unifiedLibrary.GroupAppointmentsMode; // shortcut for sap.ui.unified.CalendarIntervalType var CalendarIntervalType = unifiedLibrary.CalendarIntervalType; // shortcut for sap.ui.unified.CalendarAppointmentHeight var CalendarAppointmentHeight = unifiedLibrary.CalendarAppointmentHeight; // shortcut for sap.ui.unified.CalendarAppointmentRoundWidth var CalendarAppointmentRoundWidth = unifiedLibrary.CalendarAppointmentRoundWidth; var DRAG_DROP_CONFIG_NAME = "DragDropConfig"; var RESIZE_CONFIG_NAME = "ResizeConfig"; var CREATE_CONFIG_NAME = "CreateConfig"; var LISTITEM_SUFFIX = "-CLI"; /** * Constructor for a new <code>PlanningCalendar</code>. * * @param {string} [sID] ID for the new control, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new control * * @class * Displays rows with appointments for different entities (such as persons or teams) for the selected time interval. * * <h3>Overview</h3> * * You can use the <code>PlanningCalendar</code> to represent a calendar containing multiple rows with * appointments, where each row represents a different person. * * You can configure different time-interval views that the user can switch between, such as hours or days, and even * a whole week/month. The available navigation allows the user to select a specific interval using a picker, or * move to the previous/next interval using arrows. * * <b>Note:</b> The application developer should add dependency to <code>sap.ui.unified</code> library * on application level to ensure that the library is loaded before the module dependencies will be required. * The <code>PlanningCalendar</code> uses parts of the <code>sap.ui.unified</code> library. * This library will be loaded after the <code>PlanningCalendar</code>, if it wasn't loaded first. * This could lead to CSP compliance issues and adds an additional waiting time when a <code>PlanningCalendar</code> is used for the first time. * To prevent this, apps that use the <code>PlanningCalendar</code> should also load the * <code>sap.ui.unified</code> library in advance. * * <h3>Usage</h3> * * The <code>PlanningCalendar</code> has the following structure from top to bottom: * * <ul> * <li>A toolbar where you can add your own buttons or other controls using the <code>toolbarContent</code> aggregation.</li> * <li>A header containing a drop-down menu for selecting the {@link sap.m.PlanningCalendarView PlanningCalendarViews}, * and navigation for moving through the intervals using arrows or selecting a specific interval with a picker. * Custom views can be configured using the <code>views</code> aggregation. If not configured, the following set of default * built-in views is available - Hours, Days, 1 Week, 1 Month, and Months. Setting a custom view(s) replaces the built-in ones.</li> * <li>The rows of the <code>PlanningCalendar</code> that contain the assigned appointments. * They can be configured with the <code>rows</code> aggregation, which is of type * {@link sap.m.PlanningCalendarRow PlanningCalendarRow}.</li> * </ul> * * Since 1.48 the empty space in the cell that is below an appointment can be removed by adding * the <code>sapUiCalendarAppFitVertically</code> CSS class to the <code>PlanningCalendar</code>. * Please note that it should be used only for a <code>PlanningCalendar</code> with one appointment per day * for a row that doesn't have interval headers set. * * Since 1.44 alternating row colors can be suppressed by adding the <code>sapMPlanCalSuppressAlternatingRowColors</code> * CSS class to the <code>PlanningCalendar</code>. * * <h3>Responsive behavior</h3> * * You can define the number of displayed intervals based on the size of the <code>PlanningCalendar</code> using the * {@link sap.m.PlanningCalendarView PlanningCalendarView}'s properties. * * @extends sap.ui.core.Control * @version 1.117.4 * * @constructor * @public * @since 1.34 * @alias sap.m.PlanningCalendar * @see {@link fiori:https://experience.sap.com/fiori-design-web/planning-calendar/ Planning Calendar} */ var PlanningCalendar = Control.extend("sap.m.PlanningCalendar", /** @lends sap.m.PlanningCalendar.prototype */ { metadata : { library : "sap.m", properties : { /** * Determines the start date of the row, as a UI5Date or JavaScript Date object. The current date is used as default. */ startDate : {type : "object", group : "Data"}, /** * Defines the key of the <code>PlanningCalendarView</code> used for the output. * * <b>Note:</b> The default value is set <code>Hour</code>. If you are using your own views, the keys of these * views should be used instead. */ viewKey : {type : "string", group : "Appearance", defaultValue : CalendarIntervalType.Hour}, /** * Determines whether only a single row can be selected. */ singleSelection : {type : "boolean", group : "Misc", defaultValue : true}, /** * Specifies the width of the <code>PlanningCalendar</code>. */ width : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null}, /** * Specifies the height of the <code>PlanningCalendar</code>. * <b>Note:</b> If the set height is less than the displayed content, it will not be applied */ height : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null}, /** * Determines whether the assigned interval headers are displayed. You can assign them using the * <code>intervalHeaders</code> aggregation of the {@link sap.m.PlanningCalendarRow PlanningCalendarRow}. * * <b>Note:</b> If you set both <code>showIntervalHeaders</code> and <code>showEmptyIntervalHeaders</code> * properties to <code>true</code>, the space (at the top of the intervals) where the assigned interval * headers appear, will remain visible even if no interval headers are assigned. */ showIntervalHeaders : {type : "boolean", group : "Appearance", defaultValue : true}, /** * Determines whether the space (at the top of the intervals), where the assigned interval headers appear, should remain * visible even when no interval headers are present in the visible time frame. If set to <code>false</code>, this * space would collapse/disappear when no interval headers are assigned. * * <b>Note:</b> This property takes effect, only if <code>showIntervalHeaders</code> is also set to <code>true</code>. * @since 1.38.0 */ showEmptyIntervalHeaders : {type : "boolean", group : "Appearance", defaultValue : true}, /** * Determines whether the column containing the headers of the {@link sap.m.PlanningCalendarRow PlanningCalendarRows} * is displayed. */ showRowHeaders : {type : "boolean", group : "Appearance", defaultValue : true}, /** * Defines the text that is displayed when no {@link sap.m.PlanningCalendarRow PlanningCalendarRows} are assigned. */ 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 the different possible sizes for appointments. * @since 1.81.0 */ appointmentHeight: { type: "sap.ui.unified.CalendarAppointmentHeight", group: "Appearance", defaultValue: CalendarAppointmentHeight.Regular }, /** * Defines rounding of the width <code>CalendarAppoinment</code> * <b>Note:</b> This property is applied, when the calendar interval type is day and the view shows more than 20 days * @experimental Since 1.81.0 * @since 1.81.0 */ appointmentRoundWidth: { type: "sap.ui.unified.CalendarAppointmentRoundWidth", group: "Appearance", defaultValue: CalendarAppointmentRoundWidth.None}, /** * Determines how the appointments are visualized depending on the used theme. * @since 1.40.0 */ appointmentsVisualization : {type : "sap.ui.unified.CalendarAppointmentVisualization", group : "Appearance", defaultValue : CalendarAppointmentVisualization.Standard}, /** * Defines the minimum date that can be displayed and selected in the <code>PlanningCalendar</code>. * This must be a UI5Date or JavaScript Date object. * * <b>Note:</b> If the <code>minDate</code> is set to be after the current <code>maxDate</code>, * the <code>maxDate</code> is set to the last date of the month in which the <code>minDate</code> belongs. * @since 1.38.0 */ minDate : {type : "object", group : "Misc", defaultValue : null}, /** * Defines the maximum date that can be displayed and selected in the <code>PlanningCalendar</code>. * This must be a UI5Date or JavaScript Date object. * * <b>Note:</b> If the <code>maxDate</code> is set to be before the current <code>minDate</code>, * the <code>minDate</code> is set to the first date of the month in which the <code>maxDate</code> belongs. * @since 1.38.0 */ maxDate : {type : "object", group : "Misc", defaultValue : null}, /** * Determines whether the day names are displayed in a separate line or inside the single days. * @since 1.50 */ showDayNamesLine : {type : "boolean", group : "Appearance", defaultValue : false}, /** * Determines if the week numbers are displayed. * @since 1.52 */ showWeekNumbers : {type : "boolean", group : "Appearance", defaultValue : false}, /** * Defines the list of predefined views as an array. * The views should be specified by their keys. * * The default predefined views and their keys are available at * {@link sap.m.PlanningCalendarBuiltInView}. * * <b>Note:</b> If set, all specified views will be displayed along * with any custom views (if available). If not set and no custom * views are available, all default views will be displayed. * If not set and there are any custom views available, only the * custom views will be displayed. * @since 1.50 */ builtInViews : {type : "string[]", group : "Appearance", defaultValue : []}, /** * Determines whether the header area will remain visible (fixed on top) when the rest of the content is scrolled out of view. * * The sticky header behavior is automatically disabled on phones in landscape mode for better visibility of the content. * * <b>Note:</b> There is limited browser support, hence the API is in experimental state. * Browsers that currently support this feature are Chrome (desktop and mobile), Safari (desktop and mobile) and Edge 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 restrictions of the property in <code>sap.m.Table</code> apply to the <code>PlanningCalendar</code> as well. * @since 1.54 */ stickyHeader : {type : "boolean", group : "Appearance", defaultValue : false}, /** * If set, the first day of the displayed week is this day. Valid values are 0 to 6 starting on Sunday. * If there is no valid value set, the default of the used locale is used. * * Note: this property will only have effect in the weekly – based views of the PlanningCalendar – Week view, * and OneMonth view (on small devices). * * @since 1.94 */ firstDayOfWeek : {type : "int", group : "Appearance", defaultValue : -1}, /** * If set, the calendar week numbering is used for display. * If not set, the calendar week numbering of the global configuration is used. * @since 1.110.0 */ calendarWeekNumbering : { type : "sap.ui.core.date.CalendarWeekNumbering", group : "Appearance", defaultValue: null}, /** * If set, the calendar type is used for display. * If not set, the calendar type of the global configuration is used. * @since 1.108.0 */ primaryCalendarType : {type : "sap.ui.core.CalendarType", group : "Appearance"}, /** * If set, the days are also represented in this calendar type. * If not set, the dates are only represented in the primary calendar type. * Note: The second calendar type won't be represented in the DOM when this property is not set explicitly. * @since 1.109.0 */ secondaryCalendarType : {type : "sap.ui.core.CalendarType", group : "Appearance"}, /** * Determines whether the selection of multiple appointments is enabled. * * Note: selection of multiple appointments is possible using CTRL key regardless of the value of this property. * * @since 1.97 */ multipleAppointmentsSelection : {type : "boolean", group : "Data", defaultValue : false} }, 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", forwarding : { getter : "_getHeader", aggregation : "actions" } }, /** * Hidden, for internal use only. */ table : {type : "sap.m.Table", multiple : false, visibility : "hidden"}, /** * Hidden, for internal use only. */ header : {type : "sap.ui.core.Control", multiple : false, visibility : "hidden"} }, associations: { /** * Association to controls / IDs which label this control (see WAI-ARIA attribute aria-labelledby). * @since 1.40.0 */ ariaLabelledBy: { type: "sap.ui.core.Control", multiple: true, singularName: "ariaLabelledBy" }, /** * Association to the <code>CalendarLegend</code> explaining the colors of the <code>Appointments</code>. * * <b>Note:</b> The legend does not have to be rendered but must exist, and all required types must be assigned. * @since 1.40.0 */ legend: { type: "sap.ui.unified.CalendarLegend", multiple: false} }, events : { /** * Fired if an appointment is selected. */ appointmentSelect : { parameters : { /** * The selected appointment. */ appointment : {type : "sap.ui.unified.CalendarAppointment"}, /** * The selected appointments in case a group appointment is selected. */ appointments : {type : "sap.ui.unified.CalendarAppointment[]"}, /** * If set, the appointment was selected using multiple selection (e.g. Shift + single mouse click), * meaning more than the current appointment could be selected. */ multiSelect : {type : "boolean"}, /** * Gives the ID of the DOM element of the clicked appointment * @since 1.50.0 */ domRefId: {type: "string"} } }, /** * Fired if an interval was selected in the calendar header or in the row. */ intervalSelect : { parameters : { /** * Start date of the selected interval, as a UI5Date or JavaScript Date object. */ startDate : {type : "object"}, /** * Interval end date as a UI5Date or JavaScript Date object. * @since 1.38.0 */ endDate : {type : "object"}, /** * If set, the selected interval is a subinterval. * @since 1.38.0 */ subInterval : {type : "boolean"}, /** * Row of the selected interval. * @since 1.38.0 */ row : {type : "sap.m.PlanningCalendarRow"} } }, /** * Fires when row selection is changed. */ rowSelectionChange : { parameters : { /** * Array of rows whose selection has changed. */ rows : {type : "sap.m.PlanningCalendarRow[]"} } }, /** * Fired when the <code>startDate</code> property was changed while navigating in the <code>PlanningCalendar</code>. * The new value can be obtained using the <code>sap.m.PlanningCalendar#getStartDate()</code> method. * <b>Note:</b> This event is fired in case when the <code>viewKey</code> property is changed, and as a result of * which the view requires a change in the <code>startDate</code> property. */ startDateChange : {}, /** * Fired when the <code>viewKey</code> property was changed by user interaction. */ viewChange : {}, /** * Fires when a row header is clicked. * @since 1.46.0 */ rowHeaderClick: { parameters : { /** * The ID of the <code>PlanningCalendarRowHeader</code> of the selected appointment. * * <b>Note:</b> Intended to be used as an easy way to get an ID of a <code>PlanningCalendarRowHeader</code>. Do NOT use for modification. * * @since 1.73 */ headerId : {type : "string"}, /** * The row user clicked on. */ row : {type : "sap.m.PlanningCalendarRow"} } } }, designtime: "sap/m/designtime/PlanningCalendar.designtime" }, constructor: function(vId, mSettings) { Control.prototype.constructor.apply(this, arguments); if (typeof vId !== "string"){ mSettings = vId; } if (mSettings && typeof mSettings.customAppointmentsSorterCallback === "function") { this._fnCustomSortedAppointments = mSettings.customAppointmentsSorterCallback; } }, renderer: PlanningCalendarRenderer }); //List of private properties controlling different intervals var INTERVAL_CTR_REFERENCES = ["_oTimesRow", "_oDatesRow", "_oMonthsRow", "_oWeeksRow", "_oOneMonthsRow"], //Holds metadata of the different interval instances that should be created. INTERVAL_METADATA = {}; INTERVAL_METADATA[CalendarIntervalType.Day] = { sInstanceName: "_oDatesRow", sIdSuffix: "-DatesRow", oClass: DatesRow }; INTERVAL_METADATA[CalendarIntervalType.Week] = { sInstanceName: "_oWeeksRow", sIdSuffix: "-WeeksRow", oClass: DatesRow }; INTERVAL_METADATA[CalendarIntervalType.OneMonth] = { sInstanceName: "_oOneMonthsRow", sIdSuffix: "-OneMonthsRow", oClass: OneMonthDatesRow }; //Defines the minimum screen width for the appointments column (it is a popin column) var APP_COLUMN_MIN_SCREEN_WIDTH = ScreenSize.Desktop; //All supported built-in views var KEYS_FOR_ALL_BUILTIN_VIEWS = [ PlanningCalendarBuiltInView.Hour, PlanningCalendarBuiltInView.Day, PlanningCalendarBuiltInView.Month, PlanningCalendarBuiltInView.Week, PlanningCalendarBuiltInView.OneMonth]; var SCREEEN_BREAKPOINTS = { PHONE: "600", TABLET: "1024" }; // Holds the possible values for the "_currentPicker" property in the currentPicker association of the header var CURRENT_PICKERS = { MONTH: "month", // represents the "month" aggregation MONTH_PICKER: "monthPicker", // represents the "monthPicker" aggregation YEAR_PICKER: "yearPicker", // represents the "yearPicker" aggregation YEAR_RANGE_PICKER: "yearRangePicker" // represents the "yearRangePicker" aggregation }; var MONTH_DELEGATE = { onAfterRendering: function () { this.setProperty("_currentPicker", CURRENT_PICKERS.MONTH); this.removeDelegate(MONTH_DELEGATE); } }; var MONTH_PICKER_DELEGATE = { onAfterRendering: function () { this.setProperty("_currentPicker", CURRENT_PICKERS.MONTH_PICKER); this.removeDelegate(MONTH_PICKER_DELEGATE); } }; var YEAR_PICKER_DELEGATE = { onAfterRendering: function () { this.setProperty("_currentPicker", CURRENT_PICKERS.YEAR_PICKER); this.removeDelegate(YEAR_PICKER_DELEGATE); } }; var aIntervalRepresentatives = [ "sap.ui.unified.calendar.TimesRow", "sap.ui.unified.calendar.DatesRow", "sap.ui.unified.calendar.MonthsRow", "sap.ui.unified.calendar.OneMonthDatesRow" ]; var CalendarHeader = Control.extend("sap.m._PlanningCalendarInternalHeader", { metadata : { aggregations: { "toolbar" : {type: "sap.m.Toolbar", multiple: false}, "allCheckBox" : {type: "sap.m.CheckBox", multiple: false} } }, renderer : { apiVersion: 2, render: function(oRm, oHeader) { oRm.openStart("div", oHeader); oRm.class("sapMPlanCalHead"); oRm.openEnd(); var oToolbar = oHeader.getToolbar(); if (oToolbar) { oRm.renderControl(oToolbar); } var oAllCB = oHeader.getAllCheckBox(); if (oAllCB) { oRm.renderControl(oAllCB); } oRm.close("div"); } } }); PlanningCalendar.prototype.init = function(){ this._dateNav = new DateNavigation(); this._iBreakPointTablet = Device.media._predefinedRangeSets[Device.media.RANGESETS.SAP_STANDARD_EXTENDED].points[0]; this._iBreakPointDesktop = Device.media._predefinedRangeSets[Device.media.RANGESETS.SAP_STANDARD_EXTENDED].points[1]; this._iBreakPointLargeDesktop = Device.media._predefinedRangeSets[Device.media.RANGESETS.SAP_STANDARD_EXTENDED].points[2]; if (Device.system.phone || jQuery('html').hasClass("sapUiMedia-Std-Phone")) { this._iSize = 0; this._iSizeScreen = 0; } else if ((Device.system.tablet || jQuery('html').hasClass("sapUiMedia-Std-Tablet")) && !(Device.system.desktop || jQuery('html').hasClass("sapUiMedia-Std-Desktop"))){ this._iSize = 1; this._iSizeScreen = 1; } else { this._iSize = 2; this._iSizeScreen = 2; } this.addStyleClass("sapMSize" + this._iSize); this.setAggregation("header", this._createHeader()); this._attachHeaderEvents(); this._oRB = Core.getLibraryResourceBundle("sap.m"); var sId = this.getId(); this._oIntervalTypeSelect = this._getHeader()._getOrCreateViewSwitch(); this._oIntervalTypeSelect.attachEvent("selectionChange", changeIntervalType, this); this._oTodayButton = this._getHeader()._getTodayButton(); this._oCalendarHeader = new CalendarHeader(sId + "-CalHead", {}); this._oCalendarHeader.isRelative = this.isRelative.bind(this); this._oCalendarHeader._getRelativeInfo = this._getRelativeInfo.bind(this); this._oInfoToolbar = new Toolbar(sId + "-InfoToolbar", { height: "auto", design: ToolbarDesign.Transparent, content: [this._oCalendarHeader, this._oTimesRow], ariaLabelledBy: InvisibleText.getStaticId("sap.m", "PC_INTERVAL_TOOLBAR") }); 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").on("click", function (oEvent) { var oRowHeader = Element.closestTo(oEvent.currentTarget), oRow = getRow(oRowHeader.getParent()), sRowHeaderId = oRowHeader.getId(); this.fireRowHeaderClick({headerId: sRowHeaderId, row: oRow}); }.bind(this)); this._adjustColumnHeadersTopOffset(); } }, false, this); oTable.getStickyFocusOffset = getStickyFocusOffset.bind(this); this.setAggregation("table", oTable, true); this.setStartDate(UI5Date.getInstance()); this._resizeProxy = handleResize.bind(this); this._fnCustomSortedAppointments = undefined; //transfers a custom appointments sorter function to the CalendarRow this.iWidth = 0; }; PlanningCalendar.prototype.exit = function(){ if (this._sResizeListener) { ResizeHandler.deregister(this._sResizeListener); this._sResizeListener = undefined; } Device.orientation.detachHandler(this._updateStickyHeader, this); if (this._sUpdateCurrentTime) { clearTimeout(this._sUpdateCurrentTime); this._sUpdateCurrentTime = undefined; } // remove ColumnListItems from table to not destroy them with table but from parent PlanningCalendarRow var oTable = this.getAggregation("table"); oTable.removeAllItems(); // destroy also currently not used controls INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef]._oPlanningCalendar = undefined; this[sControlRef].destroy(); this[sControlRef] = undefined; } }, this); if (this._oViews) { for (var sKey in this._oViews) { this._oViews[sKey].destroy(); } } if (this._oSelectAllCheckBox) { this._oSelectAllCheckBox.destroy(); } // Remove event listener for rowHeaderClick event if (this._rowHeaderClickEvent) { this._rowHeaderClickEvent.off(); this._rowHeaderClickEvent = null; } }; PlanningCalendar.prototype.onBeforeRendering = function(){ this._bBeforeRendering = true; this._updateHeader(); // init interval if default one is used this._adjustViewKey(); updateSelectItems.call(this); if (this._sUpdateCurrentTime) { clearTimeout(this._sUpdateCurrentTime); this._sUpdateCurrentTime = undefined; } this._updatePickerSelection(); this._updateHeaderButtons(); Device.orientation.detachHandler(this._updateStickyHeader, this); this._bBeforeRendering = undefined; this._toggleStickyClasses(); }; PlanningCalendar.prototype._updateHeader = function () { this._getHeader().setProperty("_primaryCalendarType", this._getPrimaryCalendarType()); if (this._getSecondaryCalendarType()) { this.setShowDayNamesLine(true); this._getHeader().setProperty("_secondaryCalendarType", this.getSecondaryCalendarType()); } return this; }; PlanningCalendar.prototype.attachEvent = function (eventId, data, functionToCall, listener) { Control.prototype.attachEvent.call(this, eventId, data, functionToCall, listener); if (this.hasListeners("intervalSelect")) { INTERVAL_CTR_REFERENCES.forEach(function (sControlRef) { if (this[sControlRef]) { this[sControlRef]._setAriaRole("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; }; /** * Creates the header and adds proper <code>ariaLabelledBy</code> references on its toolbars. * * @returns {sap.m.PlanningCalendarHeader} The created header * @private */ PlanningCalendar.prototype._createHeader = function () { var oHeader = new PlanningCalendarHeader(this.getId() + "-Header", { calendarWeekNumbering: this.getCalendarWeekNumbering() }); oHeader._getRelativeInfo = this._getRelativeInfo.bind(this); oHeader.getAggregation("_actionsToolbar") .addAriaLabelledBy(InvisibleText.getStaticId("sap.m", "PC_FUNCTIONS_TOOLBAR")); oHeader.getAggregation("_navigationToolbar") .addAriaLabelledBy(InvisibleText.getStaticId("sap.m", "PC_INTERVAL_SELECTION_TOOLBAR")); return oHeader; }; /** * Attaches handlers to the events in the _header aggregation. * * @returns {this} this for method chaining * @private */ PlanningCalendar.prototype._attachHeaderEvents = function () { var oHeader = this._getHeader(); oHeader.attachEvent("pressPrevious", this._handlePressArrow, this); oHeader.attachEvent("pressToday", this._handleTodayPress, this); oHeader.attachEvent("pressNext", this._handlePressArrow, this); oHeader.attachEvent("dateSelect", this._handleDateSelect, this); return this; }; /** * Handler for the pressPrevious and pressNext events in the _header aggregation. * @param {object} oEvent The triggered event * @private */ PlanningCalendar.prototype._handlePressArrow = function (oEvent) { this._applyArrowsLogic(oEvent.getId() === "pressPrevious"); }; /** * Logic for moving the selected time range in the control via the navigation arrows. * @param {boolean} bBackwards Whether the left arrow is pressed * @private */ PlanningCalendar.prototype._applyArrowsLogic = function(bBackwards) { if (bBackwards) { this._dateNav.previous(this._getPrimaryCalendarType()); } else { this._dateNav.next(this._getPrimaryCalendarType()); } if (this.getMinDate() && this._dateNav.getStart().getTime() <= this.getMinDate().getTime()) { this._dateNav.setStart(this.getMinDate()); this._dateNav.setCurrent(this.getMinDate()); } if (this.getMaxDate() && this._dateNav.getEnd().getTime() >= this.getMaxDate().getTime()) { this._dateNav.setStart(this.getMaxDate()); this._dateNav.setCurrent(this.getMaxDate()); } var oRow = this._getRowInstanceByViewKey(this.getViewKey()); this.setStartDate(this._dateNav.getStart()); oRow.displayDate(this._dateNav.getCurrent()); this._updatePickerSelection(); this.fireStartDateChange(); }; /** * Creates and formats the strings to be displayed in the picker button from the _header aggregation. * If the date part of the start and end range of the showed view are different, the string contains * info about the current date. Otherwise, the result string shows info about a date range. * @returns {Object} The concatenated strings to be displayed * @private */ PlanningCalendar.prototype._formatPickerText = function () { var oRangeDates = this._getFirstAndLastRangeDate(), oStartDate = CalendarUtils._createLocalDate(oRangeDates.oStartDate, true), oEndDate = CalendarUtils._createLocalDate(oRangeDates.oEndDate, true), sViewKey = this.getViewKey(), sViewType = CalendarIntervalType[sViewKey] ? CalendarIntervalType[sViewKey] : this._getView(sViewKey).getIntervalType(), bRTL = Core.getConfiguration().getRTL(), oDateFormat, sResult, sBeginningResult, sEndResult, oDateFormatInSecType, sBeginnigDateResultInSecType, sEndResultInSecType, sResultInSecType; if (this._getSecondaryCalendarType()) { oDateFormatInSecType = DateFormat.getDateInstance({ format: "yMMMMd", calendarType: this.getSecondaryCalendarType() }); } switch (sViewType) { case CalendarIntervalType.Hour: oDateFormat = DateFormat.getDateInstance({format: "yMMMMd", calendarType: this._getPrimaryCalendarType()}); sBeginningResult = oDateFormat.format(oStartDate); if (oStartDate.getDate() !== oEndDate.getDate()) { sEndResult = oDateFormat.format(oEndDate); } if (this._getSecondaryCalendarType()){ sBeginnigDateResultInSecType = oDateFormatInSecType.format(oStartDate); if (oStartDate.getDate() !== oEndDate.getDate()) { sEndResultInSecType = oDateFormatInSecType.format(oEndDate); } } break; case CalendarIntervalType.Day: case CalendarIntervalType.Week: var fnIntervalLabelFormatter = this._getRelativeInfo().intervalLabelFormatter; if (this.isRelative()) { var iBeginning = this.calcIntervalOffset(this.getStartDate()); var iEnding = this.calcIntervalOffset(this.getEndDate()) - this.calcIntervalOffset(this.getStartDate()); sBeginningResult = fnIntervalLabelFormatter ? fnIntervalLabelFormatter(iBeginning) : iBeginning; var iEndingParameter = this._getRelativeInfo().iIntervalSize === 1 ? iBeginning + iEnding : iBeginning + iEnding - 1; sEndResult = fnIntervalLabelFormatter ? fnIntervalLabelFormatter(iEndingParameter) : iBeginning + iEnding; } else { oDateFormat = DateFormat.getDateInstance({ format: "yMMMMd", calendarType: this._getPrimaryCalendarType() }); sBeginningResult = oDateFormat.format(oStartDate); sEndResult = oDateFormat.format(oEndDate); if (this._getSecondaryCalendarType()) { sBeginnigDateResultInSecType = oDateFormatInSecType.format(oStartDate); sEndResultInSecType = oDateFormatInSecType.format(oEndDate); } } break; case CalendarIntervalType.OneMonth: case "OneMonth": oDateFormat = DateFormat.getDateInstance({format: "yMMMM", calendarType: this._getPrimaryCalendarType()}); sBeginningResult = oDateFormat.format(oStartDate); if (this._getSecondaryCalendarType()) { oDateFormatInSecType = DateFormat.getDateInstance({format: "yMMMM", calendarType: this.getSecondaryCalendarType()}); sBeginnigDateResultInSecType = oDateFormatInSecType.format(oStartDate); } break; case CalendarIntervalType.Month: oDateFormat = DateFormat.getDateInstance({format: "y", calendarType: this._getPrimaryCalendarType()}); sBeginningResult = oDateFormat.format(oStartDate); if (this._getSecondaryCalendarType()) { oDateFormatInSecType = DateFormat.getDateInstance({format: "y", calendarType: this.getSecondaryCalendarType()}); sBeginnigDateResultInSecType = oDateFormatInSecType.format(oStartDate); } if (oStartDate.getFullYear() !== oEndDate.getFullYear()) { sEndResult = oDateFormat.format(oEndDate); if (this._getSecondaryCalendarType()) { sEndResultInSecType = oDateFormatInSecType.format(oEndDate); } } if (sBeginningResult === sEndResult) { sEndResult = undefined; } break; default: throw new Error("Unknown IntervalType: " + sViewKey + "; " + this); } if (!bRTL) { sResult = sBeginningResult; sResultInSecType = sBeginnigDateResultInSecType; if (sEndResult) { sResult += " - " + sEndResult; } if (this._getSecondaryCalendarType() && sEndResultInSecType) { sResultInSecType += " - " + sEndResultInSecType; } } else { if (sEndResult) { sResult = sEndResult + " - " + sBeginningResult; } else { sResult = sBeginningResult; } if (this._getSecondaryCalendarType() && sEndResultInSecType) { sResultInSecType = sBeginnigDateResultInSecType + " - " + sEndResultInSecType; } else if (this._getSecondaryCalendarType()) { sResultInSecType = sBeginnigDateResultInSecType; } } return { primaryType: sResult, secondaryType: sResultInSecType }; }; PlanningCalendar.prototype._getPrimaryCalendarType = function(){ return this.getProperty("primaryCalendarType") || Configuration.getCalendarType(); }; /** * Calculates the first and the last date of the range to be displayed. The size of the range depends on the * currently selected view. * @returns {object} Two properties containing the first and the last date from the range * @private */ PlanningCalendar.prototype._getFirstAndLastRangeDate = function () { var oUniStartDate = CalendarUtils._createUniversalUTCDate(this.getStartDate(), this._getPrimaryCalendarType(), true), oUniEndDate = CalendarUtils._createUniversalUTCDate(this._dateNav.getEnd(), this._getPrimaryCalendarType(), true); return { oStartDate: oUniStartDate, oEndDate: oUniEndDate }; }; /** * Getter for header aggregation. * * @returns {sap.m.PlanningCalendarHeader} The header object * @private */ PlanningCalendar.prototype._getHeader = function () { return this.getAggregation("header"); }; /** * Applies or removes sticky class based on <code>stickyHeader</code>'s value. * * @returns {this} <code>this</code> for chaining * @private */ PlanningCalendar.prototype._toggleStickyClasses = function () { this.toggleStyleClass("sapMPCSticky", this.getStickyHeader()); return this; }; /** * Makes sure that the column headers are offset in such a way, that they are positioned right * after the navigation toolbar. * * @returns {this} <code>this</code> for chaining * @private */ PlanningCalendar.prototype._adjustColumnHeadersTopOffset = function () { var bStickyMode = this.getStickyHeader(), oColumnHeaders = this.getDomRef().querySelector(".sapMListInfoTBarContainer"), iTop; switch (bStickyMode) { case true: // Since the header will be visible, columnHeaders should be offset by its height. iTop = this._getHeader().$().outerHeight() + "px"; break; default: // Reset to default, if not in sticky mode iTop = "auto"; break; } oColumnHeaders.style.top = iTop; return this; }; /** * Getter for the end point in time of the shown interval * @returns {Date|module:sap/ui/core/date/UI5Date} date instance with the end date * @since 1.87 * @public */ PlanningCalendar.prototype.getEndDate = function () { return this._dateNav.getEnd(this._getPrimaryCalendarType()); }; /** * Getter for how many intervals are currently displayed * @returns {number} The number of displayed intervals * @public */ PlanningCalendar.prototype.getVisibleIntervalsCount = function () { var sViewKey = this.getViewKey(); if ((sViewKey === CalendarIntervalType.OneMonth || sViewKey === "OneMonth") && this._iSize < 2) { var oFirstVisibleDate = CalendarUtils.getFirstDateOfWeek(this.getStartDate()), oFirstDateInLastWeek = CalendarUtils.getFirstDateOfWeek(this._dateNav.getEnd()), oLastVisibleDate = UI5Date.getInstance(oFirstDateInLastWeek.getTime()); oLastVisibleDate.setDate(oLastVisibleDate.getDate() + 6); return ((oLastVisibleDate.getTime() - oFirstVisibleDate.getTime()) / 86400000) + 1; } else { return this._getIntervals(this._getView(this.getViewKey())); } }; 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) { return; } if (this.isRelative()) { this._oTodayButton.setEnabled(false); return; } this._oTodayButton.setEnabled(!this._dateMatchesVisibleRange(UI5Date.getInstance(), 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 && sViewKey === "One Month") { return CalendarUtils._isSameMonthAndYear(CalendarDate.fromLocalJSDate(this.getStartDate()), CalendarDate.fromLocalJSDate(oDateTime)); } else if (oInterval && sViewKey === "Week") { var iIntervals = oInterval.getDays(), oDate = CalendarDate.fromLocalJSDate(oDateTime), oStartDate = CalendarDate.fromLocalJSDate(this.getStartDate()), oEndDate = CalendarDate.fromLocalJSDate(this.getStartDate()); oEndDate.setDate(oEndDate.getDate() + iIntervals); return oDate.isSameOrAfter(oStartDate) && oDate.isBefore(oEndDate); } 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"); if (this.getHeight().indexOf("%") > -1){ $Table.style.height = this.getHeight(); return; } // Table height is the PlanningCalendar height minus the height of the toolbars var sStyle = this.$().height() - this._oInfoToolbar.$().height() + "px"; $Table.style.height = sStyle; } this._adjustColumnHeadersTopOffset(); }; PlanningCalendar.prototype.onThemeChanged = function() { // adjust offset only if the control is rendered if (this.getDomRef()) { this._adjustColumnHeadersTopOffset(); } }; PlanningCalendar.prototype.addToolbarContent = function(oContent) { if (oContent && oContent.isA("sap.m.Title")) { this._observeHeaderTitleText(oContent); this._getHeader().setTitle(oContent.getText()); oContent.setVisible(false); } this.addAggregation("toolbarContent", oContent); return this; }; PlanningCalendar.prototype.insertToolbarContent = function(oContent, iIndex) { if (oContent && oContent.isA("sap.m.Title")) { this._observeHeaderTitleText(oContent); this._getHeader().setTitle(oContent.getText()); oContent.setVisible(false); } this.insertAggregation("toolbarContent", oContent, iIndex); return this; }; PlanningCalendar.prototype.removeToolbarContent = function(oContent) { var oRemoved; if (oContent && oContent.isA("sap.m.Title")) { this._getHeader().setTitle(""); this._disconnectAndDestroyHeaderObserver(); } else { oRemoved = this.removeAggregation("toolbarContent", oContent); } return oRemoved; }; PlanningCalendar.prototype.removeAllToolbarContent = function() { var aRemoved = this.removeAllAggregation("toolbarContent"); this._getHeader().setTitle(""); this._disconnectAndDestroyHeaderObserver(); return aRemoved; }; PlanningCalendar.prototype.destroyToolbarContent = function() { var destroyed = this.destroyAggregation("toolbarContent"); this._getHeader().setTitle(""); this._disconnectAndDestroyHeaderObserver(); return destroyed; }; /** * Returns the ManagedObjectObserver for the title. * * @return {sap.ui.base.ManagedObjectObserver} The header observer object * @private */ PlanningCalendar.prototype._getHeaderObserver = function () { if (!this._oHeaderObserver) { this._oHeaderObserver = new ManagedObjectObserver(this._handleTitleTextChange.bind(this)); } return this._oHeaderObserver; }; /** * Observes the text property of the title. * * @param {sap.m.Title} oTitle text property will be observed * @private */ PlanningCalendar.prototype._observeHeaderTitleText = function (oTitle) { this._getHeaderObserver().observe(oTitle, { properties: ["text"] }); }; PlanningCalendar.prototype._handleTitleTextChange = function (oChanges) { this._getHeader().setTitle(oChanges.current); }; /** * Disconnects and destroys the ManagedObjectObserver observing title's text. * * @private */ PlanningCalendar.prototype._disconnectAndDestroyHeaderObserver = function () { if (this._oHeaderObserver) { this._oHeaderObserver.disconnect(); this._oHeaderObserver.destroy(); this._oHeaderObserver = null; } }; /** * 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} oDate 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 {this} <code>this</code> to allow method chaining * @public */ PlanningCalendar.prototype.setStartDate = function(oDate) { var oStartDate; if (!oDate) { //set default value oStartDate = UI5Date.getInstance(); } else { CalendarUtils._checkJSDateObject(oDate); oStartDate = UI5Date.getInstance(oDate.getTime()); } oStartDate = this._shiftStartDate(oStartDate); 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()