Overview

  • In this tutorial, we will configure the Vue Scheduler component to accept tasks dragged from an ordered queue of unscheduled tasks.

  • The Queue component displays a list of unscheduled tasks. Users can change the task position/priority using drag and drop.

  • Tasks that have already been scheduled can be moved back to the queue using drag and drop.

  • You can download the attached Vue 3 project with TypeScript source code.

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 configure the Vue Scheduler for external drag and drop?

vue scheduler timeline grid

In the first step, we will add the Vue Scheduler component (from DayPilot Pro for JavaScript) to our application. To see an introduction to using the Scheduler component is Vue, please see the basic tutorial - Vue Scheduler: Build a Reservation Application in 5 Minutes. You’ll learn how to install the NPM package with Vue support, add the Scheduler component to the Vue application, load the data and set the configuration properties.

Our Scheduler component uses standard configuration generated by the UI Builder. To ensure interoperability with the Queue component, we need to make a few changes.

How to remove the tasks from the queue when they are dropped in the Scheduler?

You need to extend the onEventMoved event handler to cover the external drag and drop. In case that the tasks was dragged from the queue (args.external is set to true) we need to remove the task object from the source component using events.remove().

How to enable dragging the tasks back to queue?

By default, the tasks can only be moved to another location in the Vue Scheduler grid. To enable dragging tasks outside of the Scheduler, add dragOutAllowed: true to the config properties. The dragOutAllowed property will let users drag the tasks that have been already scheduled back to the queue. The queue always accepts the external item and there is no need to enable it.

What is needed to activate the Vue Scheduler?

Here are the important parts of the source code that make the Vue Scheduler working.

Template:

<template>
  <DayPilotScheduler :config="config" ref="schedulerRef" />
  // ...
</template>

TypeScript:

<script setup lang="ts">
import { DayPilot, DayPilotScheduler, DayPilotQueue } from 'daypilot-pro-vue';
import { ref, reactive, onMounted } from 'vue';

const config = reactive<DayPilot.SchedulerConfig>({
  timeHeaders: [{"groupBy": "Month"}, {"groupBy": "Day", "format": "d"}],
  scale: "Day",
  days: DayPilot.Date.today().daysInYear(),
  startDate: DayPilot.Date.today().firstDayOfYear(),
  timeRangeSelectedHandling: "Enabled",
  onTimeRangeSelected: schedulerOnTimeRangeSelected,
  onEventMoved: schedulerOnEventMoved,
  onBeforeEventRender: onBeforeEventRender,
  dragOutAllowed: true,
  treeEnabled: true,
  cellWidth: 90,
  eventHeight: 45
});

const schedulerRef = ref<DayPilotScheduler>(null);

async function schedulerOnTimeRangeSelected(args: DayPilot.SchedulerTimeRangeSelectedArgs): Promise<void> {
  const dp = args.control;

  const form = [
    {name: "Task name", id: "text", type: "text"},
  ];

  const data = {
    text: "Task 1"
  };

  const modal = await DayPilot.Modal.form(form, data);
  dp.clearSelection();
  if (modal.canceled) {
    return;
  }

  dp.events.add({
    start: args.start,
    end: args.end,
    id: DayPilot.guid(),
    resource: args.resource,
    text: modal.result.text
  });
}

function schedulerOnEventMoved(args: DayPilot.SchedulerEventMovedArgs): void {
  if (args.external) {
    queueRef.value.control.events.remove(args.e.data.id);
  }
}

const loadEvents = () => {
  const events = [
    { id: 1, start: DayPilot.Date.today().addDays(1), end: DayPilot.Date.today().addDays(5), text: "Task 1", resource: "R2" }
  ];
  schedulerRef.value?.control.update({ events });
};

const loadResources = () => {
  const resources = [
    { name: "Resource 1", id: "R1" },
    { name: "Resource 2", id: "R2" },
    { name: "Resource 3", id: "R3" },
    { name: "Resource 4", id: "R4" },
    { name: "Resource 5", id: "R5" },
    { name: "Resource 6", id: "R6" },
    { name: "Resource 7", id: "R7" },
  ];
  schedulerRef.value?.control.update({ resources });
};

onMounted(() => {
  loadResources();
  loadEvents();
  schedulerRef.value?.control.scrollTo(DayPilot.Date.today());
  schedulerRef.value?.control.message("Welcome!");
});
</script>

How to add a Queue of unscheduled tasks?

vue scheduler queue of unscheduled tasks

The queue is implemented using the <DayPilotQueue> component. It provides a convenient way of handling an ordered queue of tasks:

  • The tasks data format is shared with the Scheduler.

  • The built-in CSS theme styles the events to look the same way.

  • You can reorder tasks using drag and drop.

  • The queue items are automatically activated as external items that can be moved to the Scheduler.

  • The tasks can be moved from the Scheduler back to the queue.

Instead of using the Vue Queue component, it’s also possible to implement your own queue using DayPilot.Scheduler.makeDraggable() and DayPilot.Scheduler.registerDropTarget() methods.

How to add the Queue component?

First, we need to add the <DayPilotQueue> tag to the Vue template.

  • Its :config attribute points to the queue configuration object.

  • The ref attribute stores a reference to the DayPilotQueue object. We will use it later to access the DayPilot.Queue API (e.g. to add a task programmatically).

<template>
  <DayPilotQueue :config="queueConfig" ref="queueRef" />
  <!-- ... -->
</template>

TypeScript:

<script setup lang="ts">
import { DayPilot, DayPilotScheduler, DayPilotQueue } from 'daypilot-pro-vue';
import { ref, reactive, onMounted } from 'vue';

const queueConfig = reactive<DayPilot.QueueConfig>({
  eventHeight: 45
});

const queueRef = ref<DayPilotQueue>(null);

const loadQueueEvents = () => {
  const events = [
    {text: "Task 31", id: 31, duration: DayPilot.Duration.ofDays(3)}
  ];

  queueRef.value.control.update({events});
}


onMounted(() => {
  loadQueueEvents();
});
</script>

The config object is quite simple - at this moment it only specifies a custom height of the queue items (events). You can also specify other properties and event handlers here.

The loadQueueEvents() function loads the queue items using the direct API - the update() method.

We also store the DayPilotQueue reference in queueRef, as mentioned above.

How to load the queue items?

Our Vue app now uses the direct API to load the event/task data:

const loadQueueEvents = () => {
  const events = [
    {text: "Task 31", id: 31, duration: DayPilot.Duration.ofDays(3)}
  ];

  queueRef.value.control.update({events});
}

Instead using the direct API, you can also modify the config object. Just make sure that the change is detected by Vue:

loadQueueEvents(): void {
  const events = [
    {text: "Task 1", id: 1, duration: DayPilot.Duration.ofDays(3)}
  ];

  queueConfig.events = events;
}

How to handle external drag and drop?

The queue component lets users drag events to a different position in the queue and it also accepts items dragged from the Vue Scheduler. In order to detect the source of the task, you need to check the args.external value in onEventMoved event handler.

  • Tasks moved from the Scheduler will have the args.external value set to true.

  • The target position is stored as a zero-based index in args.position property.

queueOnEventMoved(args: any): void {
  if (args.external) {
    this.scheduler.control.events.remove(args.e.data.id);
  }

  console.log("target position", args.position);
}

How to add a “New Task” button

Vue Scheduler with Queue of Unscheduled Tasks and New Task Button

In this step, we will add a “New Task” button above the queue component.

It will open a modal dialog for entering new task details (the task text in this case) and add the new task to the queue.

Template:

<template>
  <div>
    <button @click="addClick" class="button-add">New Task...</button>
  </div>
  <DayPilotQueue :config="queueConfig" ref="queueRef" />
</template>

TypeScript:

<script setup lang="ts">
import { DayPilot, DayPilotScheduler, DayPilotQueue } from 'daypilot-pro-vue';
import { ref, reactive, onMounted } from 'vue';

const queueRef = ref<DayPilotQueue>(null);


const addClick = async () => {
  const dp = queueRef.value?.control;

  const form = [
    {name: "Task name", id: "text", type: "text"},
  ];

  const data = {
    text: "Task 1"
  };

  const modal = await DayPilot.Modal.form(form, data);
  if (modal.canceled) {
    return;
  }

  dp.events.add({
    start: DayPilot.Date.today().addDays(1),
    end: DayPilot.Date.today().addDays(2),
    id: DayPilot.guid(),
    text: modal.result.text
  });
};

// ...

</script>

The addClick() function uses the events.add() method of the Queue component to add a new task to the bottom of the queue.

Full Source Code

Here you can find the full source code of our Scheduler.vue component that combines a visual Scheduler component (with a timeline on the horizontal axis and resources as rows) and a queue of unscheduled tasks.

It also includes additional configuration and CSS styling.

<template>
  <div class="parent">
    <div class="left">
      <div>
        <button @click="addClick" class="button-add">New Task...</button>
      </div>
      <DayPilotQueue :config="queueConfig" ref="queueRef" />
    </div>
    <div class="right">
      <DayPilotScheduler :config="config" ref="schedulerRef" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { DayPilot, DayPilotScheduler, DayPilotQueue } from 'daypilot-pro-vue';
import { ref, reactive, onMounted } from 'vue';

