Overview

In this tutorial, we'll explore how to customize the row headers of the Vue Scheduler component using Vue templates. We'll add icons, buttons, and integrate other Vue components to the row header cells.

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.

Vue Scheduler Component Config and Data

Let's start by setting up a basic Vue Scheduler component configuration with predefined events and resources.

<template>
  <DayPilotScheduler
      :events="events"
      :resources="resources"
      scale="Day"
      :timeHeaders="[ { groupBy: 'Month' }, { groupBy: 'Day', format: 'd' } ]"
      :rowHeaderColumns="[{ title: 'Name', display: 'name' }, { title: 'Location', display: 'location' }]"
      :days="DayPilot.Date.today().daysInMonth()"
      :startDate="DayPilot.Date.today().firstDayOfMonth()"
  >
  </DayPilotScheduler>
</template>

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

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

onMounted(() => {
  resources.value = [
    { name: "Resource 1", id: "R1", location: "Location 1" },
    { name: "Resource 2", id: "R2", location: "Location 2" },
    { name: "Resource 3", id: "R3", location: "Location 3" }
  ];
  events.value = [
    { id: 1, start: DayPilot.Date.today(), end: DayPilot.Date.today().addDays(5), text: "Event 1", resource: "R2" }
  ];
});
</script>

In this setup:

  • events: An array of event objects that the scheduler will display.

  • resources: An array of resources representing different entities (e.g., people, rooms) associated with the events.

  • scale: Defines the time scale of the scheduler (e.g., Day, Week, Month).

  • timeHeaders: Configures the headers that display the time intervals.

  • rowHeaderColumns: Defines the columns in the row headers, specifying the title and the data field to display.

  • days and startDate: Set the range of dates displayed in the Vue Scheduler.

Customizing Scheduler Row Headers with Vue Templates

To customize the row headers, you need to define a Vue template and assign it to the #rowHeader slot. This template receives an args object containing the row and x properties:

  • row: The DayPilot.Row object representing the current row.

  • x: The column index or ID, indicating which row header column is being rendered.

You can access the original row data (resource definition) using args.row.data.

Single Row Header Column

If you have only one row header column, your template can be simple:

<template #rowHeader="{row}">
  <!-- Custom content for single column -->
</template>

Multiple Row Header Columns

When multiple row header columns are defined, the template is applied to each row header cell. Use the x property to determine which column is being rendered:

<template #rowHeader="{ row, x }">
  <!-- Custom content based on column index -->
</template>

Getting the Default Row Header Column Text

Sometimes, you might want to start with the default text provided by the Scheduler component. To retrieve the default text, use the display property defined in your rowHeaderColumns.

Here's how you can display the default text in the Vue template:

Vue Scheduler Row Header Templates - Getting the Default Text

In this example, row.columns[x].text fetches the text based on the display property of the respective row header column.

<DayPilotScheduler
  :rowHeaderColumns="[{ title: 'Name', display: 'name' }, { title: 'Location', display: 'location' }]"
  ... >

  <template #rowHeader="{row, x}">
    {{ row.columns[x].text }}
  </template>

</DayPilotScheduler>

Additional Content in the First Header Column

Vue Scheduler Row Header Templates - Custom Content

Enhance the first row header column by adding more information, such as the owner's name or additional details. You can conditionally render content based on the column index x.

<DayPilotScheduler
    :rowHeaderColumns="[{ title: 'Name', display: 'name' }, { title: 'Location', display: 'location' }]"
    ... >

  <template #rowHeader="{row, x}">
    {{ row.columns[x].text }}
    <div v-if="x === 0">
      <span style="font-size: 80%; color: #666;">{{ row.data.owner }}</span>
    </div>
  </template>

</DayPilotScheduler>

In this snippet:

  • The default text for each column is displayed.

  • An additional <div> is rendered only for the first column (x === 0), showing the owner's name in a smaller, gray font.

Adding Icons and Badges to the First Column

Vue Scheduler Row Header Templates - Icons and Badges

To display additional information and visual clues, you can add icons and badges to the Scheduler row header:

<DayPilotScheduler
    :rowHeaderColumns="[{ title: 'Name', display: 'name' }, { title: 'Location', display: 'location' }]"
    ... >

  <template #rowHeader="{ row, x }">
    <div class="scheduler-row-header">
      <!-- Name Column with Owner Avatar and Badge -->
      <template v-if="x === 0">
        <img :src="getOwnerAvatar(row.data.owner)" alt="Owner Avatar" class="avatar" />
        <span class="resource-name">{{ row.name }}</span>
        <span :class="['badge', getOwnerBadgeClass(row.data.owner)]" :title="row.data.owner">
          {{ row.data.owner }}
        </span>
      </template>

      <!-- Location Column with Icon -->
      <template v-else-if="x === 1">
        <span class="resource-location">{{ row.data.location }}</span>
      </template>
    </div>
  </template>

</DayPilotScheduler>

Explanation:

  • Avatar Image: Displays the owner's avatar using the getOwnerAvatar method.

  • Resource Name: Shows the resource name in bold.

  • Badge: Adds a colored badge indicating the owner's status or role, styled using the getOwnerBadgeClass method.

  • Location: Simply displays the location for the second column.

Adding a Delete Button

Vue Scheduler Row Header Templates - Delete Button

You can also add interactive elements to the row header using Vue templates, such as buttons or Vue components.

The following implementation allows users to remove resources directly from the Scheduler interface using a button with SVG icon.

<DayPilotScheduler
    :rowHeaderColumns="[{ title: 'Name', display: 'name' }, { title: 'Location', display: 'location' }]"
    ... >

  <template #rowHeader="{ row, x }">
    <div class="scheduler-row-header">
      <!-- Name Column with Owner Avatar and Badge -->
      <template v-if="x === 0">
        <img :src="getOwnerAvatar(row.data.owner)" alt="Owner Avatar" class="avatar" />
        <span class="resource-name">{{ row.name }}</span>
        <span :class="['badge', getOwnerBadgeClass(row.data.owner)]" :title="row.data.owner">
          {{ row.data.owner }}
        </span>
      </template>

      <!-- Location Column with Delete Button -->
      <template v-else-if="x === 1">
        <span class="resource-location">{{ row.data.location }}</span>
        <button @click="deleteResource(row.data.id)" class="delete-button" title="Delete" >
          <svg>
            <use xlink:href="/icons/daypilot.svg#x-4"></use>
          </svg>
        </button>
      </template>
    </div>
  </template>

</DayPilotScheduler>

Explanation:

  • Delete Button: A button with an SVG icon is added to the second column (x === 1). When clicked, it triggers the deleteResource method, removing the corresponding resource from the scheduler.

Now, define the deleteResource method. This method filters out the resource with the specified ID from the resources array:

const deleteResource = (id) => {
  resources.value = resources.value.filter(r => r.id !== id);
};

Full Source Code

Below is the complete source code integrating all the customizations of the Vue Scheduler row headers described above. This example includes a Vue template with avatars, badges, and a delete button.

<template>
  <DayPilotScheduler
      :events="events"
      :resources="resources"
      scale="Day"
      :timeHeaders="[ { groupBy: 'Month' }, { groupBy: 'Day', format: 'd' } ]"
      :rowHeaderColumns="rowHeaderColumns"
      :days="DayPilot.Date.today().daysInMonth()"
      :startDate="DayPilot.Date.today().firstDayOfMonth()"
      :treeEnabled="true"
      :durationBarVisible="false"
      @timeRangeSelected="onTimeRangeSelected"
      @beforeEventRender="onBeforeEventRender"
      ref="schedulerRef"
  >
    <template #rowHeader="{ row, x }">
      <div class="scheduler-row-header">
        <!-- Name Column with Owner Avatar and Badge -->
        <template v-if="x === 0">
          <img :src="getOwnerAvatar(row.data.owner)" alt="Owner Avatar" class="avatar" />
          <span class="resource-name">{{ row.name }}</span>
          <span :class="['badge', getOwnerBadgeClass(row.data.owner)]" :title="row.data.owner">
            {{ row.data.owner }}
          </span>
        </template>

        <!-- Location Column with Icon -->
        <template v-else-if="x === 1">
          <span class="resource-location">{{ row.data.location }}</span>
          <!-- delete icon, align to the right -->
          <button @click="deleteResource(row.data.id)" class="delete-button" title="Delete" >
            <svg>
              <use xlink:href="/icons/daypilot.svg#x-4"></use>
            </svg>
          </button>
        </template>
      </div>
    </template>
  </DayPilotScheduler>
