Overview

  • Define custom zoom levels for the Angular Scheduler component
  • Switching the zoom level using HTML5 range control and plus/minus buttons
  • Requires DayPilot Pro for JavaScript 2019.2.3860 or later
  • 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.

Live Demo

Scheduler Zoom Levels

Since version 2019.2.3860, the Scheduler includes support for zoom. You can define custom zoom levels - each level sets predefined/calculated Scheduler properties. Typically, each level will set scaletimeHeadersstartDate, and days properties.

The zoom levels need to be defined using zoomLevels property of the Scheduler config object:

config: any = {

  // ...

  zoomLevels: [
    {
      name: "Year",
      properties: {
        scale: "Day",
        cellWidth: 40,
        timeHeaders: [{groupBy: "Year"}, {groupBy: "Month", format: "MMMM"}, {groupBy: "Day", format: "d"}],
        startDate: function (args) { return args.date.firstDayOfYear(); },
        days: function (args) { return args.date.daysInYear(); },
      }
    },
    {
      name: "Month",
      properties: {
        scale: "CellDuration",
        cellDuration: 720,
        cellWidth: 40,
        timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "ddd d"}, {groupBy: "Cell", format: "tt"}],
        startDate: function(args) { return args.date.firstDayOfMonth(); },
        days: function(args) { return args.date.daysInMonth();},
      }
    },
    {
      name: "Week",
      properties: {
        scale: "Hour",
        cellWidth: 40,
        timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "dddd d"}, {groupBy: "Hour"}],
        startDate: function(args) { return args.date.firstDayOfWeek(); },
        days: function() { return 7; },
      }
    },
    {
      name: "Hour",
      properties: {
        scale: "CellDuration",
        cellDuration: 15,
        cellWidth: 40,
        timeHeaders: [{groupBy: "Day", format: "dddd MMMM d, yyyy"}, {groupBy: "Hour"}, {groupBy: "Cell"}],
        startDate: function(args) { return args.date.getDatePart(); },
        days: function() { return 1; },
      }
    },
  ]

};

It is an array of zoom level objects. The zoom level object has the following structure:

{
  customProperty1: "ignored",
  // ...
  properties: {
    property1: value,
    // ...
  }
}

The properties field specifies the Scheduler properties that will be applied when switching to this level. Other properties (e.g. customProperty1) at this level are ignored and you can use them to store custom data (here we use it to store the zoom level name).

The property names correspond to the Scheduler properties. If the property value is a function, the function will be executed and the result will be assigned to the specified Scheduler property. The function will receive a special args object as a parameter.

The args object has the following structure:

  • args.date - the reference date that is calculated from the current Scheduler viewport
  • args.level - the new zoom level object (the parent zoomLevel item)

Switching the Zoom Level

The zoom level can be switched using zoom property of the Scheduler config:

this.config.zoom = 1;

We are using two controls that will let users to change the zoom level.

  • range input control
  • plus/minus buttons

HTML5 Range Control

angular-scheduler-zoom-range.png

The range control simply uses config.zoom as the ngModel:

<input type="range" min="0" max="3" step="1" [(ngModel)]="config.zoom" />

Plus/Minus Buttons

angular-scheduler-zoom-plus-minus-buttons.png

The plus and minus buttons increase/decrease the config.zoom value:

HTML

<button id="minus" (click)="minus()">-</button>
<button id="plus" (click)="plus()">+</button>

TypeScript

plus(): void {
  this.config.zoom++;
}

minus(): void {
  this.config.zoom--;
}

However, such simple setup would result in an error if the config.zoom value is too low or high. We need to check the zoomLevels array boundaries:

plus(): void {
  // checking boundaries
  this.config.zoom = Math.min(this.config.zoom + 1, this.config.zoomLevels.length - 1);
}

minus(): void {
  // checking boundaries
  this.config.zoom = Math.max(this.config.zoom - 1, 0);
}

Full Source Code

import {Component, ViewChild, AfterViewInit, DoCheck} from '@angular/core';
import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular';
import {DataService} from './data.service'; {}

@Component({
  selector: 'scheduler-component',
  template: `
    <div class="controls">
      <button id="minus" (click)="minus()">-</button>
      <input type="range" min="0" max="3" step="1" [(ngModel)]="config.zoom" />
      <button id="plus" (click)="plus()">+</button>
      <span id="label">{{config.zoomLevels[config.zoom].name}}</span>
    </div>
    
    <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>`,
  styles: [`
    .controls {
      margin: 10px 0px;
      display: flex;
      align-items: center;
    }
    .controls input[type=range] {
      width: 80px;
      margin: 0px 5px;
    }
    .controls button {
      width: 25px;
      height: 25px;

      background-color: #3c78d8;
      color: white;
      border: 0;
      cursor: pointer;
    }
    .controls button:focus {
      outline: none;
    }
    #label {
      display: inline-block;
      margin-left: 10px;
    }
  
  `]
})
export class SchedulerComponent implements AfterViewInit, DoCheck {

  @ViewChild('scheduler')
  scheduler: DayPilotSchedulerComponent;

  events: any[] = [];

  config: any = {
    onTimeRangeSelected: function (args) {
      var dp = this;
      DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
        dp.clearSelection();
        if (!modal.result) { return; }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          resource: args.resource,
          text: modal.result
        }));
      });
    },
    treeEnabled: true,
    zoom: 1,
    zoomLevels: [
      {
        name: "Year",
        properties: {
          scale: "Day",
          cellWidth: 40,
          timeHeaders: [{groupBy: "Year"}, {groupBy: "Month", format: "MMMM"}, {groupBy: "Day", format: "d"}],
          startDate: function (args) { return args.date.firstDayOfYear(); },
          days: function (args) { return args.date.daysInYear(); },
        }
      },
      {
        name: "Month",
        properties: {
          scale: "CellDuration",
          cellDuration: 720,
          cellWidth: 40,
          timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "ddd d"}, {groupBy: "Cell", format: "tt"}],
          startDate: function(args) { return args.date.firstDayOfMonth(); },
          days: function(args) { return args.date.daysInMonth();},
        }
      },
      {
        name: "Week",
        properties: {
          scale: "Hour",
          cellWidth: 40,
          timeHeaders: [{groupBy: "Month"}, {groupBy: "Day", format: "dddd d"}, {groupBy: "Hour"}],
          startDate: function(args) { return args.date.firstDayOfWeek(); },
          days: function() { return 7; },
        }
      },
      {
        name: "Hour",
        properties: {
          scale: "CellDuration",
          cellDuration: 15,
          cellWidth: 40,
          timeHeaders: [{groupBy: "Day", format: "dddd MMMM d, yyyy"}, {groupBy: "Hour"}, {groupBy: "Cell"}],
          startDate: function(args) { return args.date.getDatePart(); },
          days: function() { return 1; },
        }
      },
    ]

  };

  constructor(private ds: DataService) {
  }

  plus(): void {
    // checking boundaries
    this.config.zoom = Math.min(this.config.zoom + 1, this.config.zoomLevels.length - 1);
  }

  minus(): void {
    // checking boundaries
    this.config.zoom = Math.max(this.config.zoom - 1, 0);
  }

  ngDoCheck(): void {
  }

  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;
    });
  }

}