const config = reactive<DayPilot.SchedulerConfig>({
  timeHeaders: [{"groupBy": "Month"}, {"groupBy": "Day", "format": "d"}],
  scale: "Day",
  days: DayPilot.Date.today().daysInYear(),
  startDate: DayPilot.Date.today().firstDayOfYear(),
  timeRangeSelectedHandling: "Enabled",
  onTimeRangeSelected: schedulerOnTimeRangeSelected,
  onEventMoved: schedulerOnEventMoved,
  onBeforeEventRender: onBeforeEventRender,
  dragOutAllowed: true,
  treeEnabled: true,
  cellWidth: 90,
  eventHeight: 45
});

const queueConfig = reactive<DayPilot.QueueConfig>({
  onEventMoved: queueOnEventMoved,
  onBeforeEventRender: onBeforeEventRender,
  eventHeight: 45
});

const schedulerRef = ref<DayPilotScheduler>(null);
const queueRef = ref<DayPilotQueue>(null);

async function schedulerOnTimeRangeSelected(args: DayPilot.SchedulerTimeRangeSelectedArgs): Promise<void> {
  const dp = args.control;

  const form = [
    {name: "Task name", id: "text", type: "text"},
  ];

  const data = {
    text: "Task 1"
  };

  const modal = await DayPilot.Modal.form(form, data);
  dp.clearSelection();
  if (modal.canceled) {
    return;
  }

  dp.events.add({
    start: args.start,
    end: args.end,
    id: DayPilot.guid(),
    resource: args.resource,
    text: modal.result.text
  });
}

function schedulerOnEventMoved(args: DayPilot.SchedulerEventMovedArgs): void {
  if (args.external) {
    queueRef.value.control.events.remove(args.e.data.id);
  }
}

function queueOnEventMoved(args: any): void {
  if (args.external) {
    schedulerRef.value.control.events.remove(args.e.data.id);
  }
}

function onBeforeEventRender(args: any): void {
  args.data.barHidden = true;
  args.data.areas = [
    {
      top: 10,
      right: 10,
      width: 25,
      height: 25,
      style: "border-radius: 5px; cursor: pointer;",
      backColor: "#448d1d",
      symbol: "daypilot.svg#threedots-v",
      fontColor: "#ffffff",
      padding: 4,
      action: "ContextMenu",
      menu: new DayPilot.Menu({
        items: [
          {
            text: "Delete",
            onClick: (args: any) => {
              schedulerRef.value.control.events.remove(args.source.data.id);
              queueRef.value.control.events.remove(args.source.data.id);
            }
          },
        ]
      })
    }
  ];
}

const addClick = async () => {
  const dp = queueRef.value?.control;

  const form = [
    {name: "Task name", id: "text", type: "text"},
  ];

  const data = {
    text: "Task 1"
  };

  const modal = await DayPilot.Modal.form(form, data);
  if (modal.canceled) {
    return;
  }

  dp.events.add({
    start: DayPilot.Date.today().addDays(1),
    end: DayPilot.Date.today().addDays(2),
    id: DayPilot.guid(),
    text: modal.result.text
  });
};

const loadEvents = () => {
  const events = [
    { id: 1, start: DayPilot.Date.today().addDays(1), end: DayPilot.Date.today().addDays(5), text: "Task 1", resource: "R2" }
  ];
  schedulerRef.value?.control.update({ events });
};

const loadResources = () => {
  const resources = [
    { name: "Resource 1", id: "R1" },
    { name: "Resource 2", id: "R2" },
    { name: "Resource 3", id: "R3" },
    { name: "Resource 4", id: "R4" },
    { name: "Resource 5", id: "R5" },
    { name: "Resource 6", id: "R6" },
    { name: "Resource 7", id: "R7" },
  ];
  schedulerRef.value?.control.update({ resources });
};

const loadQueueEvents = () => {
  const events = [
    {text: "Task 31", id: 31, duration: DayPilot.Duration.ofDays(3)}
  ];

  // queueConfig.events = events;
  queueRef.value.control.update({events});
}

onMounted(() => {
  loadResources();
  loadEvents();
  loadQueueEvents();
  schedulerRef.value?.control.scrollTo(DayPilot.Date.today());
  schedulerRef.value?.control.message("Welcome!");
});
</script>

<style>
body .scheduler_default_event_inner, body .queue_default_event_inner {
  background: #5caf39;
  color: white;
  border: 1px solid #448d1d;
  border-radius: 5px;
  padding: 5px;
  margin: 2px;
}

</style>

<style scoped>
.parent {
  display: flex;
}
.left {
  width: 200px;
  margin-right: 10px;
}
.right {
  flex-grow: 1;
}

.button-add {
  margin-bottom: 10px;
  width: 100%;
  padding: 10px;
  background-color: #f1f1f1;
  border: 1px solid #cccccc;
  border-radius: 5px;
  cursor: pointer;
}

.button-add:hover {
  background-color: #e1e1e1;
}

</style>

History

  • June 14, 2024: Upgraded to Vue 3, Composition API, DayPilot Pro 2024.2.5951. Additional task styling.

  • July 16, 2021: Initial release.