</template>

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

const schedulerRef = ref(null);

const rowHeaderColumns = [
  { title: 'Name', display: 'name' },
  { title: 'Location', display: 'location' }
];

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

// Define owner information with avatars and badge colors
const owners = {
  'Owner 1': {
    avatar: '/images/owner_1.jpg',
    color: 'badge-blue'
  },
  'Owner 2': {
    avatar: '/images/owner_2.jpg',
    color: 'badge-green'
  }
};

// Fetch data on component mount
onMounted(() => {
  resources.value = [
    { name: "Resource 1", id: "R1", location: "New York", owner: "Owner 1" },
    { name: "Resource 2", id: "R2", location: "Los Angeles", owner: "Owner 1" },
    { name: "Resource 3", id: "R3", location: "Chicago", owner: "Owner 1" },
    { name: "Resource 4", id: "R4", location: "Houston", owner: "Owner 1" },
    { name: "Resource 5", id: "R5", location: "Phoenix", owner: "Owner 1" },
    { name: "Resource 6", id: "R6", location: "Philadelphia", owner: "Owner 2" },
    { name: "Resource 7", id: "R7", location: "San Antonio", owner: "Owner 2" },
    { name: "Resource 8", id: "R8", location: "San Diego", owner: "Owner 2" },
    { name: "Resource 9", id: "R9", location: "Dallas", owner: "Owner 2" },
    { name: "Resource 10", id: "R10", location: "San Jose", owner: "Owner 2" }
  ];

  events.value = [
    { id: 1, start: DayPilot.Date.today(), end: DayPilot.Date.today().addDays(5), text: "Event 1", resource: "R2" }
  ];
});

const onBeforeEventRender = (args) => {
  args.data.backColor = "#f0ad4e";
  args.data.borderColor = "darker";
  args.data.fontColor = "#ffffff";
  args.data.borderRadius = "5px";
  args.data.padding = 5;
};

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
  });
};

const deleteResource = (id) => {
  resources.value = resources.value.filter(r => r.id !== id);
};

const getOwnerAvatar = (owner) => {
  return owners[owner]?.avatar;
};

const getOwnerBadgeClass = (owner) => {
  return owners[owner]?.color || 'badge-default';
};
</script>

<style scoped>
/* Row Header Styles */
.scheduler-row-header {
  display: flex;
  align-items: center;
}

/* Avatar Styles */
.avatar {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  margin-right: 8px;
}

/* Resource Name Styles */
.resource-name {
  font-weight: bold;
  margin-right: 8px;
}

/* Badge Styles */
.badge {
  padding: 2px 6px;
  border-radius: 12px;
  font-size: 0.8em;
  color: #fff;
}

/* Badge Color Classes */
.badge-blue {
  background-color: #007bff;
}

.badge-green {
  background-color: #28a745;
}

.badge-default {
  background-color: #6c757d;
}

/* Location Icon Styles */
.location-icon {
  color: #dc3545;
  margin-right: 4px;
}

/* Resource Location Styles */
.resource-location {
  vertical-align: middle;
  margin-right: 20px;
}

/* Delete Button Styles */
.delete-button {
  background: none;
  border: none;
  cursor: pointer;
  position: absolute;
  right: 0px;
  top: 10px;
}

/* Delete Icon Styles */
.delete-button svg {
  width: 16px;
  height: 16px;
  color: #dc3545;
}
</style>

You can download the full Vue project using the download link at the top of the article.