Features

  • Uses Angular Scheduler component from DayPilot Pro for JavaScript library (trial version).

  • One or more events can be selected, copied to a local "clipboard" using context menu and pasted at a specified location.

  • The “Cut” action allows moving events to new location using context menu.

  • Optional "auto copy" feature copies selected events to the clipboard automatically on click.

This tutorial doesn't cover Scheduler component setup in an Angular 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.

Copy & Paste for a Single Scheduler Event

angular scheduler copy event using context menu

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: DayPilot.SchedulerConfig = {

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

angular scheduler copy event to clipboard

In order to better visualize 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" #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: DayPilot.SchedulerConfig = {
    // ...
  };

  // ...
}

angular scheduler paste event using context menu

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: DayPilot.SchedulerConfig = {

  // ...

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

          if (!e) {
            return;
          }

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

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

Complete Scheduler Component Class

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" #scheduler></daypilot-scheduler>
  
  <div class="options" *ngIf="false">
    <label for="autocopy"><input id="autocopy" type="checkbox" [(ngModel)]="autoCopy"> Copy selected events 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!: DayPilotSchedulerComponent;

  clipboard: DayPilot.Event[] = [];

  autoCopy: boolean;

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

            if (!e) {
              return;
            }

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

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

  constructor(private ds: DataService, private cdr: ChangeDetectorRef) {}

  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.scheduler.control.update({events: result});
    });
  }

}

Copying Multiple Scheduler Events

angular scheduler selecting multiple events for copy paste

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

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

angular scheduler selecting multiple events for copy paste same row

Selecting an arbitrary collection of events would make calculating the target location when pasting more complicated so we will limit the selection to events from the same row for now. 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: DayPilot.SchedulerConfig = {
  // ..
  onEventSelect: args => {
    const selected = this.scheduler.control.multiselect.events();
    const 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();
    }
  },
  // ...
};

angular scheduler copy multiple events context menu

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: DayPilot.SchedulerConfig = {
    // ...
    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 => {
            const selected = this.scheduler.control.multiselect.events();
            this.clipboard = selected.sort((e1, e2) => e1.start().getTime() - e2.start().getTime());
          }
        }
      ]
    }),
    // ...
};

Pasting Multiple Events

angular scheduler paste multiple events context menu

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: DayPilot.SchedulerConfig = {
  // ...
  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;
          }

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

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

            const params = {
              start: start,
              end: end,
              resource: targetResource,
              text: e.text()
            };
            this.ds.createEvent(params).subscribe(result => this.scheduler.control.events.add(result));

          });
        }
      }
    ]
  })
};

Automatically Copy Selected Events to Clipboard

angular scheduler copy selected events to clipboard automatically

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, DayPilotSchedulerComponent} from "daypilot-pro-angular";
import {DataService} from "./data.service";

@Component({
  selector: 'scheduler-component',
  template: `
  <daypilot-scheduler [config]="config" #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!: DayPilotSchedulerComponent;

  clipboard: DayPilot.Event[] = [];

  autoCopy: boolean = false;

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

  // ...

}

Cut & Paste

In this last step, we will extend out copy and paste example with a “Cut” option.

angular scheduler cut and paste events context menu

First, we will add “Cut” item to the Scheduler event context menu:

contextMenu: new DayPilot.Menu({
  items: [
    {
      text: "Copy",
      onClick: args => {
        let selected = this.scheduler.control.multiselect.events();
        this.clipboard = selected.sort((e1, e2) => e1.start().getTime() - e2.start().getTime());
        this.cut = false;
      }
    },
    {
      text: "Cut",
      onClick: args => {
        let selected = this.scheduler.control.multiselect.events();
        this.clipboard = selected.sort((e1, e2) => e1.start().getTime() - e2.start().getTime());
        this.cut = true;
      }
    },
  ]
})

As you can see the implementation is the same. The only difference is that we set the cut flag to true.

export class SchedulerComponent implements AfterViewInit {

  // ...

  cut: boolean = false;
  
  // ...
  
}

This flag is implemented as a property of our SchedulerComponent class. The default value is set to false - but we need to set the value explicitly when “Copy” or “Cut” menu items are clicked.

The “Paste” item of the time range selection context menu will have to include more complex logic. It needs to check whether the cut flag is set and update the events instead of making a copy.

contextMenuSelection: new DayPilot.Menu({
  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.scheduler.control.clearSelection();

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

          if (this.cut) {
            this.ds.updateEvent(e, start, end, targetResource).subscribe(result => {
              this.scheduler.control.events.update(result);
            });
          } else {
            const params = {
              start: start,
              end: end,
              resource: targetResource,
              text: e.text()
            };

            this.ds.createEvent(params).subscribe(result => {
              this.scheduler.control.events.add(result);
            });
          }

        });
      }
    }
  ]
})

The updateEvent() method of DataService updates the event data object:

updateEvent(e: DayPilot.Event, start: DayPilot.Date, end: DayPilot.Date, resource: string): Observable<EventData> {

  e.data.start = start;
  e.data.end = end;
  e.data.resource = resource;

  // simulating an HTTP request
  return new Observable(observer => {
    setTimeout(() => {
      observer.next(e.data);
      observer.complete();
    }, 200);
  });
}

Depending on the desired behavior, you can also clear the clipboard items in the “Paste” action.