Angular Scheduler Tutorial (TypeScript + PHP/MySQL)
source link: https://code.daypilot.org/67423/angular-scheduler-tutorial-typescript
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Features
Sample Angular project that shows how to use Angular Scheduler component from DayPilot Pro for JavaScript.
Angular 11 project available for download.
The Angular version of DayPilot Pro is available as a special npm module published at npm.daypilot.org.
Includes the required Scheduler configuration.
Loading resources and events using a HTTP service (REST interface).
Drag and drop event moving and resizing.
Includes a trial version of DayPilot Pro for JavaScript (see also License below)
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.
Node.js
You'll need npm package manager to resolve the dependencies (Angular, DayPilot). You can install npm as part of node.js.
Angular Scheduler Sample Project
You can download the sample project as a .zip file (see the download link at the top of the article). The download includes two separate projects:
angular-scheduler-php-frontend: Angular application that displays the Scheduler
angular-scheduler-php-backend: PHP application that provides a REST API to access a MySQL database
Running the PHP Project
Run the PHP project using the built-in web server at port 8090:
Linux
php -S 127.0.0.1:8090 -t /home/daypilot/tutorials/angular-scheduler-php-backend
Windows
php.exe -S 127.0.0.1:8090 -t C:\Users\daypilot\tutorials\angular-scheduler-php-backend
Running the Angular Project
To install the dependencies (node_modules
), run:
npm install
To run the Angular project, run:
ng serve --proxy-config proxy.conf.json
The Angular project uses a proxy to forward requests to /api/*
URL to the backend PHP project that is running at port 8090.
proxy-conf.json
{ "/api": { "target": "http://localhost:8090", "secure": false } }
Below are the steps required to create the project from scratch.
Scheduler Configurator
Instead of creating a new Angular project and configuring the component manually, you can also use the online Scheduler UI Builder. This configurator lets you customize the Scheduler properties and test the behavior using a live preview.
It can also generate a downloadable Angular project for you (including the selected configuration and all dependencies).
1. Angular CLI
Install Angular CLI as a global package:
npm install -g @angular/cli
2. New Angular Project
Create a new Angular project using Angular CLI:
ng new angular-scheduler-php-frontend
3. DayPilot Module
Install daypilot-pro-angular
module using the link from npm.daypilot.org:
npm install https://npm.daypilot.org/daypilot-pro-angular/trial/2020.2.4807.tar.gz --save
The updated package.json
file will look like this:
{ "name": "angular-scheduler-php", "license": "SEE LICENSE IN license/LicenseAgreementTrial.pdf", "version": "0.0.0", "scripts": { "ng": "ng", "start": "ng serve --proxy-config proxy.conf.json", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { "@angular/animations": "~11.0.0", "@angular/common": "~11.0.0", "@angular/compiler": "~11.0.0", "@angular/core": "~11.0.0", "@angular/forms": "~11.0.0", "@angular/platform-browser": "~11.0.0", "@angular/platform-browser-dynamic": "~11.0.0", "@angular/router": "~11.0.0", "daypilot-pro-angular": "https://npm.daypilot.org/daypilot-pro-angular/trial/2020.4.4807.tar.gz", "rxjs": "~6.6.0", "tslib": "^2.0.0", "zone.js": "~0.10.2" }, "devDependencies": { "@angular-devkit/build-angular": "~0.1100.0", "@angular/cli": "~11.0.0", "@angular/compiler-cli": "~11.0.0", "@types/jasmine": "~3.6.0", "@types/node": "^12.11.1", "codelyzer": "^6.0.0", "jasmine-core": "~3.6.0", "jasmine-spec-reporter": "~5.0.0", "karma": "~5.1.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "protractor": "~7.0.0", "ts-node": "~8.3.0", "tslint": "~6.1.0", "typescript": "~4.0.2" } }
Note that the DayPilot Angular module (daypilot-pro-angular) is not loaded from npmjs.org registry but it is served from npm.daypilot.org.
4. Scheduler Module
Create a new directory called scheduler
in src/app
directory.
In this new directory, create a scheduler.module.ts
file with SchedulerModule
class:
import {DataService} from './data.service'; import {FormsModule} from '@angular/forms'; import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {SchedulerComponent} from './scheduler.component'; import {DayPilotModule} from 'daypilot-pro-angular'; import {HttpClientModule} from '@angular/common/http'; @NgModule({ imports: [ BrowserModule, FormsModule, HttpClientModule, DayPilotModule ], declarations: [ SchedulerComponent ], exports: [ SchedulerComponent ], providers: [ DataService ] }) export class SchedulerModule { }
The SchedulerModule
will declare two classes necessary for our scheduler application:
scheduler.component.ts
(component class with Scheduler configuration)data.service.ts
(service class which provides HTTP services)
5. Scheduler Component
The scheduler.component.ts
file defines the main component that will display the Scheduler.
import {Component, ViewChild, AfterViewInit} 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 = { scale: "Day", startDate: DayPilot.Date.today().firstDayOfMonth(), days: DayPilot.Date.today().daysInMonth(), timeHeaders: [ {groupBy: "Month"}, {groupBy: "Day", format: "d"} ], cellWidthSpec: "Auto", resources: [], eventHeight: 30, treeEnabled: true, onBeforeEventRender: args => { if (args.data.text && args.data.text === "Vacation") { args.data.barColor = "#0F9D58"; args.data.barBackColor = "#0F9D58"; } }, onEventMove: args => { let data = { id: args.e.id(), newStart: args.newStart.toString(), newEnd: args.newEnd.toString(), newResource: args.newResource }; this.ds.moveEvent(data).subscribe(result => { this.scheduler.control.message("Updated"); }); }, onEventResize: args => { let data = { id: args.e.id(), newStart: args.newStart.toString(), newEnd: args.newEnd.toString(), newResource: args.e.resource() // existing resource id }; this.ds.moveEvent(data).subscribe(result => { this.scheduler.control.message("Updated"); }); }, onTimeRangeSelect: args => { DayPilot.Modal.prompt("Event name: ", "New event").then(modal => { if (modal.canceled) { return; } this.scheduler.control.clearSelection(); var e = { id: null, start: args.start.toString(), end: args.end.toString(), text: modal.result, resource: args.resource }; this.ds.createEvent(e).subscribe(result => { e.id = result.id; this.events.push(e); this.scheduler.control.message("Created"); }); }); } }; constructor(private ds: DataService) { } ngAfterViewInit(): void { this.ds.getResources().subscribe(result => this.config.resources = result); const from = this.scheduler.control.visibleStart(); const to = this.scheduler.control.visibleEnd(); this.ds.getEvents(from, to).subscribe(result => { this.events = result; }); } }
This component uses <daypilot-scheduler>
element to load the Scheduler:
the
events
attribute specifies the array that holds the Scheduler eventsthe
config
attribute specifies the object with Scheduler propertieswe also use
#scheduler
attribute to get the component reference
6. Angular Application
All Scheduler-related code is now in the src/app/scheduler
directory. Now we need to modify the main Angular application classes to display the Scheduler:
Add <scheduler-component>
tag to app.component.html
file:
<scheduler-component></scheduler-component>
Import SchedulerModule
in app.module.ts
file:
import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {AppComponent} from './app.component'; import {SchedulerModule} from "./scheduler/scheduler.module"; @NgModule({ declarations: [ AppComponent ], imports: [ BrowserModule, SchedulerModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
7. Application Source Code Structure
This is the final structure of the Angular project (src/app
directory):
angular-scheduler-php-frontend/src/app + scheduler - data.service.ts - scheduler.component.ts - scheduler.module.ts - app.component.css - app.component.html - app.component.spec.ts - app.component.ts - app.module.ts
8. How It Works: Scheduler Configuration
You can configure the Scheduler component by modifying the config
object of SchedulerComponent
class. Here is an example of basic Scheduler configuration:
import {Component, ViewChild, AfterViewInit} from '@angular/core'; import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular'; import {DataService} from './data.service'; {} @Component({ selector: 'scheduler-component', template: `<daypilot-scheduler [config]="config"></daypilot-scheduler>` }) export class SchedulerComponent { config: any = {}; }
You can use the config attribute to specify the Scheduler properties and event handlers. The properties will let you customize the Scheduler appearance and behavior.
You can use startDate and days properties to define the date range:
import {Component, ViewChild, AfterViewInit} from '@angular/core'; import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular'; import {DataService} from './data.service'; {} @Component({ selector: 'scheduler-component', template: `<daypilot-scheduler [config]="config"></daypilot-scheduler>` }) export class SchedulerComponent { config: DayPilot.SchedulerConfig = { scale: "Day", startDate: DayPilot.Date.today().firstDayOfMonth(), days: DayPilot.Date.today().daysInMonth(), timeHeaders: [ {groupBy: "Month"}, {groupBy: "Day", format: "d"} ] }; }
Use the scale property to set the duration of grid cells:
import {Component, ViewChild, AfterViewInit} from '@angular/core'; import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular'; import {DataService} from './data.service'; {} @Component({ selector: 'scheduler-component', template: `<daypilot-scheduler [config]="config"></daypilot-scheduler>` }) export class SchedulerComponent { config: DayPilot.SchedulerConfig = { startDate: DayPilot.Date.today().firstDayOfMonth(), days: DayPilot.Date.today().daysInMonth(), scale: "Day" }; }
Customize the time headers using timeHeaders property:
import {Component, ViewChild, AfterViewInit} from '@angular/core'; import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular'; import {DataService} from './data.service'; {} @Component({ selector: 'scheduler-component', template: `<daypilot-scheduler [config]="config"></daypilot-scheduler>` }) export class SchedulerComponent { config: DayPilot.SchedulerConfig = { startDate: DayPilot.Date.today().firstDayOfMonth(), days: DayPilot.Date.today().daysInMonth(), scale: "Day", timeHeaders: [ {groupBy: "Month"}, {groupBy: "Day", format: "d"} ] }; }
9. How It Works: Loading Row Data
The rows are defined using resources property of the Scheduler config
object:
import {Component, ViewChild, AfterViewInit} from '@angular/core'; import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular'; import {DataService} from './data.service'; {} @Component({ selector: 'scheduler-component', template: `<daypilot-scheduler [config]="config"></daypilot-scheduler>` }) export class SchedulerComponent { @ViewChild('scheduler') scheduler: DayPilotSchedulerComponent; config: DayPilot.SchedulerConfig = { scale: "Day", startDate: DayPilot.Date.today().firstDayOfMonth(), days: DayPilot.Date.today().daysInMonth(), timeHeaders: [ {groupBy: "Month"}, {groupBy: "Day", format: "d"} ], resources: [ {id:"group_1", name: "People", expanded: true, children: [ {id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}, {id: 3, name: "Person 3"} ]}, {id: "group_2", name: "Tools", expanded: true, children: [ {id: 4, name: "Tool 1"}, {id: 5, name: "Tool 2"}, {id: 6, name: "Tool 3"} ]} ] }; }
Because we want to load the row data from the database we need to replace the static definition with an HTTP call:
import {Component, ViewChild, AfterViewInit} from '@angular/core'; import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular'; import {DataService} from './data.service'; {} @Component({ selector: 'scheduler-component', template: `<daypilot-scheduler [config]="config"></daypilot-scheduler>` }) export class SchedulerComponent implements AfterViewInit { @ViewChild('scheduler') scheduler: DayPilotSchedulerComponent; config: DayPilot.SchedulerConfig = { scale: "Day", startDate: DayPilot.Date.today().firstDayOfMonth(), days: DayPilot.Date.today().daysInMonth(), timeHeaders: [ {groupBy: "Month"}, {groupBy: "Day", format: "d"} ], resources: [] }; constructor(private ds: DataService) { } ngAfterViewInit(): void { this.ds.getResources().subscribe(result => this.config.resources = result); } }
The DataService
class makes the HTTP call and returns an array with resource data:
import {Injectable} from '@angular/core'; import {DayPilot} from 'daypilot-pro-angular'; import {HttpClient} from '@angular/common/http'; import {Observable} from 'rxjs'; @Injectable() export class DataService { constructor(private http: HttpClient) { } getResources(): Observable<any[]> { return this.http.get("/api/backend_resources.php") as Observable<any>; } }
The PHP implementation of the backend_resources.php endpoint looks like this:
<?php require_once '_db.php'; $scheduler_groups = $db->query('SELECT * FROM groups ORDER BY name'); class Group {} class Resource {} $groups = array(); foreach($scheduler_groups as $group) { $g = new Group(); $g->id = "group_".$group['id']; $g->name = $group['name']; $g->expanded = true; $g->children = array(); $groups[] = $g; $stmt = $db->prepare('SELECT * FROM resources WHERE group_id = :group ORDER BY name'); $stmt->bindParam(':group', $group['id']); $stmt->execute(); $scheduler_resources = $stmt->fetchAll(); foreach($scheduler_resources as $resource) { $r = new Resource(); $r->id = $resource['id']; $r->name = $resource['name']; $g->children[] = $r; } } header('Content-Type: application/json'); echo json_encode($groups);
The structure of the JSON response corresponds to the structure required by DayPilot.Scheduler.resources array.
Example response:
[ {"id":"group_1","name":"People","expanded":true,"children":[ {"id":"1","name":"Person 1"}, {"id":"2","name":"Person 2"}, {"id":"3","name":"Person 3"} ]}, {"id":"group_2","name":"Tools","expanded":true,"children":[ {"id":"4","name":"Tool 1"}, {"id":"5","name":"Tool 2"}, {"id":"6","name":"Tool 3"} ]} ]
10. How It Works: Loading Event Data
The next step will be loading the Scheduler events. We will use the same approach that we used for loading resources:
Subscribe to
DataService.getEvents()
inngAfterViewInit()
and save them toevents
objectDataService.getEvents()
makes an HTTP call to the backend PHP project to get the event data
Note that the events are stored in a special events
object, which is referenced using [events]
attribute of <daypilot-scheduler>
.
import {Component, ViewChild, AfterViewInit} 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"></daypilot-scheduler>` }) export class SchedulerComponent implements AfterViewInit { events: any[] = []; config: DayPilot.SchedulerConfig = { scale: "Day", startDate: DayPilot.Date.today().firstDayOfMonth(), days: DayPilot.Date.today().daysInMonth(), timeHeaders: [ {groupBy: "Month"}, {groupBy: "Day", format: "d"} ], resources: [] }; constructor(private ds: DataService) { } ngAfterViewInit(): void { this.ds.getResources().subscribe(result => this.config.resources = result); const from = this.scheduler.control.visibleStart(); const to = this.scheduler.control.visibleEnd(); this.ds.getEvents(from, to).subscribe(result => { this.events = result; }); } }
data.service.ts
import {Injectable} from '@angular/core'; import {DayPilot} from 'daypilot-pro-angular'; import {HttpClient} from '@angular/common/http'; import {Observable} from 'rxjs'; @Injectable() export class DataService { constructor(private http: HttpClient) { } getEvents(from: DayPilot.Date, to: DayPilot.Date): Observable<any[]> { return this.http.get("/api/backend_events.php?from=" + from + "&to=" + to) as Observable<any>; } // ... }
The REST endpoint (backend_events.php
) is defined in the PHP project. It queries the database and returns the event data for the selected date range.
backend_events.php
<?php require_once '_db.php'; $stmt = $db->prepare('SELECT * FROM events WHERE NOT ((end <= :start) OR (start >= :end))'); $stmt->bindParam(':start', $_GET["from"]); $stmt->bindParam(':end', $_GET["to"]); $stmt->execute(); $result = $stmt->fetchAll(); class Event {} $events = array(); foreach($result as $row) { $e = new Event(); $e->id = $row['id']; $e->text = $row['name']; $e->start = $row['start']; $e->end = $row['end']; $e->resource = $row['resource_id']; $events[] = $e; } header('Content-Type: application/json'); echo json_encode($events);
Sample JSON response:
[{"id":"1","text":"Activity","start":"2018-06-04T00:00:00","end":"2018-06-07T00:00:00","resource":"1"}]
Change History
December 20, 2020: Upgraded to Angular 11, DayPilot Pro for JavaScript 2020.4.2807
June 3, 2020: Upgraded to Angular 9, DayPilot Pro for JavaScript 2020.2.4481
September 4, 2019: Upgraded to Angular 8, DayPilot Pro for JavaScript 2019.2.3871
June 5, 2018: Upgraded to Angular 6, DayPilot Pro for JavaScript 2018.2.3297, Angular CLI 6.0
September 19, 2016: PHP backend added.
September 17, 2016: Build 8.2.2384 includes complete TypeScript definitions for DayPilot.Scheduler and related classes (DayPilot.Date, DayPilot.Event, DayPilot.Bubble, DayPilot.Menu, DayPilot.Locale, DayPilot.Row).
September 15, 2016: Build 8.2.2381 includes TypeScript definitions of all event handlers. A generic EventHandler interface is used.
September 15, 2016: Initial release, based on DayPilot Pro for JavaScript 8.2.2380, Angular 2.0.0
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK