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)
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: DayPilot.SchedulerConfig = {
treeEnabled: true,
};
A 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";
@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: DayPilot.SchedulerConfig = {
timeHeaders : [
{groupBy: "Month", format: "MMMM yyyy"},
{groupBy: "Day", format: "d"}
],
eventHeight: 40,
scale: "Day",
days: 31,
startDate: "2021-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..."}
]
});
Next, we will add custom event handler to each of the items:
menu: DayPilot.Menu = new DayPilot.Menu({
items: [
{
text: "Edit...",
onClick: args => {
const form = [
{name: "Resource name", id: "name"}
];
const row = args.source;
const data = row.data;
DayPilot.Modal.form(form, data).then(modal => {
if (modal.canceled) {
return;
}
const name = modal.result.name;
this.ds.updateResource(row, name).subscribe(result => {
row.data.name = name;
});
});
}
},
{
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 => {
const form = [
{name: "Resource name", id: "name"}
];
const row = args.source;
const data = {};
DayPilot.Modal.form(form, data).then(modal => {
if (modal.canceled) {
return;
}
const name = modal.result.name;
this.ds.createResource(row, name).subscribe(resource => {
// add the new resource to the tree
let parent = row.data;
parent.expanded = true;
if (!parent.children) {
parent.children = [];
}
parent.children.push(resource);
});
});
}
}
]
});
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: DayPilot.SchedulerConfig = {
// ...
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";
@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 => {
const form = [
{name: "Resource name", id: "name"}
];
const row = args.source;
const data = row.data;
DayPilot.Modal.form(form, data).then(modal => {
if (modal.canceled) {
return;
}
const name = modal.result.name;
this.ds.updateResource(row, name).subscribe(result => {
row.data.name = name;
});
});
}
},
{
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 => {
const form = [
{name: "Resource name", id: "name"}
];
const row = args.source;
const data = {};
DayPilot.Modal.form(form, data).then(modal => {
if (modal.canceled) {
return;
}
const name = modal.result.name;
this.ds.createResource(row, name).subscribe(resource => {
// add the new resource to the tree
let parent = row.data;
parent.expanded = true;
if (!parent.children) {
parent.children = [];
}
parent.children.push(resource);
});
});
}
}
]
});
config: DayPilot.SchedulerConfig = {
timeHeaders : [
{groupBy: "Month", format: "MMMM yyyy"},
{groupBy: "Day", format: "d"}
],
eventHeight: 40,
scale: "Day",
days: 31,
startDate: "2021-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: DayPilot.SchedulerConfig = {
// ...
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";
@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({
// ...
});
config: DayPilot.SchedulerConfig = {
timeHeaders : [
{groupBy: "Month", format: "MMMM yyyy"},
{groupBy: "Day", format: "d"}
],
eventHeight: 40,
scale: "Day",
days: 31,
startDate: "2021-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: DayPilot.SchedulerConfig = {
// ...
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 user hits <enter>
the Scheduler fires 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";
@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: DayPilot.SchedulerConfig = {
// ...
rowCreateHandling: "Enabled",
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
The “Add Child…” context menu item lets users add a new child resource.
menu: DayPilot.Menu = new DayPilot.Menu({
items: [
// ...
{
text: "Add Child...",
onClick: args => {
const form = [
{name: "Resource name", id: "name"}
];
const row = args.source;
const data = {};
DayPilot.Modal.form(form, data).then(modal => {
if (modal.canceled) {
return;
}
const name = modal.result.name;
this.ds.createResource(row, name).subscribe(resource => {
// add the new resource to the tree
let parent = row.data;
parent.expanded = true;
if (!parent.children) {
parent.children = [];
}
parent.children.push(resource);
});
});
}
}
]
});
It uses DayPilot.Modal to display a modal dialog with the specified fields (resource name in this case).
The onClick
event handler of the "Add Child...." menu items opens the modal dialog using DayPilot.Modal.form()
method and passes the row object (available as args.source
) as a data parameter. The args.source
objects holds a DayPilot.Row
object with row details.
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 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: DayPilot.SchedulerConfig = {
// ...
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
object holds the operation details:
args.source
-DayPilot.Row
object with source rowargs.target
- the targetDayPilot.Row
objectargs.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";
@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: DayPilot.SchedulerConfig = {
// ...
rowMoveHandling: "Update",
onRowMoved: args => {
this.ds.moveResource(args.source, args.target, args.position).subscribe(result => this.scheduler.control.message("Moved"));
},
// ...
};
}