Overview

  • The tasks queue Angular component lets you display a list of unscheduled tasks.

  • You can change the task priority using drag and drop (you can move the task to a different position in the queue).

  • You can schedule the task by dragging it to the desired position in the Angular Scheduler component.

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.

How to create a queue of unscheduled tasks in Angular?

angular scheduler drag and drop task queue component

Since version 2021.2.4972, DayPilot Pro for JavaScript includes a new Angular Queue component that can display an ordered list of tasks. It lets you implement a queue of unscheduled tasks that can be scheduled for a specific time using drag and drop.

  • It uses the same styling as the Angular Scheduler component so the appearance will be unified.

  • The queue component automatically activates drag and drop for the items. You can drag items from the queue to the Scheduler component and schedule it for the desired time.

  • The tasks queue is ordered - it respects the order of items in the data source.

  • Users can change the order of the tasks by dragging to a different position in the queue.

When you drag the queue item outside of the queue element, external drag and drop is activated:

  • During dragging, the source task in the queue is displayed as semi-transparent. This way users will see which task is being dragged.

  • If you drag the item outside of the queue it will show a global shadow indicating that dragging is active.

  • As soon as you reach the Scheduler grid the shadow will highlight the target position. The duration specified in the task properties will be used to calculate the exact target position.

The queue items have the following required fields:

  • id (string or number; the id must be unique)

  • text (string; this is the text that will be displayed in the task queue item)

  • duration (number or DayPilot.Duration object; this value specifies the duration that will be used when converting the task to a Scheduler event during drag and drop)

  • start and end fields (string or DayPilot.Date object) can be used instead of duration - this makes the interoperability with the Scheduler component easier

The queue task data format is compatible with the Scheduler event format. The id and text properties are used the same way in the Scheduler.

Instead of using the Queue component, you can also use the Scheduler API to create your own queue.

However, the Queue component will do a lot of work for you:

  • The queue styles the items the same way as the Scheduler

  • The queue supports item reordering

  • The queue API is compatible with the Scheduler (e.g. DayPilot.Queue.events.add() is used to add a new task to the queue, just like DayPilot.Scheduler.events.add() is used to add a new event to the Angular Scheduler component).

HTML template:

<button (click)="addTask()">Add</button>
<daypilot-queue [config]="queueConfig" #queue></daypilot-queue>

TypeScript

export class SchedulerComponent implements AfterViewInit {

  @ViewChild('queue')
  queue!: DayPilotQueueComponent;

  queueConfig: DayPilot.QueueConfig = {
    emptyText: "No tasks",
  };

  async addTask(): Promise<void> {

    const durations = [
      {name: "1 hour", id: "60"},
      {name: "2 hours", id: "120"},
      {name: "3 hours", id: "180"},
      {name: "4 hours", id: "240"},
    ];

    const form = [
      {name: "Name", id: "text"},
      {name: "Duration", id: "minutes", type: "select", options: durations}
    ];
    const data= {
      text: "Task 1",
      minutes: 60
    };
    const modal = await DayPilot.Modal.form(form, data);

    if (modal.canceled) {
      return;
    }

    const e = modal.result;
    e.id = DayPilot.guid();
    e.duration = DayPilot.Duration.ofMinutes(e.minutes);

    this.queue.control.events.add(e);
  }

}

The queue items are loaded in the ngAfterViewInit() handler:

ngAfterViewInit(): void {

  // ...

  this.ds.getQueue().subscribe(events => {
    this.queue.control.update({events});
  });

}

This example uses a simplified getQueue() implementation that returns a static array with queue items (DataService class):

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 {

  // ...

  hourAsSeconds = 60 * 60;

  queue: any[] = [
    {
      id: '4',
      duration: 4 * this.hourAsSeconds,
      text: 'Queue Event 1',
      type: 'Standard',
    },
    {
      id: '5',
      start: "2024-01-01T00:00:00",
      end: "2024-01-01T04:00:00",
      text: 'Queue Event 2',
      type: 'Important',
    },
  ];

  constructor(private http: HttpClient) {
  }

  getQueue(): Observable<any[]> {

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

      // return this.http.get("/api/queue");
  }


}

You can see that the tasks use a different duration specification:

  • The “Queue Event 1” item uses a duration property that specifies the duration in seconds.

  • The “Queue Event 2” item uses a combination of start and end properties. The duration will be calculated automatically as a difference of these two date/time values.

