External Form

Apart from the built-in edit dialog, the Scheduler provides options for editing events in external forms.

Setup

The following example demonstrates how to use an external form to edit the displayed Scheduler events.

import { Component, OnInit } from '@angular/core';
import { EditMode, CrudOperation } from '@progress/kendo-angular-scheduler';
import { EditService } from './edit.service';
import { filter } from 'rxjs/operators';
import {
    CrudOperation,
    EditMode,
    EventClickEvent,
    RemoveEvent,
    SlotClickEvent
} from '@progress/kendo-angular-scheduler';

import '@progress/kendo-date-math/tz/regions/Europe';
import '@progress/kendo-date-math/tz/regions/NorthAmerica';

@Component({
    selector: 'my-app',
    template: `
        <kendo-scheduler
            [kendoSchedulerBinding]="editService.events | async"
            [modelFields]="editService.fields"
            [loading]="editService.loading"
            [editable]="true"
            [selectedDate]="selectedDate"
            (slotDblClick)="slotDblClickHandler($event)"
            (eventDblClick)="eventDblClickHandler($event)"
            (remove)="removeHandler($event)"
            style="height: 400px"
        >
            <kendo-scheduler-week-view startTime="07:00">
            </kendo-scheduler-week-view>
        </kendo-scheduler>

        <scheduler-edit-form
            [event]="editedEvent"
            [editMode]="editMode"
            [isNew]="isNew"
            (save)="saveHandler($event)"
            (cancel)="cancelHandler()"
            style="display: block; margin-top: 20px;">
        </scheduler-edit-form>
    `
})
export class AppComponent implements OnInit {
    public selectedDate: Date = new Date('2013-06-10T00:00:00');
    public editedEvent: any;
    public editMode: EditMode;
    public isNew: boolean;

    constructor(public editService: EditService) { }

    public ngOnInit(): void {
        this.editService.read();
    }

    public slotDblClickHandler({ start, end, isAllDay }: SlotClickEvent): void {
        this.isNew = true;

        this.editMode = EditMode.Series;

        this.editedEvent = {
            Start: start,
            End: end,
            IsAllDay: isAllDay
        };
    }

    public eventDblClickHandler({ sender, event }: EventClickEvent): void {
        this.isNew = false;

        let dataItem = event.dataItem;

        if (this.editService.isRecurring(dataItem)) {
            sender.openRecurringConfirmationDialog(CrudOperation.Edit)
                  // The dialog will emit `undefined` on cancel
                  .pipe(filter(editMode => editMode !== undefined))
                  .subscribe((editMode: EditMode) => {
                      if (editMode === EditMode.Series) {
                        dataItem = this.editService.findRecurrenceMaster(dataItem);
                      }

                      this.editMode = editMode;
                      this.editedEvent = dataItem;
                  });
        } else {
            this.editMode = EditMode.Series;
            this.editedEvent = dataItem;
        }
    }

    public saveHandler(formValue: any): void {
        if (this.isNew) {
            this.editService.create(formValue);
        } else {
            this.handleUpdate(this.editedEvent, formValue, this.editMode);
        }
    }

    public removeHandler({ sender, dataItem }: RemoveEvent): void {
        if (this.editService.isRecurring(dataItem)) {
            sender.openRecurringConfirmationDialog(CrudOperation.Remove)
                  // result will be undefined if the Dialog was closed
                  .pipe(filter(editMode => editMode !== undefined))
                  .subscribe((editMode) => {
                      this.handleRemove(dataItem, editMode);
                  });
        } else {
            sender.openRemoveConfirmationDialog().subscribe((shouldRemove) => {
                if (shouldRemove) {
                    this.editService.remove(dataItem);
                }
            });
        }
    }

    public cancelHandler(): void {
        this.editedEvent = undefined;
    }

    private handleUpdate(item: any, value: any, mode: EditMode): void {
        const service = this.editService;
        if (mode === EditMode.Occurrence) {
            if (service.isException(item)) {
                service.update(item, value);
            } else {
                service.createException(item, value);
            }
        } else {
            // Item is not recurring or we're editing the entire series
            service.update(item, value);
        }
    }

