Features
This tutorial shows how to manage resources (displayed as rows) using the built-in UI elements.
- Angular Scheduler component that displays a timeline for multiple resources
- Loading resources from the server-side backend
- Row header context menu
- Context menu hint (active area icon)
- Creating top-level resources (groups) using an inline edit box
- Creating child resources using a context menu and modal dialog
- Deleting resources using context menu
- Moving resources using drag and drop
- Includes a trial version of DayPilot Pro for JavaScript (see License below)
- Use Angular CLI 6.0
This tutorial focuses on managing resources (rows). For an introduction to the Scheduler component please see the basic tutorial:
License
Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.
Live Demo
You can test this project in a live demo:
Loading Resources
The Scheduler displays resources on the vertical axis. You can specify the resources using the "config" object ("resources" property). We will use a dummy DataService class to simulate a server-side backend. The resources are loaded using DataService in ngAfterViewInit() method:
this.ds.getResources().subscribe(result => this.config.resources = result);
By default the Scheduler displays resources in a flat list. We want to uses a hierarchy of resources - that needs to be enabled using "treeEnabled" property of the config:
config: any = { treeEnabled: true, };
Simplified version of SchedulerComponent that loads resources and events from the data source:
scheduler.component.ts
import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} from "@angular/core"; import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular"; import {DataService} from "./data.service"; import {EditComponent} from "./edit.component"; import {CreateComponent} from "./create.component"; @Component({ selector: 'scheduler-component', template: ` <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler> `, styles: [``] }) export class SchedulerComponent implements AfterViewInit { @ViewChild("scheduler") scheduler: DayPilotSchedulerComponent; events: any[] = []; config: any = { timeHeaders : [ {groupBy: "Month", format: "MMMM yyyy"}, {groupBy: "Day", format: "d"} ], eventHeight: 40, scale: "Day", days: 31, startDate: "2017-01-01", treeEnabled: true, // ... }; constructor(private ds: DataService, private cdr: ChangeDetectorRef) {} ngAfterViewInit(): void { this.ds.getResources().subscribe(result => this.config.resources = result); var from = this.scheduler.control.visibleStart(); var to = this.scheduler.control.visibleEnd(); this.ds.getEvents(from, to).subscribe(result => { this.events = result; }); } }
Row Header Context Menu
We want to let users access resource-related actions using a context menu. Our context menu will have three items:
- Edit
- Delete
- Add Child
The menu can be created using DayPilot.Menu object:
menu: DayPilot.Menu = new DayPilot.Menu({ items: [ {text: "Edit..." }, {text: "Delete" }, {text: "-"}, // separator {text: "Add Child..."} ] });
In later steps, we will add custom event handler to each of the items:
menu: DayPilot.Menu = new DayPilot.Menu({ items: [ {text: "Edit...", onClick: args => this.edit.show(args.source) }, {text: "Delete", onClick: args => { let row = args.source; this.ds.deleteResource(row).subscribe(result => { row.remove(); this.scheduler.control.message("Deleted"); }); } }, {text: "-"}, {text: "Add Child...", onClick: args => this.create.show(args.source) } ] });
At this moment we just need to assign the context menu to row headers (resources) using "contextMenuResource" property of the config. The context menu can be activated by right-clicking the row header.
config: any = { // ... contextMenuResource: this.menu };
This is how our SchedulerComponent looks like now:
scheduler.component.ts
import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} from "@angular/core"; import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular"; import {DataService} from "./data.service"; import {EditComponent} from "./edit.component"; import {CreateComponent} from "./create.component"; @Component({ selector: 'scheduler-component', template: ` <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler> `, styles: [``] }) export class SchedulerComponent implements AfterViewInit { @ViewChild("scheduler") scheduler: DayPilotSchedulerComponent; events: any[] = []; menu: DayPilot.Menu = new DayPilot.Menu({ items: [ {text: "Edit...", onClick: args => this.edit.show(args.source) }, {text: "Delete", onClick: args => { let row = args.source; this.ds.deleteResource(row).subscribe(result => { row.remove(); this.scheduler.control.message("Deleted"); }); } }, {text: "-"}, {text: "Add Child...", onClick: args => this.create.show(args.source) } ] }); config: any = { timeHeaders : [ {groupBy: "Month", format: "MMMM yyyy"}, {groupBy: "Day", format: "d"} ], eventHeight: 40, scale: "Day", days: 31, startDate: "2017-01-01", treeEnabled: true, // ... contextMenuResource: this.menu }; // ... }
Context Menu Hint Icon (Active Area)
In addition to right-click activation, we also add an explicit indicator that a context menu is available. We will add an icon in the upper-right corner of the row header that will appear on hover. The icon is created using a customizable object called an active area.
The active area uses one of the predefined icons ("icon-triangle-down") from the DayPilot icon font (see the "icons" directory).
config: any = { // ... onBeforeRowHeaderRender: args => { args.row.areas = [ { right: 2, top: 2, icon: "icon-triangle-down", style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px 2px 0px 2px; cursor:pointer", visibility: "Hover", action: "ContextMenu" } ]; } };
scheduler.component.ts
import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} from "@angular/core"; import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular"; import {DataService} from "./data.service"; import {EditComponent} from "./edit.component"; import {CreateComponent} from "./create.component"; @Component({ selector: 'scheduler-component', template: ` <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler> `, styles: [``] }) export class SchedulerComponent implements AfterViewInit { @ViewChild("scheduler") scheduler: DayPilotSchedulerComponent; events: any[] = []; menu: DayPilot.Menu = new DayPilot.Menu({ items: [ {text: "Edit...", onClick: args => this.edit.show(args.source) }, {text: "Delete", onClick: args => { let row = args.source; this.ds.deleteResource(row).subscribe(result => { row.remove(); this.scheduler.control.message("Deleted"); }); } }, {text: "-"}, {text: "Add Child...", onClick: args => this.create.show(args.source) } ] }); config: any = { timeHeaders : [ {groupBy: "Month", format: "MMMM yyyy"}, {groupBy: "Day", format: "d"} ], eventHeight: 40, scale: "Day", days: 31, startDate: "2017-01-01", treeEnabled: true, // ... contextMenuResource: this.menu, onBeforeRowHeaderRender: args => { args.row.areas = [ { right: 2, top: 2, icon: "icon-triangle-down", style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px 2px 0px 2px; cursor:pointer", visibility: "Hover", action: "ContextMenu" } ]; } }; // ... }
Creating Groups (Top-Level Resources)
The Scheduler has a built-in helper for creating new resources at the top level. You can activate it using "rowCreateHandling" property of the config:
config: any = { // ... rowCreateHandling: "Enabled", };
When in-line row creating is enabled, the Scheduler will display an extra row at the bottom. After clicking the row header the user can enter a name of the new row:
After a users hits <enter> the Scheduler fire onRowCreate event handler. We will use this event handler to notify the server-side backend.
scheduler.component.ts
import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} from "@angular/core"; import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular"; import {DataService} from "./data.service"; import {EditComponent} from "./edit.component"; import {CreateComponent} from "./create.component"; @Component({ selector: 'scheduler-component', template: ` <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler> `, styles: [``] }) export class SchedulerComponent implements AfterViewInit { @ViewChild("scheduler") scheduler: DayPilotSchedulerComponent; events: any[] = []; config: any = { // ... rowCreateHandling: "Enabled", rowCreateHeight: 25, rowCreateHtml: "New group...", onRowCreate: args => { // notify the backend; when successful, add the new resource to the config to display it this.ds.createResource(null, args.text).subscribe(result => { this.config.resources.push(result); this.scheduler.control.message("Created."); }); }, }; }
Creating Child Resources
One of the context menu items lets users add a new child resource.
menu: DayPilot.Menu = new DayPilot.Menu({ items: [ // ... {text: "Add Child...", onClick: args => this.create.show(args.source) } ] });
scheduler.component.ts
import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} from "@angular/core"; import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular"; import {DataService} from "./data.service"; import {EditComponent} from "./edit.component"; import {CreateComponent} from "./create.component"; @Component({ selector: 'scheduler-component', template: ` <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler> ... <create-dialog #create (close)="createClosed($event)"></create-dialog> `, styles: [``] }) export class SchedulerComponent implements AfterViewInit { @ViewChild("scheduler") scheduler: DayPilotSchedulerComponent; @ViewChild("create") create: CreateComponent; events: any[] = []; menu: DayPilot.Menu = new DayPilot.Menu({ items: [ {text: "Edit...", onClick: args => this.edit.show(args.source) }, {text: "Delete", onClick: args => { let row = args.source; this.ds.deleteResource(row).subscribe(result => { row.remove(); this.scheduler.control.message("Deleted"); }); } }, {text: "-"}, {text: "Add Child...", onClick: args => this.create.show(args.source) } ] }); config: any = { //... contextMenuResource: this.menu, onBeforeRowHeaderRender: args => { args.row.areas = [ { right: 2, top: 2, icon: "icon-triangle-down", style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px 2px 0px 2px; cursor:pointer", visibility: "Hover", action: "ContextMenu" } ]; } }; constructor(private ds: DataService, private cdr: ChangeDetectorRef) {} createClosed(args) { if (args.result) { this.scheduler.control.message("Created"); } } }
The "create" object holds a reference to a modal dialog with new resource details. We have created the modal dialog using DayPilot.Angular.Modal helper. See also a special tutorial that explains using Angular 2 modal dialogs with DayPilot Scheduler.
The onClick event handler of the "Add Child...." menu items opens the modal dialog using .show() method and passes the menu source (args.source) to it. The args.source objects holds a DayPilot.Row object with row details.
create.component.ts
import {Component, ViewChild, Output, EventEmitter} from '@angular/core'; import {DayPilot, DayPilotModalComponent} from "daypilot-pro-angular"; import {Validators, FormBuilder, FormGroup, FormControl} from "@angular/forms"; import {DataService, CreateEventParams, EventData, UpdateEventParams} from "./data.service"; @Component({ selector: 'create-dialog', template: ` <daypilot-modal #modal (close)="closed($event)"> <div class="center"> <h1>Create Resource</h1> <form [formGroup]="form"> <div class="form-item"> <input formControlName="name" type="text" placeholder="Resource Name"> <span *ngIf="!form.controls.name.valid">Resource name required</span> </div> <div class="form-item"> <button (click)="submit()" [disabled]="!form.valid">Save</button> <button (click)="cancel()">Cancel</button> </div> </form> </div> </daypilot-modal> `, styles: [` .center { max-width: 800px; margin-left: auto; margin-right: auto; } .form-item { margin: 4px 0px; } `] }) export class CreateComponent { @ViewChild("modal") modal : DayPilotModalComponent; @Output() close = new EventEmitter(); form: FormGroup; row: DayPilot.Row; constructor(private fb: FormBuilder, private ds: DataService) { this.form = this.fb.group({ name: ["", Validators.required], }); } show(row: DayPilot.Row) { this.row = row; this.form.setValue({ name: "", }); this.modal.show(); } submit() { let data = this.form.getRawValue(); this.ds.createResource(this.row, data.name).subscribe(resource => { // add the new resource to the tree let parent = this.row.data; parent.expanded = true; if (!parent.children) { parent.children = []; } parent.children.push(resource); this.modal.hide(resource); }); } cancel() { this.modal.hide(); } closed(args) { this.close.emit(args); } }
Deleting Resources
In our example, we use a context menu item to delete a resource:
menu: DayPilot.Menu = new DayPilot.Menu({ items: [ // ... {text: "Delete", onClick: args => { let row = args.source; this.ds.deleteResource(row).subscribe(result => { row.remove(); this.scheduler.control.message("Deleted"); }); } }, // ... ] });
The onClick event handler directly calls the DataService class. On success, it removes the row from the Scheduler using DayPilot.Row.remove() method.
Moving Resources using Drag and Drop
The Scheduler supports drag and drop row moving. This feature is disabled by default - you need to enable it using "rowMoveHandling" property of the config:
config: any = { // ... rowMoveHandling: "Update", // ... };
When you enable drag and drop row moving the row will display a move handle on hover. Users can drag the row by the handle and move it to a new location. The source row remains highlighted during dragging.
As soon as a user drops the row at the target location "onRowMoved" event handler is fired. The event args hold the operation details:
- args.source - DayPilot.Row object with source row
- args.target - the target DayPilot.Row object
- args.position - drop position relative to target ("child" | "before" | "after")
import {Component, ViewChild, AfterViewInit, ChangeDetectorRef} from "@angular/core"; import {DayPilot, DayPilotSchedulerComponent} from "daypilot-pro-angular"; import {DataService} from "./data.service"; import {EditComponent} from "./edit.component"; import {CreateComponent} from "./create.component"; @Component({ selector: 'scheduler-component', template: ` <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler> `, styles: [``] }) export class SchedulerComponent implements AfterViewInit { @ViewChild("scheduler") scheduler: DayPilotSchedulerComponent; events: any[] = []; config: any = { // ... rowMoveHandling: "Update", onRowMoved: args => { this.ds.moveResource(args.source, args.target, args.position).subscribe(result => this.scheduler.control.message("Moved")); }, // ... }; }