How to drag tasks from the Queue to the Scheduler?

angular scheduler drag tasks from the queue

The queue items are automatically marked as draggable. No extra steps are necessary if you want to drag them to the Scheduler.

The Scheduler will accept external items without any additional configuration changes.

The only thing you need to do is to detect the external source when the event is dropped at the target position in the Scheduler - and remove the item from the queue.

  • The Scheduler fires onEventMoved event handler when you drop the event. You need to check args.external value to detect is the item comes from the queue.

  • Then you need to remove the item from the queue using events.remove() method.

See the following example:

export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler')
  scheduler!: DayPilotSchedulerComponent;

  @ViewChild('queue')
  queue!: DayPilotQueueComponent;

  schedulerConfig: DayPilot.SchedulerConfig = {
    
    onEventMove: (args) => {
      if (args.external) {
        args.control.message("Moved from queue");
        this.queue.control.events.remove(args.e.data.id);
      }
    },
    
    // ...
    
  };

  constructor(private ds: DataService) {
  }

}

How to drag tasks from the Scheduler to the Queue?

angular scheduler drag tasks from the scheduler

By default, the Angular Scheduler doesn’t allow dragging events outside of the grid. You need to enable this behavior using dragOutAllowed property. No special configuration is required in the Queue - it will always accept the external item.

When the event is dropped in the queue, the Queue component will fire onEventMoved event handler. You can use the same logic that we used for the opposite direction - detect an external source and remove the source item from the Angular Scheduler.

export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler')
  scheduler!: DayPilotSchedulerComponent;

  @ViewChild('queue')
  queue!: DayPilotQueueComponent;

  queueConfig: DayPilot.QueueConfig = {
    emptyText: "No tasks",
    onEventMove: (args) => {
      if (args.external) {
        this.scheduler.control.events.remove(args.e.data.id);
      }
    },
  };

  schedulerConfig: DayPilot.SchedulerConfig = {
    dragOutAllowed: true,
    // ...
  };

  constructor(private ds: DataService) {
  }

}

How to change the task order in the Queue?

angular scheduler change task order in the queue

The queue supports reordering items. Simply start dragging the item - the target position will be highlighted using a thin horizontal line.

The target position is stored in args.position in onEventMoved event handler.

  • You can use it to update your backend using an HTTP call.

  • The queue handles the update automatically, you don’t need to explicitly change the task order in the data source.

export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler')
  scheduler!: DayPilotSchedulerComponent;

  @ViewChild('queue')
  queue!: DayPilotQueueComponent;

  queueConfig: DayPilot.QueueConfig = {
    emptyText: "No tasks",
    onEventMove: (args) => {
      if (args.external) {
        // ...
      }
      else {
        console.log("target position", args.position);
      }
    },
  };
}

Full Source Code

Here is the full TypeScript source code of our Angular Scheduler component (scheduler.component.ts) that integrates the queue of unscheduled tasks and the main grid with planned events into a single view:

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

@Component({
  selector: 'scheduler-component',
  template: `
    <div class="parent">
      <div class="left">
        <button (click)="addTask()">Add</button>
        <daypilot-queue [config]="queueConfig" #queue></daypilot-queue>
      </div>
      <div class="right">
        <daypilot-scheduler [config]="schedulerConfig" #scheduler></daypilot-scheduler>
      </div>
    </div>
  `,
  styles: [`
    .parent {
      display: flex;
    }
    .left {
      margin-right: 10px;
      width: 200px;
    }
    .right {
      flex-grow: 1;
    }
    button {
      background-color: #f3f3f3;
      border: 1px solid #c0c0c0;
      color: #333;
      padding: 8px 0px;
      width: 100%;
      border-radius: 2px;
      cursor: pointer;
      margin-right: 5px;
      margin-bottom: 5px;
    }
  `]
})
export class SchedulerComponent implements AfterViewInit {

  @ViewChild('scheduler')
  scheduler!: DayPilotSchedulerComponent;

  @ViewChild('queue')
  queue!: DayPilotQueueComponent;

  queueConfig: DayPilot.QueueConfig = {
    emptyText: "No tasks",
    eventBarVisible: false,
    eventHeight: 40,
    contextMenu: new DayPilot.Menu({
      items: [
        {
          text: "Edit...",
          onClick: async (args) => {
            const form = [
              {name: "Text", id: "text"},
              {name: "Type", id: "type", type: "select", options: [
                {name: "Standard", id: "Standard"},
                {name: "Important", id: "Important"},
              ]},
            ];

            const data = args.source.data;
            const modal = await DayPilot.Modal.form(form, data);
            if (modal.canceled) {
              return;
            }
            this.queue.control.events.update(modal.result);

          }
        },
        {
          text: "Delete",
          onClick: (args) => {
            this.queue.control.events.remove(args.source);
          }
        }
      ]
    }),
    onEventMove: (args) => {
      if (args.external) {
        this.scheduler.control.events.remove(args.e.data.id);
      }
      else {
        console.log("target position", args.position);
      }
    },
    onBeforeEventRender: (args) => {
      args.data.html = DayPilot.Util.escapeHtml(args.data.text) + "<br>" + args.data.type;
      args.data.areas = [
        {
          top: 5,
          right: 5,
          width: 30,
          height: 30,
          style: "border-radius: 50%; padding: 4px; box-sizing: border-box; cursor: pointer;",
          fontColor: "#fff",
          backColor: DayPilot.ColorUtil.darker("#6aa84f", 2),
          symbol: "/icons/daypilot.svg#minichevron-down-2",
          action: "ContextMenu",
        }
      ]
    }
  };

  schedulerConfig: DayPilot.SchedulerConfig = {
    timeHeaders: [
      {groupBy:"Day", format: "dddd MMMM d, yyyy"},
      {groupBy:"Hour"}
    ],
    rowHeaderColumns: [
      { text: "Name"},
      { text: "Capacity", display: "capacity"}
    ],
    scale: "Hour",
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    cellWidth: 80,
    dragOutAllowed: true,
    timeRangeSelectedHandling: "Enabled",
    durationBarVisible: false,
    eventHeight: 40,
    eventMarginBottom: 5,
    rowMarginTop: 5,
    rowMinHeight: 50,
    treeEnabled: true,
    contextMenu: new DayPilot.Menu({
      items: [
        {
          text: "Delete",
          onClick: (args) => {
            this.scheduler.control.events.remove(args.source);
          }
        }
      ]
    }),
    onTimeRangeSelected: async (args) => {
      const dp = args.control;
      const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
      dp.clearSelection();
      if (modal.canceled) { return; }
      dp.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result
      });
    },
    onEventMove: (args) => {
      if (args.external) {
        args.control.message("Moved from queue");
        this.queue.control.events.remove(args.e.data.id);
      }
    },
    onBeforeEventRender: (args) => {
      args.data.fontColor = "#fff";
      args.data.areas = [
        {
          top: 5,
          right: 5,
          width: 30,
          height: 30,
          style: "border-radius: 50%; padding: 4px; box-sizing: border-box; cursor: pointer;",
          fontColor: "#fff",
          backColor: DayPilot.ColorUtil.darker("#6aa84f", 2),
          symbol: "/icons/daypilot.svg#minichevron-down-2",
          action: "ContextMenu",
        }
      ]
    }
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {
    this.ds.getResources().subscribe(result => this.schedulerConfig.resources = result);

    const from = this.scheduler.control.visibleStart();
    const to = this.scheduler.control.visibleEnd();
    this.ds.getEvents(from, to).subscribe(events => {
      this.scheduler.control.update({events});
      this.scheduler.control.scrollTo(DayPilot.Date.today());
    });

    this.ds.getQueue().subscribe(events => {
      this.queue.control.update({events});
    });
  }

  async addTask(): Promise<void> {

    const durations = [
      {name: "1 hour", id: "60"},
      {name: "2 hours", id: "120"},
      {name: "3 hours", id: "180"},
      {name: "4 hours", id: "240"},
    ];

    const form = [
      {name: "Name", id: "text"},
      {name: "Duration", id: "minutes", type: "select", options: durations}
    ];
    const data= {
      text: "Task 1",
      minutes: 60
    };
    const modal = await DayPilot.Modal.form(form, data);

    if (modal.canceled) {
      return;
    }

    const e = modal.result;
    e.id = DayPilot.guid();
    e.duration = DayPilot.Duration.ofMinutes(e.minutes);

    this.queue.control.events.add(e);
  }

}