    private handleRemove(item: any, mode: EditMode): void {
        const service = this.editService;
        if (mode === EditMode.Series) {
            service.removeSeries(item);
        } else if (mode === EditMode.Occurrence) {
            if (service.isException(item)) {
                service.remove(item);
            } else {
                service.removeOccurrence(item);
            }
        } else {
            service.remove(item);
        }
    }
}
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Observable, zip } from 'rxjs';
import { map, tap } from 'rxjs/operators';

import { BaseEditService, SchedulerModelFields } from '@progress/kendo-angular-scheduler';
import { parseDate } from '@progress/kendo-angular-intl';

import { MyEvent } from './my-event.interface';

const CREATE_ACTION = 'create';
const UPDATE_ACTION = 'update';
const REMOVE_ACTION = 'destroy';

const fields: SchedulerModelFields = {
    id: 'TaskID',
    title: 'Title',
    description: 'Description',
    startTimezone: 'StartTimezone',
    start: 'Start',
    end: 'End',
    endTimezone: 'EndTimezone',
    isAllDay: 'IsAllDay',
    recurrenceRule: 'RecurrenceRule',
    recurrenceId: 'RecurrenceID',
    recurrenceExceptions: 'RecurrenceException'
};

@Injectable()
export class EditService extends BaseEditService<MyEvent> {
    public loading = false;

    constructor(private http: HttpClient) {
        super(fields);
    }

    public read(): void {
        if (this.data.length) {
            this.source.next(this.data);
            return;
        }

        this.fetch().subscribe(data => {
            this.data = data.map(item => this.readEvent(item));
            this.source.next(this.data);
        });
    }

    protected save(created: MyEvent[], updated: MyEvent[], deleted: MyEvent[]): void {
        const completed = [];
        if (deleted.length) {
            completed.push(this.fetch(REMOVE_ACTION, deleted));
        }

        if (updated.length) {
            completed.push(this.fetch(UPDATE_ACTION, updated));
        }

        if (created.length) {
            completed.push(this.fetch(CREATE_ACTION, created));
        }

        zip(...completed).subscribe(() => this.read());
    }

    protected fetch(action: string = '', data?: any): Observable<any[]> {
        this.loading = true;

        return this.http
            .jsonp(`https://demos.telerik.com/kendo-ui/service/tasks/${action}?${this.serializeModels(data)}`, 'callback')
            .pipe(
                map(res => <any[]>res),
                tap(() => this.loading = false)
            );
    }

    private readEvent(item: any): MyEvent {
        return {
            ...item,
            Start: parseDate(item.Start),
            End: parseDate(item.End),
            RecurrenceException: this.parseExceptions(item.RecurrenceException)
        };
    }

    private serializeModels(events: MyEvent[]): string {
        if (!events) {
            return '';
        }

        const data = events.map(event => ({
             ...event,
             RecurrenceException:
                this.serializeExceptions(event.RecurrenceException)
        }));

        return `&models=${JSON.stringify(data)}`;
    }
}

export interface MyEvent {
    TaskID?: number;
    OwnerID?: number;
    Title?: string;
    Description?: string;
    Start?: Date;
    End?: Date;
    StartTimezone?: string;
    EndTimezone?: string;
    IsAllDay?: boolean;
    RecurrenceException?: any;
    RecurrenceID?: number;
    RecurrenceRule?: string;
}
import { Component, Output, EventEmitter, Input, ViewEncapsulation } from '@angular/core';
import { Validators, FormControl, FormGroup, FormBuilder } from '@angular/forms';
import { EditMode } from '@progress/kendo-angular-scheduler';

