Overview

  • You can use Vue templates to define the content of Vue Scheduler event boxes.

  • Add a checkbox that marks the event as “Important”.

  • Use a drop-down list that sets the event status (e.g., “In Progress”).

  • Upload an attachment to the event using a special icon.

  • This feature is available since version 2024.4.6241.

  • The Vue project includes a trial version of DayPilot Pro for JavaScript (see 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.

Using Vue Templates to Customize Appearance of Scheduler Events

In Vue, you can use templates to define content for named slots.

The Vue Scheduler component defines named slots for the following visual elements:

  • Events: <template #event={event}></template>

  • Grid cells: <template #cell={cell}></template>

  • Time headers: <template #timeHeader={header}></template>

  • Row headers: <template #rowHeader={row, x}></template>

Scheduler templates are supported in Vue 3.

In addition to Vue templates, you can also customize the event content using the onBeforeEventRender event handler. It lets you add icons, time segments, drag handles and other active areas. You can also use it to adjust the behavior (e.g., by disabling the drag and drop actions).

A simple Vue template for the event slot can look like this:

<template>
  <DayPilotScheduler
      :events="events"
      :resources="resources">
    <template #event="{ event }">
        <div style="font-weight: bold">{{ event.text() }}</div>
    </template>
  </DayPilotScheduler>
</template>

You can see that the event template receives an event object (DayPilot.Event) which holds the event details.

Adding a Checkbox Using an Event Template

Vue Scheduler Event Template with Active Element (Checkbox)

Let’s add a checkbox that will toggle the “Important” status of an event on click.

<template>
  <DayPilotScheduler
      :events="events"
      :resources="resources"
  >
    <template #event="{ event }">
      <div class="event-title">{{ event.text() }}</div>
      <div class="event-important">
        <label><span class="event-item-desc">Important:</span><input type="checkbox" v-model="event.data.important" @change="onImportantChange(event)" :title="'Mark as important'" /></label>
      </div>
    </template>

  </DayPilotScheduler>
</template>

Sample event data:

const loadEvents = () => {
  events.value = [
    {
      id: 1,
      start: "2024-10-01T00:00:00",
      end: "2024-10-05T00:00:00",
      text: "Event 1",
      resource: "R1",
      important: false,
    },
    {
      id: 2,
      start: DayPilot.Date.today(),
      end: DayPilot.Date.today().addDays(5),
      text: "Event 2",
      resource: "R2",
      important: false,
    }
  ];
};

The onImportantChange event handler prints the change details to the JavaScript console:

const onImportantChange = (event) => {
  console.log(`Event ${event.text()} important: ${event.data.important}`);
};

Adding a Drop-Down List Using a Template

Vue Scheduler Event Template with a Drop-Down List

You can also add other HTML elements, such as a drop-down list.

The following Vue event template defines the drop-down list using a <select> element and includes three options (“Not Started”, “In Progress”, “Completed”).

The selected value (v-model) is bound to the status property of the event data object.

<template>
  <DayPilotScheduler
      :events="events"
      :resources="resources"
  >
    <template #event="{ event }">
      <div class="event-title">{{ event.text() }}</div>
      <div class="event-status">
        <span class="event-item-desc">Status:</span>
        <select v-model="event.data.status" @change="onStatusChange(event)" @mousedown.stop>
          <option value="Not Started">Not Started</option>
          <option value="In Progress">In Progress</option>
          <option value="Completed">Completed</option>
        </select>
      </div>
    </template>
  </DayPilotScheduler>
</template>

On change, the drop-down list fires the onStatusChange() function which prints the details to the JavaScript console:

const onStatusChange = (event) => {
  console.log(`Event ${event.text()} status changed to: ${event.data.status}`);
};

Adding an Attachment to the Vue Scheduler Event

Vue Scheduler Event Template with an Attachment Upload Button

In the third example, we will include an <input type="file"> element in the Vue event template so users can upload an attachment.

<template>
  <DayPilotScheduler
      :events="events"
      :resources="resources"
  >
    <template #event="{ event }">
      <div class="event-body">
        <div class="event-title">{{ event.text() }}</div>
        <div class="event-attachment">
          <span class="event-item-desc">Attachment:</span>
          <label class="file-label" :title="'Attach a file'">
            <input type="file" @change="onFileChange($event, event)" class="file-input" />
            <svg>
              <use xlink:href="/icons/daypilot.svg#plus-circle"></use>
            </svg>
          </label>
          <span v-if="event.data.attachment">
              <a :href="event.data.attachment.url" target="_blank">{{ event.data.attachment.name }}</a>
            </span>
        </div>
      </div>
    </template>
  </DayPilotScheduler>
</template>

The onFileChange() function handles the actual upload:

const onFileChange = (e, event) => {
  const file = e.target.files[0];
  if (file) {
    const url = URL.createObjectURL(file);
    event.data.attachment = {
      name: file.name,
      url: url
    };
    console.log(`File attached to event "${event.text()}": ${file.name}`);
  }
};

Full Source Code

Here is the full source code of our Vue Scheduler component that customizes the event content using a named Vue template.

<template>
  <DayPilotScheduler
      scale="Day"
      :eventHeight="100"
      :cellWidth="100"
      :durationBarVisible="false"
      :eventBorderRadius="10"
      :rowMarginTop="10"
      :rowMarginBottom="10"
      :days="DayPilot.Date.today().daysInMonth()"
      :startDate="DayPilot.Date.today().firstDayOfMonth()"
      :timeHeaders="[ { groupBy: 'Month' }, { groupBy: 'Day', format: 'd' } ]"
      :treeEnabled="true"
      @timeRangeSelected="onTimeRangeSelected"
      :events="events"
      :resources="resources"
      ref="schedulerRef"
  >
    <template #event="{ event }">
        <div class="event-body">
          <div class="event-title">{{ event.text() }}</div>
          <div class="event-important">
            <label><span class="event-item-desc">Important:</span><input type="checkbox" v-model="event.data.important" @change="onImportantChange(event)" :title="'Mark as important'" /></label>
          </div>
          <div class="event-status">
            <span class="event-item-desc">Status:</span>
            <select v-model="event.data.status" @change="onStatusChange(event)" @mousedown.stop>
              <option value="Not Started">Not Started</option>
              <option value="In Progress">In Progress</option>
              <option value="Completed">Completed</option>
            </select>
          </div>
          <div class="event-attachment">
            <span class="event-item-desc">Attachment:</span>
            <label class="file-label" :title="'Attach a file'">
              <input type="file" @change="onFileChange($event, event)" class="file-input" />
              <svg>
                <use xlink:href="/icons/daypilot.svg#plus-circle"></use>
              </svg>
            </label>
            <span v-if="event.data.attachment">
              <a :href="event.data.attachment.url" target="_blank">{{ event.data.attachment.name }}</a>
            </span>
          </div>
        </div>
    </template>

  </DayPilotScheduler>
</template>

<script setup>
import { DayPilot, DayPilotScheduler } from 'daypilot-pro-vue';
import { ref, onMounted } from 'vue';

const events = ref([]);
const resources = ref([]);

const onTimeRangeSelected = async (args) => {
  const scheduler = args.control;
  const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
  scheduler.clearSelection();
  if (modal.canceled) { return; }
  scheduler.events.add({
    start: args.start,
    end: args.end,
    id: DayPilot.guid(),
    resource: args.resource,
    text: modal.result,
    data: {
      completed: false,
      status: 'Not Started',
      attachment: null
    }
  });
};

const onImportantChange = (event) => {
  console.log(`Event ${event.text()} important: ${event.data.important}`);
};

const onStatusChange = (event) => {
  console.log(`Event ${event.text()} status changed to: ${event.data.status}`);
};

const onFileChange = (e, event) => {
  const file = e.target.files[0];
  if (file) {
    const url = URL.createObjectURL(file);
    event.data.attachment = {
      name: file.name,
      url: url
    };
    console.log(`File attached to event "${event.text()}": ${file.name}`);
  }
};

const schedulerRef = ref(null);

const loadEvents = () => {
  events.value = [
    {
      id: 1,
      start: "2024-10-01T00:00:00",
      end: "2024-10-05T00:00:00",
      text: "Event 1",
      resource: "R1",
      important: false,
      status: 'Not Started',
      attachment: null
    },
    {
      id: 2,
      start: DayPilot.Date.today(),
      end: DayPilot.Date.today().addDays(5),
      text: "Event 2",
      resource: "R2",
      important: false,
      status: 'In Progress',
      attachment: null
    }
  ];
};

const loadResources = () => {
  resources.value = [
    { name: "Resource 1", id: "R1" },
    { name: "Resource 2", id: "R2" },
    { name: "Resource 3", id: "R3" }
  ];
};

onMounted(() => {
  loadResources();
  loadEvents();
});
</script>

<style>
.scheduler_default_event_inner {
  background: #ffcc66c0;
  border: 1px solid rgba(248, 185, 50, 0.75);
}

</style>
<style scoped>

.event-body {
  padding: 5px;
}

.event-title {
  font-size: 14px;
  font-weight: bold;
  color: #333;
  flex-grow: 1;
  margin-right: 5px;
}

.event-actions input[type="checkbox"] {
  margin-right: 10px;
  cursor: pointer;
}

.file-input {
  display: none;
}

.file-label {
  cursor: pointer;
  font-size: 16px;
}

.file-label svg {
  width: 16px;
  height: 16px;
  color: #0075ff;
}

.event-body select {
  padding: 2px;
  border-radius: 15px;
}

.event-important {
  margin-top: 5px;
}

.event-important label {
  display: flex;
  align-items: center;
  gap: 2px;
}

.event-item-desc {
  width: 70px;
}

.event-status {
  margin-top: 5px;
  display: flex;
  align-items: center;
  gap: 5px;
}

.event-attachment {
  margin-top: 5px;
  display: flex;
  align-items: center;
  gap: 5px;
}

.event-attachment label {
  display: flex;
  align-items: center;
  gap: 5px;
}

.event-attachment a {
  color: #1a73e8;
  text-decoration: none;
}

.event-attachment a:hover {
  text-decoration: underline;
}

.important .event-title {
  color: red;
}

</style>

You can also download the complete Vue 3 project using the link at the top of this article.