37

Angular Scheduler Tutorial (TypeScript + PHP/MySQL)

 3 years ago
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.
neoserver,ios ssh client

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

angular-6-scheduler-configurator.png

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

angular-scheduler-component-typescript-php-mysql-main.png

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 events

  • the configattribute specifies the object with Scheduler properties

  • we 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

angular-scheduler-component-typescript-php-mysql-configuration.png

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

angular-scheduler-component-typescript-php-mysql-resources.png

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

angular-scheduler-component-typescript-php-mysql-events.png

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() in ngAfterViewInit() and save them to events object

  • DataService.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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK