Features

  • Uses Angular 2 Scheduler from DayPilot Pro for JavaScript library (trial version)
  • One or more events can be selected, copied to "clipboard" using context menu and pasted at a specified location.
  • Optional "auto copy" feature copies selected events to the clipboard automatically.
  • Uses Angular 2 CLI version 1.0.0-beta.25.5

This tutorial doesn't cover Scheduler setup in an Angular 2 application. For a step-by-step tutorial, please see the following article:

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:

Copy & Paste for Single Scheduler Event

angular2-scheduler-copy.png

We will implement the copy feature using event context menu. The context menu will have a single item ("Copy") which saves the selected event (available as args.source in the onClick event handler) to this.clipboard array.

config: any = {

  contextMenu: new DayPilot.Menu({
    items: [
      {
        text: "Copy",
        onClick: args => {
          this.clipboard = [ args.source ];
        }
      }
    ]
  }),
  
  // ...
  
};

angular2-scheduler-event-in-clipboard.png

In order to better visualise the workflow we will display the content of the clipboard array in a special section below the Scheduler:

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

@Component({
  selector: 'scheduler-component',
  template: `
  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>
  
  <div class="clipboard">
    <div class="clipboard-header">Clipboard</div>    
    <div *ngFor="let item of clipboard" class="clipboard-item">
    {{item.text()}}
    </div>
    <div *ngIf="clipboard.length === 0" class="clipboard-empty">
    No items.
    </div>
  </div>
`,
  styles: [`
  .options {
    margin-top: 10px;
  }
  .clipboard {
    margin-top: 20px;
    border: 1px solid #ccc;
    width: 200px;
  }
  .clipboard-header {
    background-color: #efefef;
    border-bottom: 1px solid #ccc;
    padding: 5px;
  }
  .clipboard-item {
    padding: 5px;
  }
  .clipboard-empty {
    padding: 5px;
  }
`]
})
export class SchedulerComponent implements AfterViewInit {

  // ...

  clipboard: DayPilot.Event[] = [];

  config: any = {
    // ...
  };

  // ...
}

angular2-scheduler-copy-paste-clipboard.png

The "Paste" action will also require a context menu. This time it's the context menu for time range selection. The time range context menu is activated automatically when you right click a grid cell. The onClick event handler copies the event properties from the clipboard and creates a new event at the target location using createEvent() method of the DataService class.

config: any = {

  // ...

  contextMenuSelection: new DayPilot.Menu({
    items: [
      {
        text: "Paste",
        onClick: args => {
          let e = this.clipboard[0];

          if (!e) {
            return;
          }

          let duration = e.duration();
          let start = args.source.start;
          let end = start.addTime(duration);
          let targetResource = args.source.resource;

          let params = {
            start: start,
            end: end,
            resource: targetResource,
            text: e.text()
          };
          this.ds.createEvent(params).subscribe(result => this.events.push(result));
        }
      }
    ]
  })
};

Complete Scheduler Component Class

scheduler.component.ts

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

@Component({
  selector: 'scheduler-component',
  template: `
  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>
  
  <div class="options" *ngIf="false">
    <label for="autocopy"><input id="autocopy" type="checkbox" [(ngModel)]="autoCopy"> Copy to clipboard automatically</label>  
  </div>
  
  <div class="clipboard">
    <div class="clipboard-header">Clipboard</div>    
    <div *ngFor="let item of clipboard" class="clipboard-item">
    {{item.text()}}
    </div>
    <div *ngIf="clipboard.length === 0" class="clipboard-empty">
    No items.
    </div>
  </div>
`,
  styles: [`
  .options {
    margin-top: 10px;
  }
  .clipboard {
    margin-top: 20px;
    border: 1px solid #ccc;
    width: 200px;
  }
  .clipboard-header {
    background-color: #efefef;
    border-bottom: 1px solid #ccc;
    padding: 5px;
  }
  .clipboard-item {
    padding: 5px;
  }
  .clipboard-empty {
    padding: 5px;
  }
`]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler: DayPilot.Angular.Scheduler;

  clipboard: DayPilot.Event[] = [];

  events: any[] = [];

  autoCopy: boolean;

  config: any = {
    timeHeaders : [
      {groupBy: "Month", format: "MMMM yyyy"},
      {groupBy: "Day", format: "d"}
    ],
    scale: "Day",
    days: 31,
    startDate: "2017-01-01",
    contextMenu: new DayPilot.Menu({
      items: [
        {
          text: "Copy",
          onClick: args => {
            this.clipboard = [ args.source ];
          }
        }
      ]
    }),
    contextMenuSelection: new DayPilot.Menu({
      items: [
        {
          text: "Paste",
          onClick: args => {
            let e = this.clipboard[0];

            if (!e) {
              return;
            }

            let duration = e.duration();
            let start = args.source.start;
            let end = start.addTime(duration);
            let targetResource = args.source.resource;

            let params = {
              start: start,
              end: end,
              resource: targetResource,
              text: e.text()
            };
            this.ds.createEvent(params).subscribe(result => this.events.push(result));
          }
        }
      ]
    })
  };

  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;

      // this is required for getEvents() that resolves immediately (no http)
      this.cdr.detectChanges();
    });
  }

}

