---
title: "Vue Timeline Dynamic Resource Availability Example | Mobiscroll"
component: "Timeline"
framework: "Vue"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/vue/timeline/dynamically-color-and-invalidate"
---

# Dynamic Resource Availability - Vue Timeline demo

Color-code resource rows and restrict drop zones by task type on a Vue Timeline — compatible resources turn green, incompatible ones gray during drag. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/timeline/dynamically-color-and-invalidate)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/eventcalendar/timeline/dynamically-color-and-invalidate)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/eventcalendar/timeline/dynamically-color-and-invalidate)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/javascript/timeline/dynamically-color-and-invalidate.md), [jQuery](https://demo.mobiscroll.com/jquery/timeline/dynamically-color-and-invalidate.md), [Angular](https://demo.mobiscroll.com/angular/timeline/dynamically-color-and-invalidate.md), [React](https://demo.mobiscroll.com/react/timeline/dynamically-color-and-invalidate.md)

## Description

The options of the timeline can be dynamically changed. That applies to both the `color` and `invalid` options which control the resource track background colors and their valid state.

Based on the type of event someone wants to add or create we can highlight resources that support that type of event and invalidate resources that don't.

In the following example we have two types of tasks/events: **HW** for Hardware and **SW** for Software tasks. We also have two teams or two groups of resources: **HW Team** and **SW Team**. What we want to do is enforce event creation for teams based on the type of event: **HW** for the hardware team and **SW** for the software team.

We'll use the [`onEventDragStart`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDragStart) and [`onEventDragEnd`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDragEnd) lifecycle events to highlight & invalidate and then set everything back to the default state. We have access to the `event.category` through the lifecycle event's `args`.

**Want to learn about lifecycle events?** [Learn how you can levarage the various events that are triggered →](https://demo.mobiscroll.com/vue/timeline/event-hooks)

## What this demo shows

- A desktop daily timeline with a horizontal 24-hour time grid, a hierarchical resource tree on the left, and an external task list for drag-and-drop scheduling.
- **Header navigation** The month and year label in the top left opens date navigation, while the blue previous and next arrows and the Today button on the right make it easy to move between days and jump back to the current day.
- **Day view** A fixed strip below the header shows the selected day using the `DD DDD MMM YYYY` date format, with the current date highlighted.
- **Time grid** The timeline displays the selected day as hourly columns across a full 24-hour range.
- **Resources** The left side shows expandable and collapsible resource groups, including parent groups such as HW Team and SW Team.
- **Resource hierarchy** Individual resources are organized under their respective team groups.
- **External task list** A left-side panel titled `Available tasks` lists preset tasks: `Task 1 HW`, `Task 2 HW`, `Task 3 HW`, `Task 4 SW`, `Task 5 SW`, and `Task 6 SW`.
- **External drag and drop** Tasks can be dragged from the external list and dropped onto the timeline to create predefined events.
- **Drop validation** Tasks ending in HW can only be dropped on HW Team resources, while tasks ending in SW can only be dropped on SW Team resources.
- **Drag feedback** While dragging an external task, compatible resource backgrounds are highlighted in green and incompatible team resources are shown in gray.
- **Created and updated event confirmation** Creating, dropping, or updating a task on the timeline shows a confirmation toast in the center of the timeline.
- **Current time** A vertical blue line with a time label marks the current time.
- **Hover behavior** Hovering over the time grid shows a time indicator that follows the cursor in 15-minute increments.
- **Event cards** The demo starts with no loaded events. Events can be scheduled from the external task list or created directly on the timeline.
- **Event rendering** Events appear as colored cards with a colored left stripe, exact start and end times above the title, and the project name in bold.
- **Event positioning** Events are positioned by assigned resource, date, start time, and end time.
- **Event creation** New events can be created by double-clicking an individual resource row or by clicking and dragging across a time range.
- **Team constraints** Events created in one team cannot be moved to the other team.
- **Event interaction** Events are highlighted on hover and show drag and resize handles for repositioning or changing duration.
- **Event selection** Clicking an event selects and highlights it.
- **Vertical scrolling** The resource area scrolls vertically so many resources can be displayed in one timeline.
- **Horizontal scrolling** The timeline supports horizontal scrolling for navigating the full time range.

## Best for

- **Task assignment** Assigning work items to specific resources through direct drag-and-drop scheduling.
- **Dispatching** Scheduling hardware and software tasks against the correct team or resource group.
- **Field service planning** Matching task types to available people, crews, or equipment while preventing invalid assignments.
- **Manufacturing workflows** Scheduling categorized work across grouped production resources.
- **Project scheduling** Planning team-based tasks on a daily timeline with visible resource availability.
- **Validation-heavy scheduling** Enforcing resource rules while giving users clear visual feedback during drag and drop.

## API used in this demo

- [`theme`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-themeVariant) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`resources`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-resources) - option
- [`invalid`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-invalid) - option
- [`colors`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-colors) - option
- [`dragToMove`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-dragToMove) - option
- [`externalDrop`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-externalDrop) - option
- [`extendDefaultEvent`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-extendDefaultEvent) - option
- [`event-drag-start`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDragStart) - event
- [`event-drag-end`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDragEnd) - event
- [`event-created`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreated) - event
- [`event-updated`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdated) - event
- [`event-create-failed`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventCreateFailed) - event
- [`event-update-failed`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdateFailed) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import {
  MbscDraggable,
  MbscEventcalendar,
  MbscPage,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import { ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const hwInvalids = [
  {
    recurring: {
      repeat: 'daily'
    },
    resource: ['res4', 'res5', 'res6']
  }
]
const swInvalids = [
  {
    recurring: {
      repeat: 'daily'
    },
    resource: ['res1', 'res2', 'res3']
  }
]
const hwColors = [
  {
    recurring: {
      repeat: 'daily'
    },
    resource: ['res1', 'res2', 'res3'],
    background: '#1ad4041a'
  }
]
const swColors = [
  {
    recurring: {
      repeat: 'daily'
    },
    resource: ['res4', 'res5', 'res6'],
    background: '#1ad4041a'
  }
]
const myView = {
  timeline: { type: 'day' }
}
const myResources = [
  {
    id: 'hwt',
    name: 'HW Team',
    eventCreation: false,
    children: [
      {
        id: 'res1',
        name: 'Resource 1',
        color: '#0e9ea5'
      },
      {
        id: 'res2',
        name: 'Resource 2',
        color: '#0e9ea5'
      },
      {
        id: 'res3',
        name: 'Resource 3',
        color: '#0e9ea5'
      }
    ]
  },
  {
    id: 'swt',
    name: 'SW Team',
    eventCreation: false,
    children: [
      {
        id: 'res4',
        name: 'Resource 4',
        color: '#c3b726'
      },
      {
        id: 'res5',
        name: 'Resource 5',
        color: '#c3b726'
      },
      {
        id: 'res6',
        name: 'Resource 6',
        color: '#c3b726'
      }
    ]
  }
]
const myTasks = [
  {
    taskId: 1,
    title: 'Task 1',
    start: '08:00',
    end: '12:00',
    category: 'hw',
    color: '#0e9ea5'
  },
  {
    taskId: 2,
    title: 'Task 2',
    start: '08:00',
    end: '12:00',
    category: 'hw',
    color: '#0e9ea5'
  },
  {
    taskId: 3,
    title: 'Task 3',
    start: '08:00',
    end: '12:00',
    category: 'hw',
    color: '#0e9ea5'
  },
  {
    taskId: 4,
    title: 'Task 4',
    start: '08:00',
    end: '12:00',
    category: 'sw',
    color: '#c3b726'
  },
  {
    taskId: 5,
    title: 'Task 5',
    start: '08:00',
    end: '12:00',
    category: 'sw',
    color: '#c3b726'
  },
  {
    taskId: 6,
    title: 'Task 6',
    start: '08:00',
    end: '12:00',
    category: 'sw',
    color: '#c3b726'
  }
]

const myInvalids = ref([])
const myColors = ref([])
const toastMessage = ref('')
const isToastOpen = ref(false)
const dragElements = ref([])

function myDefaultEvent(event) {
  const res = event.resource

  if (res) {
    if (res === 'res1' || res === 'res2' || res === 'res3') {
      return {
        category: 'hw'
      }
    } else {
      return {
        category: 'sw'
      }
    }
  }
}

function handleEventDragStart(args) {
  let event = args.event

  if (event) {
    event = event.original || event

    if (event.category === 'hw') {
      myInvalids.value = hwInvalids
      myColors.value = hwColors
    } else {
      myInvalids.value = swInvalids
      myColors.value = swColors
    }
  }
}

function handleEventDragEnd() {
  myInvalids.value = []
  myColors.value = []
}

function handleEventCreated() {
  toastMessage.value = 'Event created'
  isToastOpen.value = true
}

function handleEventUpdated() {
  toastMessage.value = 'Event moved'
  isToastOpen.value = true
}

function handleEventCreateFailed() {
  toastMessage.value = "Can't create event"
  isToastOpen.value = true
}

function handleEventUpdateFailed() {
  toastMessage.value = "Can't move event"
  isToastOpen.value = true
}

function handleToastClose() {
  isToastOpen.value = false
}
</script>

<template>
  <MbscPage>
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div class="mbsc-col-sm-3">
          <div class="mbsc-form-group-title">Available tasks</div>
          <div v-for="(task, i) in myTasks" :key="task.taskId">
            <div ref="dragElements" class="dynamically-color-and-invalidate-task">
              <div>
                {{ task.title }}
                <span class="dynamically-color-and-invalidate-task-type">{{ task.category }}</span>
              </div>
              <MbscDraggable :element="dragElements[i]" :dragData="task" />
            </div>
          </div>
        </div>
        <div class="mbsc-col-sm-9 dynamically-color-and-invalidate-calendar">
          <MbscEventcalendar
            :view="myView"
            :resources="myResources"
            :invalid="myInvalids"
            :colors="myColors"
            :dragToMove="true"
            :externalDrop="true"
            :extendDefaultEvent="myDefaultEvent"
            @event-drag-start="handleEventDragStart"
            @event-drag-end="handleEventDragEnd"
            @event-created="handleEventCreated"
            @event-updated="handleEventUpdated"
            @event-create-failed="handleEventCreateFailed"
            @event-update-failed="handleEventUpdateFailed"
          />
        </div>
      </div>
    </div>
  </MbscPage>
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
</template>

<style>
.dynamically-color-and-invalidate-calendar {
  border-left: 1px solid #ccc;
}

.dynamically-color-and-invalidate-task {
  border: 2px solid #888;
  color: #666;
  padding: 10px;
  margin: 20px;
  border-radius: 8px;
  font-family:
    -apple-system,
    Segoe UI,
    Roboto,
    sans-serif;
}

.dynamically-color-and-invalidate-task-type {
  float: right;
  margin-left: 30px;
  text-transform: uppercase;
}

.dynamically-color-and-invalidate-calendar .mbsc-timeline-parent {
  height: 32px;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  MbscDraggable,
  MbscEventcalendar,
  MbscPage,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventDragEvent,
  MbscResource
} from '@mobiscroll/vue'
import { ref } from 'vue'

setOptions({
  theme: 'ios',
  themeVariant: 'light'
})

const hwInvalids = [
  {
    recurring: {
      repeat: 'daily'
    },
    resource: ['res4', 'res5', 'res6']
  }
]
const swInvalids = [
  {
    recurring: {
      repeat: 'daily'
    },
    resource: ['res1', 'res2', 'res3']
  }
]
const hwColors = [
  {
    recurring: {
      repeat: 'daily'
    },
    resource: ['res1', 'res2', 'res3'],
    background: '#1ad4041a'
  }
]
const swColors = [
  {
    recurring: {
      repeat: 'daily'
    },
    resource: ['res4', 'res5', 'res6'],
    background: '#1ad4041a'
  }
]
const myView: MbscEventcalendarView = {
  timeline: { type: 'day' }
}
const myResources: MbscResource[] = [
  {
    id: 'hwt',
    name: 'HW Team',
    eventCreation: false,
    children: [
      {
        id: 'res1',
        name: 'Resource 1',
        color: '#0e9ea5'
      },
      {
        id: 'res2',
        name: 'Resource 2',
        color: '#0e9ea5'
      },
      {
        id: 'res3',
        name: 'Resource 3',
        color: '#0e9ea5'
      }
    ]
  },
  {
    id: 'swt',
    name: 'SW Team',
    eventCreation: false,
    children: [
      {
        id: 'res4',
        name: 'Resource 4',
        color: '#c3b726'
      },
      {
        id: 'res5',
        name: 'Resource 5',
        color: '#c3b726'
      },
      {
        id: 'res6',
        name: 'Resource 6',
        color: '#c3b726'
      }
    ]
  }
]
const myTasks: MbscCalendarEvent[] = [
  {
    taskId: 1,
    title: 'Task 1',
    start: '08:00',
    end: '12:00',
    category: 'hw',
    color: '#0e9ea5'
  },
  {
    taskId: 2,
    title: 'Task 2',
    start: '08:00',
    end: '12:00',
    category: 'hw',
    color: '#0e9ea5'
  },
  {
    taskId: 3,
    title: 'Task 3',
    start: '08:00',
    end: '12:00',
    category: 'hw',
    color: '#0e9ea5'
  },
  {
    taskId: 4,
    title: 'Task 4',
    start: '08:00',
    end: '12:00',
    category: 'sw',
    color: '#c3b726'
  },
  {
    taskId: 5,
    title: 'Task 5',
    start: '08:00',
    end: '12:00',
    category: 'sw',
    color: '#c3b726'
  },
  {
    taskId: 6,
    title: 'Task 6',
    start: '08:00',
    end: '12:00',
    category: 'sw',
    color: '#c3b726'
  }
]

const myInvalids = ref<any>([])
const myColors = ref<any>([])
const toastMessage = ref<string>('')
const isToastOpen = ref<boolean>(false)
const dragElements = ref<any>([])

function myDefaultEvent(event: MbscCalendarEvent) {
  const res = event.resource

  if (res) {
    if (res === 'res1' || res === 'res2' || res === 'res3') {
      return {
        category: 'hw'
      }
    } else {
      return {
        category: 'sw'
      }
    }
  }
}

function handleEventDragStart(args: MbscEventDragEvent) {
  let event = args.event

  if (event) {
    event = event.original || event

    if (event.category === 'hw') {
      myInvalids.value = hwInvalids
      myColors.value = hwColors
    } else {
      myInvalids.value = swInvalids
      myColors.value = swColors
    }
  }
}

function handleEventDragEnd() {
  myInvalids.value = []
  myColors.value = []
}

function handleEventCreated() {
  toastMessage.value = 'Event created'
  isToastOpen.value = true
}

function handleEventUpdated() {
  toastMessage.value = 'Event moved'
  isToastOpen.value = true
}

function handleEventCreateFailed() {
  toastMessage.value = "Can't create event"
  isToastOpen.value = true
}

function handleEventUpdateFailed() {
  toastMessage.value = "Can't move event"
  isToastOpen.value = true
}

function handleToastClose() {
  isToastOpen.value = false
}
</script>

<template>
  <MbscPage>
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div class="mbsc-col-sm-3">
          <div class="mbsc-form-group-title">Available tasks</div>
          <div v-for="(task, i) in myTasks" :key="task.taskId">
            <div ref="dragElements" class="dynamically-color-and-invalidate-task">
              <div>
                {{ task.title }}
                <span class="dynamically-color-and-invalidate-task-type">{{ task.category }}</span>
              </div>
              <MbscDraggable :element="dragElements[i]" :dragData="task" />
            </div>
          </div>
        </div>
        <div class="mbsc-col-sm-9 dynamically-color-and-invalidate-calendar">
          <MbscEventcalendar
            :view="myView"
            :resources="myResources"
            :invalid="myInvalids"
            :colors="myColors"
            :dragToMove="true"
            :externalDrop="true"
            :extendDefaultEvent="myDefaultEvent"
            @event-drag-start="handleEventDragStart"
            @event-drag-end="handleEventDragEnd"
            @event-created="handleEventCreated"
            @event-updated="handleEventUpdated"
            @event-create-failed="handleEventCreateFailed"
            @event-update-failed="handleEventUpdateFailed"
          />
        </div>
      </div>
    </div>
  </MbscPage>
  <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="handleToastClose" />
</template>

<style>
.dynamically-color-and-invalidate-calendar {
  border-left: 1px solid #ccc;
}

.dynamically-color-and-invalidate-task {
  border: 2px solid #888;
  color: #666;
  padding: 10px;
  margin: 20px;
  border-radius: 8px;
  font-family:
    -apple-system,
    Segoe UI,
    Roboto,
    sans-serif;
}

.dynamically-color-and-invalidate-task-type {
  float: right;
  margin-left: 30px;
  text-transform: uppercase;
}

.dynamically-color-and-invalidate-calendar .mbsc-timeline-parent {
  height: 32px;
}
</style>
```

## Implementation notes

- Build a two-panel layout: a sidebar listing draggable task cards and a timeline Eventcalendar panel beside it.
- Wrap each task card in the sidebar with the `Draggable` component, passing the full task object (including its `category` field) as `dragData`.
- Enable `externalDrop: true` on the timeline so sidebar cards can be dropped onto it, and `dragToMove: true` so already-scheduled events can be repositioned.
- Define resources as two groups - HW Team and SW Team - each containing leaf resource rows. Set `eventCreation: false` on the group nodes so events are only placed on individual resource rows.
- Give each task a custom `category` property (`'hw'` or `'sw'`) that determines which team it belongs to.
- Pre-define four constant arrays: `hwInvalids` and `swInvalids` (blocking the incompatible team's resource rows) and `hwColors` and `swColors` (highlighting the compatible team's rows). Each entry uses `recurring: { repeat: 'daily' }` and a `resource` array to target whole teams.
- Use `onEventDragStart` to read the dragged event's `category`. Access it via `args.event.original || args.event` to handle both timeline events and externally dragged cards. Set the `invalid` state to the matching `*Invalids` array and the `colors` state to the matching `*Colors` array so incompatible rows are greyed out and valid rows are highlighted while dragging.
- Use `onEventDragEnd` to clear both `invalid` and `colors` back to empty arrays, restoring the default timeline appearance.
- Use `extendDefaultEvent` to assign the correct `category` to a newly dropped event based on the target resource ID, so events created by drop carry the right team association.
- Use `onEventCreated` and `onEventUpdated` to show a `Toast` confirming a successful drop or move. Use `onEventCreateFailed` and `onEventUpdateFailed` to show a `Toast` when a drop or move is blocked by the active `invalid` constraint.

## Run it in your project

1. If not signed up yet, or don't own a license, you can [start a free trial](https://mobiscroll.com/starttrial).

2. Clone the repository:

   ```
   git clone https://github.com/acidb/mobiscroll-demos-vue.git
   ```

3. If not installed yet, install the Mobiscroll CLI from NPM:

   ```
   npm install -g @mobiscroll/cli
   ```

4. Config Mobiscroll - open a terminal window in the root directory and run:

   ```
   mobiscroll config vue
   ```

5. Install dependencies - open a terminal window in the root directory and run:

   ```
   npm install
   ```

6. Run the project:

   ```
   npm run dev
   ```

On a trial, add `--trial` to the config command: `mobiscroll config vue --trial`.

Running a demo locally requires a Mobiscroll trial or a license; setup is per framework at https://mobiscroll.com/docs/vue/getting-started/installation

## Related demos

- Previous: [Meal Planner](https://demo.mobiscroll.com/vue/timeline/meal-planner.md) - Build a weekly meal planner with the Vue Timeline — meal types as color-coded resource rows, custom add/edit popover for creating or updating meals.
- Next: [Multi-Classroom Timetable](https://demo.mobiscroll.com/vue/timeline/multi-classroom-timetable.md) - Build a weekly classroom timetable with the Vue Timeline — rooms and days listed vertically with hourly slots from 8 AM to 8 PM, Monday to Friday.
