@progress/kendo-angular-scheduler
Version:
Kendo UI Scheduler Angular - Outlook or Google-style angular scheduler calendar. Full-featured and customizable embedded scheduling from the creator developers trust for professional UI components.
1,186 lines (1,152 loc) • 97.5 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Component, ContentChild, ContentChildren, EventEmitter, HostBinding, Input, isDevMode, NgZone, Output, QueryList, ViewChild, ViewContainerRef, ChangeDetectorRef, ChangeDetectionStrategy, ElementRef, Renderer2 } from '@angular/core';
import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n';
import { ResizeSensorComponent, isDocumentAvailable, shouldShowValidationUI, getLicenseMessage, anyChanged, isChanged, hasObservers, WatermarkOverlayComponent } from '@progress/kendo-angular-common';
import { getDate, ZonedDate, Day } from '@progress/kendo-date-math';
import { take } from 'rxjs/operators';
import { validatePackage } from '@progress/kendo-licensing';
import { packageMetadata } from './package-metadata';
import { DateChangeEvent, NavigateEvent, VIEW_EVENT_MAP } from './events';
import { ToolbarTemplateDirective } from './toolbar/toolbar-template.directive';
import { ToolbarService } from './toolbar/toolbar.service';
import { SchedulerView } from './types';
import { AgendaDateTemplateDirective } from './views/templates/agenda-date-template.directive';
import { AgendaTimeTemplateDirective } from './views/templates/agenda-time-template.directive';
import { AllDayEventTemplateDirective } from './views/templates/all-day-event-template.directive';
import { AllDaySlotTemplateDirective } from './views/templates/all-day-slot-template.directive';
import { DateHeaderTemplateDirective } from './views/templates/date-header-template.directive';
import { EventTemplateDirective } from './views/templates/event-template.directive';
import { GroupHeaderTemplateDirective } from './views/templates/group-header-template.directive';
import { MajorTimeHeaderTemplateDirective } from './views/templates/major-time-header-template.directive';
import { MinorTimeHeaderTemplateDirective } from './views/templates/minor-time-header-template.directive';
import { MonthDaySlotTemplateDirective } from './views/templates/month-day-slot-template.directive';
import { MultiWeekDaySlotTemplateDirective } from './views/templates/multi-week-day-slot-template.directive';
import { TimeSlotTemplateDirective } from './views/templates/time-slot-template.directive';
import { ViewContextService } from './views/view-context.service';
import { ViewStateService } from './views/view-state.service';
import { EditService } from './editing/edit.service';
import { EditDialogTemplateDirective } from './editing/edit-dialog-template.directive';
import { FormGroup, FormControl } from '@angular/forms';
import { LocalDataChangesService } from './editing/local-data-changes.service';
import { DialogsService } from './editing/dialogs.service';
import { SchedulerLocalizationService } from './localization/scheduler-localization.service';
import { defaultModelFields } from './common/default-model-fields';
import { readEvent, isRecurrenceMaster, copyResources, isPresent } from './common/util';
import { IntlService } from '@progress/kendo-angular-intl';
import { PDFService } from './pdf/pdf.service';
import { PDFExportEvent } from './pdf/pdf-export-event';
import { LoadingComponent } from './loading.component';
import { FocusService } from './navigation';
import { DomEventsService } from './views/common/dom-events.service';
import { closest } from './common/dom-queries';
import { alwaysFalse } from './views/utils';
import { SlotDragEvent } from './events/slot-drag-event';
import { SlotDragEndEvent } from './events/slot-drag-end-event';
import { EditDialogComponent } from './editing/edit-dialog.component';
import { NgTemplateOutlet, NgIf } from '@angular/common';
import { ToolbarComponent } from './toolbar/toolbar.component';
import { LocalizedMessagesDirective } from './localization/localized-messages.directive';
import * as i0 from "@angular/core";
import * as i1 from "./views/view-context.service";
import * as i2 from "./views/view-state.service";
import * as i3 from "./editing/edit.service";
import * as i4 from "./editing/dialogs.service";
import * as i5 from "@progress/kendo-angular-intl";
import * as i6 from "./pdf/pdf.service";
import * as i7 from "@progress/kendo-angular-l10n";
import * as i8 from "./views/common/dom-events.service";
import * as i9 from "./navigation";
const todayDate = () => getDate(new Date());
const DAYS_IN_WEEK = 7;
/**
* Represents the [Kendo UI Scheduler component for Angular](slug:overview_scheduler).
*
* @example
* ```html
* <kendo-scheduler [kendoSchedulerBinding]="events" [selectedDate]="selectedDate">
* <kendo-scheduler-day-view></kendo-scheduler-day-view>
* </kendo-scheduler>
* ```
*
* @remarks
* Supported children components are: {@link AgendaViewComponent},
* {@link WeekViewComponent}, {@link WorkWeekViewComponent}, {@link YearViewComponent},
* {@link SchedulerCustomMessagesComponent}, {@link TimelineMonthViewComponent},
* {@link TimelineViewComponent}, {@link TimelineWeekViewComponent}, {@link DayViewComponent},
* {@link MonthViewComponent}, {@link MultiDayViewComponent}, {@link MultiWeekViewComponent},
* {@link PDFComponent}, {@link TimeZoneEditorComponent}, {@link RecurrenceEditorComponent}
*/
export class SchedulerComponent {
wrapper;
viewContext;
viewState;
editService;
dialogsService;
intlService;
changeDetector;
zone;
pdfService;
localization;
domEvents;
renderer;
focusService;
hostClasses = true;
ariaRole = 'application';
rtl = false;
get dir() {
return this.direction;
}
/**
* Sets the index of the currently selected view. The default value is `0`, which shows the first declared view.
* @default 0
*/
set selectedViewIndex(index) {
this.viewIndex = index;
this.onViewIndexChange();
}
get selectedViewIndex() {
return this.viewIndex;
}
/**
* Specifies whether the Scheduler is editable. Accepts a `boolean` or `EditableSettings` object.
* @default false
*/
editable = false;
/**
* Specifies whether the Scheduler's day or time slots are selectable ([see example](slug:slotselection_scheduler#custom-callback)).
* When set to `true`, the Scheduler emits [`slotDragStart`]({% slug api_scheduler_schedulercomponent %}#toc-slotdragstart), [`slotDrag`]({% slug api_scheduler_schedulercomponent %}#toc-slotdrag), and [`slotDragEnd`]({% slug api_scheduler_schedulercomponent %}#toc-slotdragend) events on user interaction.
* @default false
*/
selectable = false;
/**
* Sets the minimum date that you can select using the Scheduler navigation.
*/
min;
/**
* Sets the maximum date that you can select using the Scheduler navigation.
*/
max;
/**
* Sets the height of events in the **Month** and **Timeline** views, and the height of **All day** events in the **Day** and **Week** views.
* @default 25
*/
eventHeight = 25;
/**
* Sets the column width for **Timeline** views.
* @default 100
*/
columnWidth = 100;
/**
* Sets whether the view shows business hours on initialization. By default, the view shows full-day hours. Applies to **Day**, **Week**, and **Timeline** views.
* @default false
*/
showWorkHours;
/**
* Sets the start time of the view. Accepts a string in `HH:mm` format. Applies to **Day**, **Week**, and **Timeline** views.
* @default '00:00'
*/
startTime = '00:00';
/**
* Sets the end time of the view. Accepts a string in `HH:mm` format. Applies to **Day**, **Week**, and **Timeline** views.
* @default '00:00'
*/
endTime = '00:00';
/**
* Sets the start time of the view when `showWorkHours` is `true`. Accepts a string in `HH:mm` format. Applies to **Day**, **Week**, and **Timeline** views.
* @default '08:00'
*/
workDayStart = '08:00';
/**
* Sets the end time of the view when `showWorkHours` is `true`. Accepts a string in `HH:mm` format. Applies to **Day**, **Week**, and **Timeline** views.
* @default '17:00'
*/
workDayEnd = '17:00';
/**
* Sets the start of the work week. Applies to **Day**, **Week**, and **Timeline** views.
* @default 1
*/
workWeekStart;
/**
* Sets the end of the work week. Applies to **Day**, **Week**, and **Timeline** views.
* @default 5
*/
workWeekEnd;
/**
* Sets the first day of the week. Applies to **Week**, **Month**, and **TimelineWeek** views. Defaults to the locale settings.
*/
set weekStart(value) {
this._weekStart = value;
}
get weekStart() {
if (isPresent(this._weekStart)) {
return this._weekStart;
}
return this.intlService.firstDay();
}
/**
* Sets the duration (in minutes) of the time slots. Applies to **Day**, **Week**, and **Timeline** views.
* @default 60
*/
slotDuration = 60;
/**
* Sets the number of divisions for the time slots. Applies to **Day**, **Week**, and **Timeline** views.
* @default 2
*/
slotDivisions = 2;
/**
* Sets the percentage of the slot filled by events. Accepts a value between 0 and 1. Applies to **Day** and **Week** views.
* @default 0.9
*/
slotFill = 0.9;
/**
* Toggles the visibility of the all-day slot. Applies to **Day**, **Multi-Day**, **Week**, and **Work-Week** views.
* @default true
*/
allDaySlot = true;
/**
* Sets the time to which the view initially scrolls. Accepts a string in `HH:mm` format or a JavaScript `Date` object. Applies to **Day**, **Week**, and **Timeline** views.
* @default '08:00'
*/
scrollTime = this.workDayStart;
/**
* Defines the Scheduler groups.
*/
group;
/**
* Specifies the resources of the Scheduler.
*/
resources;
/**
* Specifies whether the Scheduler displays a loading indicator.
* @default false
*/
loading;
/**
* Sets the timezone ID displayed in the Scheduler. For example, `Europe/Sofia`.
* @default 'Etc/UTC'
*/
set timezone(value) {
this._timezone = value;
this.events = this.events || [];
}
get timezone() {
return this._timezone;
}
/**
* The currently selected view.
*/
selectedView;
/**
* Sets the array of event instances shown by the Scheduler.
*/
set events(value) {
this._events = value;
this.processEvents(value);
}
get events() {
return this._events;
}
/**
* Sets the currently selected date of the Scheduler. Determines the displayed period.
*/
set selectedDate(value) {
if (!value) {
return;
}
this._selectedDate = value;
this.viewContext.notifySelectedDate(value);
}
get selectedDate() {
return this._selectedDate;
}
/**
* Sets the names of the model fields from which the Scheduler reads its data ([see example](slug:databinding_scheduler#binding-to-custom-object-schemas).
*/
set modelFields(value) {
this._modelFields = { ...defaultModelFields, ...value };
}
get modelFields() {
return this._modelFields;
}
/**
* Sets the Scheduler current time marker settings.
* @default true
*/
currentTimeMarker = true;
/**
* Defines the settings for highlighting ongoing events in the Scheduler.
* @default true
*/
highlightOngoingEvents = true;
/**
* Specifies whether to display the Scheduler toolbar.
* @default true
*/
showToolbar = true;
/**
* Specifies whether to display the Scheduler footer.
* @default true
*/
showFooter = true;
/**
* Defines a function that is executed for every slot in the view.
* The function returns a value supported by [`ngClass`](link:site.data.urls.angular['ngclassapi']).
*/
slotClass;
/**
* Defines a function that is executed for every event in the view.
* The function returns a value supported by [`ngClass`](link:site.data.urls.angular['ngclassapi']).
*/
eventClass;
/**
* Defines a function that is executed for every event in the view.
* The function returns a value supported by [`ngStyle`](link:site.data.urls.angular['ngstyleapi']).
*/
eventStyles;
/**
* A callback that is executed for each slot of the Scheduler view.
* If it returns `true`, the `k-selected` CSS class will be added to the cell, making it visibly selected.
* @default () => false
*/
isSlotSelected = alwaysFalse;
/**
* @hidden
*/
selectedViewIndexChange = new EventEmitter();
/**
* Emits when the Scheduler is about to perform a navigation action, such as changing the view, date, or focus.
*/
navigate = new EventEmitter();
/**
* Emits when the displayed date range in the Scheduler changes.
*/
dateChange = new EventEmitter();
/**
* Emits when a Scheduler view slot is clicked.
*/
slotClick = new EventEmitter();
/**
* Fires when a Scheduler view slot is double-clicked.
*/
slotDblClick = new EventEmitter();
/**
* Emits when the user creates a new event using the `'c'` key ([see example](slug:keyboard_navigation_scheduler)).
*/
create = new EventEmitter();
/**
* Fires when a Scheduler event is clicked.
*/
eventClick = new EventEmitter();
/**
* Fires when a Scheduler event is double-clicked.
*/
eventDblClick = new EventEmitter();
/**
* Emits when you press a key on a focused Scheduler event.
*/
eventKeydown = new EventEmitter();
/**
* Fires when the user cancels the editing by clicking the **Cancel** command button.
*/
cancel = new EventEmitter();
/**
* Fires when the user clicks the **Save** command button to save the changes of the edited event.
*/
save = new EventEmitter();
/**
* Fires when the user clicks the **Remove** icon of a Scheduler event.
*/
remove = new EventEmitter();
/**
* Fires when the user starts resizing a Scheduler event.
*/
resizeStart = new EventEmitter();
/**
* Emits while the user is resizing a Scheduler event.
*/
resize = new EventEmitter();
/**
* Fires when the user stops resizing a Scheduler event.
*/
resizeEnd = new EventEmitter();
/**
* Fires when the user starts dragging a Scheduler event.
*/
dragStart = new EventEmitter();
/**
* Fires while the user is dragging a Scheduler event.
*/
drag = new EventEmitter();
/**
* Fires when the user stops dragging a Scheduler event.
*/
dragEnd = new EventEmitter();
/**
* Fires when the user starts drag-selecting a Scheduler slot range.
*/
slotDragStart = new EventEmitter();
/**
* Fires while the user is drag-selecting a Scheduler slot range.
*/
slotDrag = new EventEmitter();
/**
* Fires when the user stops drag-selecting a Scheduler slot range.
*/
slotDragEnd = new EventEmitter();
/**
* Fires when the user clicks the **PDF export** command button.
*/
pdfExport = new EventEmitter();
/**
* Fires when the Scheduler is resized horizontally.
*/
schedulerResize = new EventEmitter();
/**
* @hidden
*/
dragEndConfirmed = new EventEmitter();
/**
* @hidden
*/
resizeEndConfirmed = new EventEmitter();
/**
* @hidden
*/
removeConfirmed = new EventEmitter();
/**
* @hidden
*/
editDialogTemplate;
/**
* @hidden
*/
toolbarTemplate;
/**
* @hidden
*/
dateRangeStream;
/**
* @hidden
*/
selectedDateStream;
/**
* @hidden
*/
views;
/**
* @hidden
*/
resizeSensor;
/**
* @hidden
*/
confirmationDialogContainerRef;
/**
* @hidden
*/
loadingComponent;
/**
* @hidden
*/
allDayEventTemplate;
/**
* @hidden
*/
eventTemplate;
/**
* @hidden
*/
timeSlotTemplate;
/**
* @hidden
*/
minorTimeHeaderTemplate;
/**
* @hidden
*/
majorTimeHeaderTemplate;
/**
* @hidden
*/
monthDaySlotTemplate;
/**
* @hidden
*/
multiWeekDaySlotTemplate;
/**
* @hidden
*/
dateHeaderTemplate;
/**
* @hidden
*/
allDaySlotTemplate;
/**
* @hidden
*/
groupHeaderTemplate;
/**
* @hidden
*/
agendaDateTemplate;
/**
* @hidden
*/
agendaTimeTemplate;
/**
* @hidden
*/
showLicenseWatermark = false;
/**
* @hidden
*/
licenseMessage;
/**
* @hidden
*/
get viewToolbar() {
return this.viewState?.toolbarVisibilityByView.get(this.selectedView);
}
direction;
subs;
viewIndex = 0;
_selectedDate;
_events;
_timezone = '';
_modelFields = defaultModelFields;
viewItems;
detachElementEventHandlers;
_weekStart;
constructor(wrapper, viewContext, viewState, editService, dialogsService, intlService, changeDetector, zone, pdfService, localization, domEvents, renderer, focusService) {
this.wrapper = wrapper;
this.viewContext = viewContext;
this.viewState = viewState;
this.editService = editService;
this.dialogsService = dialogsService;
this.intlService = intlService;
this.changeDetector = changeDetector;
this.zone = zone;
this.pdfService = pdfService;
this.localization = localization;
this.domEvents = domEvents;
this.renderer = renderer;
this.focusService = focusService;
const isValid = validatePackage(packageMetadata);
this.licenseMessage = getLicenseMessage(packageMetadata);
this.showLicenseWatermark = shouldShowValidationUI(isValid);
this.dateRangeStream = viewState.dateRange;
this.selectedDateStream = viewContext.selectedDate;
}
ngOnInit() {
if (!this.selectedDate) {
this.selectedDate = todayDate();
}
}
ngAfterContentInit() {
if (isDevMode() && this.views.length === 0) {
throw new Error('No views declared for <kendo-scheduler>. Please, declare at least one view.');
}
this.subs = this.views.changes.subscribe(() => this.resetViewIndex());
this.subs.add(this.intlService.changes.subscribe(this.intlChange.bind(this)));
this.subs.add(this.viewState.nextDate.subscribe(nextDate => {
this.selectedDate = nextDate;
}));
this.subs.add(this.viewState.dateRange.subscribe(dateRange => {
const isEmpty = dateRange.start.getTime() === 0;
if (!isEmpty) {
const args = new DateChangeEvent(this, this.selectedDate, dateRange);
this.dateChange.emit(args);
}
}));
this.subs.add(this.viewState.navigate.subscribe(({ viewName, date }) => {
const views = this.views.toArray();
const view = views.find(v => v.name === viewName);
const args = new NavigateEvent(this, {
type: 'show-date',
view: view || {
name: viewName,
title: viewName
},
date: date
});
this.navigate.next(args);
if (view && !args.isDefaultPrevented()) {
const index = views.indexOf(view);
this.selectedView = view;
this.setViewIndex(index);
this.selectedDate = date;
}
}));
this.subs.add(this.viewState.viewEvent.subscribe(({ name, args }) => {
const emitter = this[name];
const confirmedEmitter = this[`${name}Confirmed`];
if (hasObservers(emitter) || (confirmedEmitter && hasObservers(confirmedEmitter))) {
this.zone.run(() => {
const eventInstance = new VIEW_EVENT_MAP[name](this, args);
emitter.emit(eventInstance);
args.prevented = eventInstance.prevented;
if (confirmedEmitter && !args.prevented) {
confirmedEmitter.emit(eventInstance);
}
});
}
}));
this.subs.add(this.viewState.layoutEnd.subscribe(() => {
if (this.resizeSensor) {
this.resizeSensor.acceptSize();
}
}));
this.subs.add(this.viewState.slotSelectionStart
.subscribe((e) => {
if (hasObservers(this.slotDragStart)) {
this.zone.run(() => {
this.slotDragStart.emit(Object.assign(e, { sender: this }));
});
}
}));
this.subs.add(this.viewState.slotSelectionDrag
.subscribe((value) => {
if (hasObservers(this.slotDrag)) {
this.zone.run(() => {
this.slotDrag.emit(new SlotDragEvent(this, value));
});
}
}));
this.subs.add(this.viewState.slotSelectionEnd
.subscribe((value) => {
if (hasObservers(this.slotDragEnd)) {
this.zone.run(() => {
this.slotDragEnd.emit(new SlotDragEndEvent(this, value));
});
}
}));
this.onViewIndexChange();
this.notifyOptionsChange = this.notifyOptionsChange.bind(this);
this.subs.add(this.allDayEventTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.eventTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.timeSlotTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.timeSlotTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.minorTimeHeaderTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.majorTimeHeaderTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.monthDaySlotTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.multiWeekDaySlotTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.dateHeaderTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.allDaySlotTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.groupHeaderTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.agendaDateTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.agendaTimeTemplate.changes.subscribe(this.notifyOptionsChange));
this.subs.add(this.views.changes.subscribe(() => {
this.changeDetector.markForCheck();
}));
//this.editService.timezone = this.timezone;
this.attachEditHandlers();
this.dialogsService.container = this.confirmationDialogContainerRef;
this.notifyOptionsChange();
this.subs.add(this.pdfService.exportClick.subscribe(() => {
const args = new PDFExportEvent();
this.pdfExport.emit(args);
if (!args.isDefaultPrevented()) {
this.saveAsPDF();
}
}));
this.subs.add(this.pdfService.done.subscribe(() => {
this.loadingComponent.toggle(false);
}));
this.subs.add(this.localization.changes.subscribe(({ rtl }) => {
this.rtl = rtl;
this.direction = this.rtl ? 'rtl' : 'ltr';
}));
this.subs.add(this.viewState.optionsChange.subscribe(() => {
this.changeDetector.markForCheck();
}));
this.attachElementEventHandlers();
}
ngOnChanges(changes) {
if (isChanged('resources', changes) && !isChanged('events', changes) && this.viewItems) {
this.viewItems.forEach(item => {
copyResources(item.event, this.resources);
});
}
if (anyChanged([
'group', 'resources', 'min', 'max', 'showWorkHours', 'startTime', 'scrollTime', 'endTime', 'eventHeight',
'workDayStart', 'workDayEnd', 'workWeekStart', 'workWeekEnd', 'weekStart', 'slotDuration', 'slotDivisions',
'editable', 'timezone', 'slotClass', 'slotFill', 'columnWidth', 'eventClass', 'eventStyles', 'isSlotSelected',
'selectable', 'allDaySlot', 'showToolbar', 'showFooter'
], changes)) {
this.notifyOptionsChange(changes);
}
}
ngOnDestroy() {
if (this.subs) {
this.subs.unsubscribe();
}
if (this.detachElementEventHandlers) {
this.detachElementEventHandlers();
}
}
/**
* @hidden
*/
onResize(_event) {
this.viewContext.notifyResize();
}
/**
* @hidden
*/
onNavigationAction(action) {
const args = new NavigateEvent(this, action);
this.navigate.next(args);
if (args.isDefaultPrevented()) {
return true;
}
if (action.type === 'view-change') {
const views = this.views.toArray();
const index = views.indexOf(action.view);
this.selectedView = action.view;
this.setViewIndex(index);
}
else if (action.type === 'select-date') {
if (this.isInRange(action.date)) {
this.selectedDate = action.date;
}
}
else if (action.type === 'today') {
const date = new Date();
if (this.isInRange(date)) {
this.selectedDate = date;
}
}
else if (action.type === 'toggle-business-hours') {
this.viewState.toggleWorkHours.next(null);
}
else if (action.type === 'focus-toolbar') {
this.focusService.focusToolbar();
}
else {
this.viewContext.notifyAction(action);
}
}
/**
* @hidden
*/
onToolbarWidthChange(width) {
this.schedulerResize.emit(width);
}
/**
* Creates a popup editor for the new event.
*
* @param group - The [`FormGroup`](https://angular.io/docs/ts/latest/api/forms/index/FormGroup-class.html) that describes
* the edit form. If called with a data item, the parameter will build the `FormGroup` from the data item fields.
*/
addEvent(group) {
const isFormGroup = group instanceof FormGroup;
if (!isFormGroup) {
const createControl = (source) => (acc, key) => {
acc[key] = new FormControl(source[key]);
return acc;
};
const fields = Object.keys(group).reduce(createControl(group), {});
group = new FormGroup(fields);
}
this.editService.addEvent(group);
}
/**
* Switches the specified event in edit mode.
*
* @param dataItem - The event that will be switched to edit mode.
* @param options - An object which contains the form `group` that will be bound in the edit dialog and the current edit `mode`.
*
*/
editEvent(dataItem, options = {}) {
const { group, mode } = options;
this.editService.editEvent(dataItem, group, mode);
}
/**
* Closes the event editor, if open.
*/
closeEvent() {
this.editService.close();
}
/**
* Returns a flag which indicates if an event is currently edited.
*
* @return {boolean} - A flag which indicates if an event is currently edited.
*/
isEditing() {
return this.editService.isEditing();
}
/**
* Opens the built-in confirmation dialog for defining the edit mode
* that will be used when the user edits or removes a recurring event.
*
* @param operation - The type of operation that will be confirmed. Has to be either **Edit** or **Remove**.
*
* @returns the result from the confirmation dialog.
*/
openRecurringConfirmationDialog(operation) {
return this.dialogsService.openRecurringConfirmationDialog(operation);
}
/**
* Opens the built-in removal confirmation dialog.
*
* @return the result from the confirmation dialog.
*/
openRemoveConfirmationDialog() {
return this.dialogsService.openRemoveConfirmationDialog();
}
/**
* Saves the current view as PDF.
*/
saveAsPDF() {
this.loadingComponent.toggle(true);
this.zone.runOutsideAngular(() => {
// wait a tick in order for the loading element style to be updated by the browser.
// if the export is synchronous, the browser will not update the element before the export is finished.
setTimeout(() => {
this.pdfService.save();
}, 0);
});
}
/**
* Scrolls the view to the specified time.
*/
scrollToTime(time) {
this.viewContext.notifyAction({
type: 'scroll-time',
time: time
});
}
/**
* Returns the current view slot that matches the passed document position.
*
* @param x - The x document position.
* @param y - The y document position.
*
* @return {SchedulerSlot} - The slot.
*/
slotByPosition(x, y) {
return this.viewContext.executeMethod('slotByPosition', { x, y });
}
/**
* Returns the event associated with the specified DOM element, if any.
*
* @param element - The DOM element document position.
* @return the event instance, if found.
*/
eventFromElement(element) {
return this.viewContext.executeMethod('eventFromElement', { element });
}
/**
* Gets the currently active event, if any.
* The active event is the event that can currently receive focus.
*/
get activeEvent() {
const activeElement = this.focusService.activeElement;
if (activeElement && activeElement.nativeElement.matches('.k-event, [data-task-index]')) {
return this.eventFromElement(activeElement.nativeElement);
}
}
/**
* Focuses the next event or an event at a specified relative position.
* The `options` parameter can be used to set a positive or negative offset
* that is relative to the currently focused event ([see example](slug:keyboard_navigation_scheduler#toc-event-navigation-with-the-tab-key)).
* A `nowrap` flag toggles the wrapping to the first or to the last item.
*
* @param position The relative position of the event to focus.
* @returns `true` if the focused event changed. Otherwise, returns `false`.
*/
focusNext(position) {
const changed = this.focusService.focusNext(position);
this.zone.onStable.pipe(take(1)).subscribe(() => this.focusService.focus());
return changed;
}
/**
* Focuses the previous event or an event at a specified relative position.
* The `options` parameter can be used to set a positive or negative offset
* that is relative to the currently focused event ([see example](slug:keyboard_navigation_scheduler#toc-event-navigation-with-the-tab-key)).
* A `nowrap` flag toggles the wrapping to the first or to the last item.
*
* @param position The relative position of the event to focus.
* @returns `true` if the focused event changed. Otherwise, returns `false`.
*/
focusPrev(position) {
const prevPosition = { offset: -1, ...position };
const changed = this.focusService.focusNext(prevPosition);
this.zone.onStable.pipe(take(1)).subscribe(() => this.focusService.focus());
return changed;
}
/**
* Focuses the last focused scheduler element or the Scheduler element, if no events are available.
*/
focus() {
this.zone.onStable.pipe(take(1)).subscribe(() => this.focusService.focus());
}
/**
* @hidden
*/
get toolbarVisibilityState() {
return isPresent(this.viewToolbar) ? this.viewToolbar : this.showToolbar;
}
isInRange(date) {
return (!this.min || this.min <= date) && (!this.max || date <= this.max);
}
notifyOptionsChange(changes) {
const workweek = this.workWeek;
this.viewContext.notifyOptionsChange({
group: this.group,
resources: this.resources,
allDayEventTemplate: this.allDayEventTemplate ? this.allDayEventTemplate.first : null,
eventTemplate: this.eventTemplate ? this.eventTemplate.first : null,
timeSlotTemplate: this.timeSlotTemplate ? this.timeSlotTemplate.first : null,
minorTimeHeaderTemplate: this.minorTimeHeaderTemplate ? this.minorTimeHeaderTemplate.first : null,
majorTimeHeaderTemplate: this.majorTimeHeaderTemplate ? this.majorTimeHeaderTemplate.first : null,
monthDaySlotTemplate: this.monthDaySlotTemplate ? this.monthDaySlotTemplate.first : null,
multiWeekDaySlotTemplate: this.multiWeekDaySlotTemplate ? this.multiWeekDaySlotTemplate.first : null,
dateHeaderTemplate: this.dateHeaderTemplate ? this.dateHeaderTemplate.first : null,
allDaySlotTemplate: this.allDaySlotTemplate ? this.allDaySlotTemplate.first : null,
groupHeaderTemplate: this.groupHeaderTemplate ? this.groupHeaderTemplate.first : null,
agendaDateTemplate: this.agendaDateTemplate ? this.agendaDateTemplate.first : null,
agendaTimeTemplate: this.agendaTimeTemplate ? this.agendaTimeTemplate.first : null,
min: this.min,
max: this.max,
showWorkHours: this.showWorkHours,
startTime: this.startTime,
scrollTime: this.scrollTime,
endTime: this.endTime,
workDayStart: this.workDayStart,
workDayEnd: this.workDayEnd,
workWeekStart: workweek.start,
workWeekEnd: workweek.end,
weekStart: this.weekStart,
allDaySlot: this.allDaySlot,
slotDuration: this.slotDuration,
slotDivisions: this.slotDivisions,
eventHeight: this.eventHeight,
editable: this.editable,
selectable: this.selectable,
timezone: this.timezone,
currentTimeMarker: this.currentTimeMarker,
highlightOngoingEvents: this.highlightOngoingEvents,
showToolbar: this.showToolbar,
showFooter: this.showFooter,
slotClass: this.slotClass,
slotFill: this.slotFill,
columnWidth: this.columnWidth,
eventClass: this.eventClass,
eventStyles: this.eventStyles,
isSlotSelected: this.isSlotSelected,
changes: changes
});
}
get workWeek() {
const { start, end } = this.intlService.weekendRange();
const workWeekStart = isPresent(this.workWeekStart) ? this.workWeekStart : (end + 1) % DAYS_IN_WEEK;
const weekEnd = start > 0 ? start - 1 : Day.Saturday;
const workWeekEnd = isPresent(this.workWeekEnd) ? this.workWeekEnd : weekEnd;
return { start: workWeekStart, end: workWeekEnd };
}
resetViewIndex() {
const index = this.selectedViewIndex;
const newIndex = Math.max(0, Math.min(index, this.views.length - 1));
this.setViewIndex(newIndex);
this.onViewIndexChange();
}
onViewIndexChange() {
if (!this.views) {
return;
}
const views = this.views.toArray();
const selectedView = views[this.viewIndex];
if (selectedView) {
this.selectedView = selectedView;
}
}
setViewIndex(newIndex) {
const changed = this.selectedViewIndex !== newIndex;
if (changed) {
this.selectedViewIndex = newIndex;
this.selectedViewIndexChange.emit(newIndex);
}
return changed;
}
processEvents(dataItems) {
const timezone = this.timezone;
const fields = this.modelFields;
const items = dataItems
.map(dataItem => readEvent(dataItem, fields, this.resources))
.filter(event => !isRecurrenceMaster(event))
.map(event => {
const start = ZonedDate.fromLocalDate(event.start, timezone);
const end = ZonedDate.fromLocalDate(event.end, timezone);
return { start, end, event };
});
this.viewItems = items;
this.viewContext.notifyItems(items);
}
attachEditHandlers() {
if (!this.editService) {
return;
}
this.subs.add(this.editService.changes.subscribe(this.emitCRUDEvent.bind(this)));
}
emitCRUDEvent(args) {
Object.assign(args, { sender: this });
switch (args.action) {
case 'cancel':
this.cancel.emit(args);
break;
case 'save':
this.save.emit(args);
break;
default: break;
}
}
intlChange() {
const currentView = this.selectedView;
this.selectedView = null;
if (!isPresent(this.workWeekStart) || !isPresent(this.workWeekEnd)) {
this.notifyOptionsChange();
}
this.changeDetector.detectChanges();
this.selectedView = currentView;
if (NgZone.isInAngularZone()) {
this.changeDetector.markForCheck();
}
else {
this.changeDetector.detectChanges();
}
}
attachElementEventHandlers() {
if (!isDocumentAvailable()) {
return;
}
const wrapper = this.wrapper.nativeElement;
this.zone.runOutsideAngular(() => {
const windowBlurSubscription = this.renderer.listen('window', 'blur', (args) => {
this.domEvents.windowBlur.emit(args);
});
const clickSubscription = this.renderer.listen(wrapper, 'click', (args) => {
this.domEvents.click.emit(args);
});
const keydownSubscription = this.renderer.listen(wrapper, 'keydown', args => {
this.domEvents.keydown.emit(args);
});
let focused = false;
const focusInSubscription = this.renderer.listen(wrapper, 'focusin', (args) => {
this.domEvents.focus.emit(args);
if (!focused) {
this.domEvents.focusIn.emit(args);
this.renderer.addClass(this.wrapper.nativeElement, 'k-focus');
focused = true;
}
});
const focusOutSubscription = this.renderer.listen(wrapper, 'focusout', (args) => {
const next = args.relatedTarget || document.activeElement;
const outside = !closest(next, (node) => node === wrapper);
if (outside) {
this.domEvents.focusOut.emit(args);
this.renderer.removeClass(this.wrapper.nativeElement, 'k-focus');
focused = false;
}
});
this.detachElementEventHandlers = () => {
windowBlurSubscription();
clickSubscription();
keydownSubscription();
focusInSubscription();
focusOutSubscription();
};
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SchedulerComponent, deps: [{ token: i0.ElementRef }, { token: i1.ViewContextService }, { token: i2.ViewStateService }, { token: i3.EditService }, { token: i4.DialogsService }, { token: i5.IntlService }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i6.PDFService }, { token: i7.LocalizationService }, { token: i8.DomEventsService }, { token: i0.Renderer2 }, { token: i9.FocusService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SchedulerComponent, isStandalone: true, selector: "kendo-scheduler", inputs: { selectedViewIndex: "selectedViewIndex", editable: "editable", selectable: "selectable", min: "min", max: "max", eventHeight: "eventHeight", columnWidth: "columnWidth", showWorkHours: "showWorkHours", startTime: "startTime", endTime: "endTime", workDayStart: "workDayStart", workDayEnd: "workDayEnd", workWeekStart: "workWeekStart", workWeekEnd: "workWeekEnd", weekStart: "weekStart", slotDuration: "slotDuration", slotDivisions: "slotDivisions", slotFill: "slotFill", allDaySlot: "allDaySlot", scrollTime: "scrollTime", group: "group", resources: "resources", loading: "loading", timezone: "timezone", events: "events", selectedDate: "selectedDate", modelFields: "modelFields", currentTimeMarker: "currentTimeMarker", highlightOngoingEvents: "highlightOngoingEvents", showToolbar: "showToolbar", showFooter: "showFooter", slotClass: "slotClass", eventClass: "eventClass", eventStyles: "eventStyles", isSlotSelected: "isSlotSelected" }, outputs: { selectedViewIndexChange: "selectedViewIndexChange", navigate: "navigate", dateChange: "dateChange", slotClick: "slotClick", slotDblClick: "slotDblClick", create: "create", eventClick: "eventClick", eventDblClick: "eventDblClick", eventKeydown: "eventKeydown", cancel: "cancel", save: "save", remove: "remove", resizeStart: "resizeStart", resize: "resize", resizeEnd: "resizeEnd", dragStart: "dragStart", drag: "drag", dragEnd: "dragEnd", slotDragStart: "slotDragStart", slotDrag: "slotDrag", slotDragEnd: "slotDragEnd", pdfExport: "pdfExport", schedulerResize: "schedulerResize" }, host: { properties: { "class.k-scheduler": "this.hostClasses", "attr.role": "this.ariaRole", "class.k-rtl": "this.rtl", "attr.dir": "this.dir" } }, providers: [
EditService,
DialogsService,
DomEventsService,
LocalDataChangesService,
FocusService,
SchedulerLocalizationService,
{
provide: LocalizationService,
useExisting: SchedulerLocalizationService
},
{
provide: L10N_PREFIX,
useValue: 'kendo.scheduler'
},
ToolbarService,
ViewContextService,
ViewStateService,
PDFService
], queries: [{ propertyName: "editDialogTemplate", first: true, predicate: EditDialogTemplateDirective, descendants: true }, { propertyName: "toolbarTemplate", first: true, predicate: ToolbarTemplateDirective, descendants: true }, { propertyName: "views", predicate: SchedulerView }, { propertyName: "allDayEventTemplate", predicate: AllDayEventTemplateDirective }, { propertyName: "eventTemplate", predicate: EventTemplateDirective }, { propertyName: "timeSlotTemplate", predicate: TimeSlotTemplateDirective }, { propertyName: "minorTimeHeaderTemplate", predicate: MinorTimeHeaderTemplateDirective }, { propertyName: "majorTimeHeaderTemplate", predicate: MajorTimeHeaderTemplateDirective }, { propertyName: "monthDaySlotTemplate", predicate: MonthDaySlotTemplateDirective }, { propertyName: "multiWeekDaySlotTemplate", predicate: MultiWeekDaySlotTemplateDirective }, { propertyName: "dateHeaderTemplate", predicate: DateHeaderTemplateDirective }, { propertyName: "allDaySlotTemplate", predicate: AllDaySlotTemplateDirective }, { propertyName: "groupHeaderTemplate", predicate: GroupHeaderTemplateDirective }, { propertyName: "agendaDateTemplate", predicate: AgendaDateTemplateDirective }, { propertyName: "agendaTimeTemplate", predicate: AgendaTimeTemplateDirective }], viewQueries: [{ propertyName: "resizeSensor", first: true, predicate: ResizeSensorComponent, descendants: true, static: true }, { propertyName: "confirmationDialogContainerRef", first: true, predicate: ["confirmationDialogContainer"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "loadingComponent", first: true, predicate: LoadingComponent, descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: `
<ng-container kendoSchedulerLocalizedMessages
i18n-allEvents="kendo.scheduler.allEvents|The All events text displayed in the timeline views when there is no vertical grouping."
allEvents="All events"
i18n-allDay="kendo.scheduler.allDay|The all day text displayed in the day and week views."
allDay="all day"
i18n-dateHeader="kendo.scheduler.dateHeader|The date header text displayed in the agenda view."
dateHeader="Date"
i18n-timeHeader="kendo.scheduler.timeHeader|The time header text displayed in the agenda view."
timeHeader="Time"
i18n-deleteTitle="kendo.scheduler.deleteTitle|The delete icon title."
deleteTitle="Delete"
i18n-eventHeader="kendo.scheduler.eventHeader|The event header text displayed in the agenda view."
eventHeader="Event"
i18n-nextTitle="kendo.scheduler.nextTitle|The title of the navigation next button."
nextTitle="Next"
i18n-previousTitle="kendo.scheduler.previousTitle|The title of the navigation previous button."
previousTitle="Previous"
i18n-showFullDay="kendo.scheduler.showFullDay|The text of the show full day button displayed in the footer of the day, week and timeline views."
showFullDay="Show full day"
i18n-showWorkDay="kendo.scheduler.showWorkDay|The text of the show work day button displayed in the footer of the day, week and timeline views."
showWorkDay="Show business hours"
i18n-today="kendo.scheduler.today|The today button text displayed in the navigation."
today="Today"
i18n-calendarToday="kendo.scheduler.calendarToday|The text of today's date in the header of the Calendar."
calendarToday="TODAY"
i18n-dayViewTitle="kendo.scheduler.dayViewTitle|The day view title."
dayViewTitle="Day"
i18n-multiDayViewTitle="kendo.scheduler.multiDayViewTitle|The multi day view title."
multiDayViewTitle="Multi-Day"
i18n-weekViewTitle="kendo.scheduler.weekViewTitle|The week view title."
weekViewTitle="Week"
i18n-workWeekViewTitle="kendo.scheduler.workWeekViewTitle|The work week view title."
workWeekViewTitle="Work Week"
i18n-monthViewTitle="kendo.scheduler.monthViewTitle|The month view title."
monthViewTitle="Month"
i18n-multiWeekViewTitle="kendo.scheduler.multiWeekViewTitle|The multi week view title."
multiWeekViewTitle="Multi-Week"
i18n-timelineViewTitle="kendo.scheduler.timelineViewTitle|The timeline view title."
timelineViewTitle="Timeline"
i18n-timelineWeekViewTitle="kendo.scheduler.timelineWeekViewTitle|The timeline week view title."
timelineWeekViewTitle="Timeline Week"
i18n-timelineMonthViewTitle="kendo.scheduler.timelineMonthViewTitle|The timeline month view title."
timelineMonthViewTitle="Timeline Month"
i18n-agendaViewTitle="kendo.scheduler.agendaViewTitle|The agenda view title."
agendaViewTitle="Agenda"
i18n-yearViewTitle="kendo.scheduler.yearViewTitle|The year view title."
yearViewTitle="Year"
i18n-yearViewNoEvents="kendo.scheduler.yearViewNoEvents|The year view no events message."
yearViewNoEvents="No events on this date."
i18n-cancel="kendo.scheduler.cancel|The text similar to 'Cancel' displayed in the Scheduler."
cancel="Cancel"
i18n-save="kendo.scheduler.save|The text similar to 'Save' displayed in the Scheduler."
save="Save"
i18n-editorEventTitle="kendo.scheduler.editorEventTitle|The text similar to 'Title' displayed in the Scheduler event editor."
editorEventTitle='Title'
i18n-editorEventStart="kendo.scheduler.editorEventStart|The text similar to 'Start' displayed in the Scheduler event editor."
editorEventStart="Start"
i18n-editorEventStartTimeZone="kendo.scheduler.editorEventStartTimeZone|The text similar to 'Start Time Zone' displayed in the Scheduler event editor."
editorEventStartTimeZone="Start Time Zone"
i18n-editorEventEnd="kendo.scheduler.editorEventEnd|The text similar to 'End' displayed in the Scheduler event editor."
editorEventEnd="End"
i18n-editorEventEndTimeZone="kendo.scheduler.editorEventEndTimeZone|The text similar to 'End Time Zone' displayed in the Scheduler event editor."
editorEventEndTimeZone="End Time Zone"
i18n-editorEventAllDay="kendo.scheduler.editorEventAllDay|The text similar to 'All Day event' displayed in the Scheduler event editor."
editorEventAllDay="All Day Event"
i18n-editorEventDescription="kendo.scheduler.editorEventDescription|The text similar to 'Description' displayed in the Scheduler event editor."
editorEventDescription="Description"
i18n-editorEventSeparateTimeZones="kendo.scheduler.editorEventSeparateTimeZones|The text similar to 'Use separate Start and End Time Zones' displayed in the Scheduler event editor."