Copying Multiple Scheduler Events

angular2-scheduler-selecting-multiple-events.png

As the first step, we will enable selecting multiple events using Ctrl+click:

config: any = {
  // ...
  allowMultiSelect: true,
  eventClickHandling: "Select",
  // ...
};

angular2-scheduler-event-selecting-same-row.png

Selecting an arbitrary collection of events would make calculating the target location when pasting very complicated so we will limit the selection to events from the same row. The onEventSelect event handler is fired after click but before the actual change to the selection is made. We can use args.preventDefault() to cancel the selection if it doesn't meet our rules:

config: any = {
  // ..
  onEventSelect: args => {
    let selected = this.scheduler.control.multiselect.events();
    let onlyThis = !args.selected && !args.ctrl && !args.meta;
    if (selected.length > 0 && selected[0].resource() !== args.e.resource() && !onlyThis) {
      this.scheduler.control.message("You can only select events from the same row.");
      args.preventDefault();
    }
  },
  // ...
};

angular2-scheduler-copy-multiple-events.png

Now we can modify the event context menu to allow copying multiple events. We will also sort the selected events by start date before saving them in the clipboard. Note that the selection is updated before displaying the context menu using onShow event - if you click an event that isn't selected it will clear the selection and select the current event.

config: any = {
    // ...
    contextMenu: new DayPilot.Menu({
      onShow: args => {
        if (!this.scheduler.control.multiselect.isSelected(args.source)) {
          this.scheduler.control.multiselect.clear();
          this.scheduler.control.multiselect.add(args.source);
        }
      },
      items: [
        {
          text: "Copy",
          onClick: args => {
            let selected = this.scheduler.control.multiselect.events();
            this.clipboard = selected.sort((e1, e2) => e1.start().getTime() - e2.start().getTime());
          }
        }
      ]
    }),
    // ...
};

Pasting Multiple Events

angular2-scheduler-pasting-multiple-events.png

Our modified time range selection menu creates new events at the specified offset (depending on the distance from the first event) at the target location. In this example, the backend event creating method is called for every event separately but you may want to replace it with a batch method that creates all events in a single transaction.

config: any = {
  // ...
  contextMenuSelection: new DayPilot.Menu({
    onShow: args => {
      let noItemsInClipboard = this.clipboard.length === 0;
      args.menu.items[0].disabled = noItemsInClipboard;
    },
    items: [
      {
        text: "Paste",
        onClick: args => {
          if (this.clipboard.length === 0) {
            return;
          }

          let targetStart = args.source.start;
          let targetResource = args.source.resource;
          let firstStart = this.clipboard[0].start();

          this.clipboard.forEach(e => {
            let offset = new DayPilot.Duration(firstStart, e.start());
            let duration = e.duration();
            let start = targetStart.addTime(offset);
            let end = start.addTime(duration);

            let params = {
              start: start,
              end: end,
              resource: targetResource,
              text: e.text()
            };
            this.ds.createEvent(params).subscribe(result => this.events.push(result));

          });
        }
      }
    ]
  })
};

AutoCopy

angular2-scheduler-auto-copy.png

It may be more convenient to copy the selected events to the clipboard automatically, immediately after selection. We will add a checkbox for this option and implement the "auto copy" feature using onEventSelected event handler of the Scheduler. Unlike onEventSelect, this event handler is called after the selection is updated so we can get the updated list of selected events there.

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

@Component({
  selector: 'scheduler-component',
  template: `
  <daypilot-scheduler [config]="config" [events]="events" #scheduler></daypilot-scheduler>
  
  <div class="options" *ngIf="false">
    <label for="autocopy"><input id="autocopy" type="checkbox" [(ngModel)]="autoCopy"> Copy to clipboard automatically</label>  
  </div>
  
  ...  

`,
  styles: [`...`]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler: DayPilot.Angular.Scheduler;

  clipboard: DayPilot.Event[] = [];

  events: any[] = [];

  autoCopy: boolean;

  config: any = {
    // ...
    onEventSelected: args => {
      if (this.autoCopy) {
        this.clipboard = this.scheduler.control.multiselect.events();
      }
    },
    // ...
  };

  // ...

}