@Component({
    selector: 'scheduler-edit-form',
    encapsulation: ViewEncapsulation.None,
    styles: [`
        #eventForm {
            max-width: 600px;
        }

        #eventForm .k-datetime-picker-wrapper .k-widget {
            display: inline-block;
            width: 150px;
            margin-right: 15px;
        }

        #eventForm .k-edit-label { width: 17%; }
        #eventForm .k-edit-field { width: 77%; }
    `],
    template: `
        <div *ngIf="active" class="card">
            <form novalidate [formGroup]="editForm" class="k-form-inline" id="eventForm">
                <fieldset>
                    <legend>{{ isNew ? 'Add New Event' : 'Edit Event' }}</legend>

                    <div class="k-form-field">
                        <span>Title</span>
                        <input class="k-textbox" placeholder="Title" formControlName="Title" />
                    </div>
                    <div class="k-form-field k-datetime-picker-wrapper">
                        <span>Start</span>
                        <kendo-datepicker formControlName="Start">
                        </kendo-datepicker>
                        <kendo-timepicker formControlName="Start" *ngIf='!editForm.controls.IsAllDay.value'>
                        </kendo-timepicker>
                    </div>
                    <div class="k-form-field k-datetime-picker-wrapper">
                        <span>End</span>
                        <kendo-datepicker formControlName="End">
                        </kendo-datepicker>
                        <kendo-timepicker formControlName="End" *ngIf='!editForm.controls.IsAllDay.value'>
                        </kendo-timepicker>
                    </div>
                    <div class="k-form-field">
                        <input type='checkbox' id='k-is-allday-chkbox' class='k-checkbox' formControlName='IsAllDay' />
                        <label class='k-checkbox-label' for='k-is-allday-chkbox'>All Day Event</label>
                    </div>
                    <div>
                        <kendo-recurrence-editor *ngIf="isEditingSeries" formControlName='RecurrenceRule'>
                        </kendo-recurrence-editor>
                    </div>
                </fieldset>
                <div class="text-right">
                    <button kendoButton (click)="onCancel($event)">Cancel</button>
                    <button kendoButton (click)="onSave($event)" [disabled]="!editForm.valid" [primary]="true">Save</button>
                </div>
            </form>
        </div>
    `
})
export class SchedulerEditFormComponent {
    @Input()
    public isNew = false;

    @Input()
    public editMode: EditMode;

    @Input()
    public set event(ev: any) {
        if (ev !== undefined) {
            this.editForm.reset(ev);
            this.active = true;
        }
    }

    @Output()
    public cancel: EventEmitter<any> = new EventEmitter();

    @Output()
    public save: EventEmitter<any> = new EventEmitter();

    public active = false;

    public editForm = new FormGroup({
        'Title': new FormControl('', Validators.required),
        'Start': new FormControl('', Validators.required),
        'End': new FormControl('', Validators.required),
        'IsAllDay': new FormControl(false),
        'RecurrenceRule': new FormControl(),
        'RecurrenceID': new FormControl()
    });

    public get isEditingSeries(): boolean {
        return this.editMode === EditMode.Series;
    }

    constructor(public formBuilder: FormBuilder) {}

    public onSave(e: MouseEvent): void {
        e.preventDefault();
        this.active = false;

        this.save.emit(this.editForm.value);
    }

    public onCancel(e: MouseEvent): void {
        e.preventDefault();
        this.active = false;

        this.cancel.emit();
    }
}
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms';
import { HttpClientJsonpModule, HttpClientModule } from '@angular/common/http';

import { SchedulerModule } from '@progress/kendo-angular-scheduler';
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { DateInputsModule } from '@progress/kendo-angular-dateinputs';

import { AppComponent } from './app.component';
import { SchedulerEditFormComponent } from './edit-form.component';
import { EditService } from './edit.service';

@NgModule({
  imports: [
      BrowserModule,
      BrowserAnimationsModule,
      ReactiveFormsModule,
      SchedulerModule,
      ButtonsModule,
      DateInputsModule,
      HttpClientModule,
      HttpClientJsonpModule
  ],
  declarations: [
    AppComponent,
    SchedulerEditFormComponent
  ],
  providers: [ EditService ],
  bootstrap: [ AppComponent ]
})

export class AppModule { }
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

In this article