---
title: "Vue Agenda Bulk Actions Example | Mobiscroll"
component: "Agenda"
framework: "Vue"
library_version: "6.2.0"
url: "https://demo.mobiscroll.com/vue/agenda/event-bulk-actions-edit-delete-update"
---

# Bulk Actions - Vue Agenda demo

Select multiple events in the Vue agenda view with keyboard shortcuts or programmatically — bulk delete or update, or right-click for per-event edits. For Vue and Nuxt.

- [Live demo](https://demo.mobiscroll.com/vue/agenda/event-bulk-actions-edit-delete-update)
- [View SFC JS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue/tree/main/src/demos/eventcalendar/agenda/event-bulk-actions-edit-delete-update)
- [View SFC TS code on GitHub](https://github.com/acidb/mobiscroll-demos-vue-ts/tree/main/src/demos/eventcalendar/agenda/event-bulk-actions-edit-delete-update)
- This demo in other frameworks: [JavaScript](https://demo.mobiscroll.com/agenda/event-bulk-actions-edit-delete-update.md), [jQuery](https://demo.mobiscroll.com/jquery/agenda/event-bulk-actions-edit-delete-update.md), [Angular](https://demo.mobiscroll.com/angular/agenda/event-bulk-actions-edit-delete-update.md), [React](https://demo.mobiscroll.com/react/agenda/event-bulk-actions-edit-delete-update.md)

## Description

Multiple event selection can be turned on with the [`selectMultipleEvents`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-selectMultipleEvents) option. Selecting multiple events can be done through user interaction with `CTRL`/`SHIFT`/`CMD` + click or programmatically eg. click of a button or 'select all' checkbox.

Bulk operations like delete, update can be applied the selected events. Things like deleting with the `backspace` or `delete` buttons work out of the box but custom actions can be also applied. The selection can be easily retrieved and updated with the [`selectedEvents`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-selectedEvents) option and the [`selected-events-change`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onSelectedEventsChange) event.

Custom actions can be performed with external buttons or with context menu activated on right-click.

## What this demo shows

- Shows a monthly agenda view with events listed and grouped by date.
- **Header navigation** The month and year label in the top left opens date navigation. The previous and next arrows and the Today button on the right move between months and jump back to the current date.
- **Agenda list** The area below the header lists events for the selected month. Events in the agenda are grouped by date.
- **Sticky day headers** As the agenda scrolls vertically, each day stays visible with a sticky date header while its events appear underneath.
- **Bulk action panel** A side panel next to the agenda contains `Select all from view`, `Reset selection`, and `Update selected` actions.
- **Select all from view** Clicking this action selects all events currently visible and highlights thei titles in the week view and shows a bottom-centered toast message: `All events selected from the view`.
- **Reset selection** Clicking this action clears the current selection and shows a bottom-centered toast message: `Selection cleared`.
- **Update selected** Clicking this action changes the color of all selected events to orange and shows a bottom-centered toast message: `All selected event's color changed to orange`.
- **Currently selected** The side panel includes a `Currently selected` section that is empty by default and lists the titles of all selected events when one or more events are selected.
- **Multi-selection** Users can select multiple events with `CTRL`/`SHIFT`/`CMD` + click.
- **Context menu** Right-clicking an event opens a small popup with `Update` and `Delete` actions for that event.
- **Events** Events appear as agenda cards with a colored strip on the left and the event title next to it. Start and end times are stacked on the right side of timed events. All-day events show the all-day label on the right.
- **Event interaction** Hovering an event highlights it.
- **Event selection** Clicking an event selects and highlights it.actions for that event.

## Best for

- **Scheduling tools** Calendar-based scheduling interfaces where users need to review and manage many visible events in a month view.
- **Resource management** Resource planning workflows that require selecting and adjusting multiple bookings, assignments, or availability blocks together.
- **Operations teams** Internal tools for managing appointments, team shifts, project deadlines, or other time-based records in batches.
- **Mixed individual and batch actions** Use cases that need both multi-event actions and single-event actions from a contextual right-click menu.

## 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
- [`getEvents`](https://mobiscroll.com/docs/vue/eventcalendar/api#method-getEvents) - method
- [`data`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-data) - option
- [`view`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-view) - option
- [`selectMultipleEvents`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-selectMultipleEvents) - option
- [`selectedEvents`](https://mobiscroll.com/docs/vue/eventcalendar/api#opt-selectedEvents) - option
- [`event-delete`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventDelete) - event
- [`event-update`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventUpdate) - event
- [`event-right-click`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onEventRightClick) - event
- [`selected-events-change`](https://mobiscroll.com/docs/vue/eventcalendar/api#event-onSelectedEventsChange) - event
- [`display`](https://mobiscroll.com/docs/vue/select/api#opt-display) - option
- [`anchor`](https://mobiscroll.com/docs/vue/select/api#opt-anchor) - option
- [`data`](https://mobiscroll.com/docs/vue/select/api#opt-data) - option
- [`isOpen`](https://mobiscroll.com/docs/vue/select/api#opt-isOpen) - option
- [`showInput`](https://mobiscroll.com/docs/vue/select/api#opt-showInput) - option
- [`touchUi`](https://mobiscroll.com/docs/vue/select/api#opt-touchUi) - option
- [`change`](https://mobiscroll.com/docs/vue/select/api#event-onChange) - event
- [`close`](https://mobiscroll.com/docs/vue/select/api#event-onClose) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/confirm#event-onClose) - event
- [`close`](https://mobiscroll.com/docs/vue/notifications/toast#event-onClose) - event

## Code

### SFC JS

```vue
<script setup>
import {
  formatDate,
  getJson,
  MbscButton,
  MbscConfirm,
  MbscEventcalendar,
  MbscPage,
  MbscSelect,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

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

const calRef = ref()
const confirmMessage = ref()
const menuAction = ref()
const menuAnchor = ref()
const menuOpen = ref()
const myEvents = ref([])
const isConfirmOpen = ref()
const isToastOpen = ref()
const selectedEvent = ref()
const selectedEvents = ref([])
const toastMessage = ref()

const myView = { agenda: { type: 'month' } }

const menuData = [
  { text: 'Update', value: 'update' },
  { text: 'Delete', value: 'delete' }
]

function updateSelectedEvents() {
  const updatedEvents = [...myEvents.value]
  const events =
    selectedEvents.value.length === 0 && selectedEvent.value
      ? [selectedEvent.value]
      : selectedEvents.value
  for (const event of events) {
    const index = updatedEvents.findIndex((x) => x.id === event.id)
    // Handle recurring event occurrence
    if (event.recurring) {
      // Create a new event, with updated color and id
      const newEvent = {
        ...event,
        color: 'orange',
        id: event.id + '_' + formatDate('YYYY-MM-DD', event.start),
        recurring: undefined
      }
      // Update the original event with a recurring exception
      const updatedEvent = event.original
      const updatedExceptionDates = updatedEvent.recurringException || []
      updatedExceptionDates.push(event.start)
      updatedEvent.recurringException = updatedExceptionDates
      updatedEvents.splice(index, 1, updatedEvent)
      updatedEvents.push(newEvent)
    } else {
      // Update the event color
      const updatedEvent = { ...event, color: 'orange' }
      updatedEvents.splice(index, 1, updatedEvent)
    }
  }
  myEvents.value = updatedEvents
  selectedEvents.value = []
  toastMessage.value = "All selected event's color changed to orange"
  isToastOpen.value = true
}

function deleteSelectedEvents() {
  const events =
    selectedEvents.value.length === 0 && selectedEvent.value
      ? [selectedEvent.value]
      : selectedEvents.value
  confirmMessage.value = events.map((e) => e.title).join(', ')
  isConfirmOpen.value = true
}

function selectAllEvents() {
  selectedEvents.value = calRef.value.instance.getEvents()
  toastMessage.value = 'All events selected from view'
  isToastOpen.value = true
}

function resetSelection() {
  selectedEvents.value = []
  toastMessage.value = 'Selection cleared'
  isToastOpen.value = true
}

function handleConfirmClose(result) {
  if (result) {
    const events =
      selectedEvents.value.length === 0 && selectedEvent.value
        ? [selectedEvent.value]
        : selectedEvents.value
    const updatedEvents = [...myEvents.value]
    for (const event of events) {
      const index = updatedEvents.findIndex((x) => x.id === event.id)
      // Handle recurring event occurrence
      if (event.recurring) {
        // Update the original event with a recurring exception
        const updatedEvent = event.original
        const updatedExceptionDates = updatedEvent.recurringException || []
        updatedExceptionDates.push(event.start)
        updatedEvent.recurringException = updatedExceptionDates
        updatedEvents.splice(index, 1, updatedEvent)
      } else {
        // Remove the event
        updatedEvents.splice(index, 1)
      }
    }

    myEvents.value = updatedEvents
    selectedEvents.value = []
    toastMessage.value = 'Deleted'
    isToastOpen.value = true
  }
  isConfirmOpen.value = false
}

function handleEventUpdate(args) {
  if (args.isDelete) {
    if (!isConfirmOpen.value) {
      deleteSelectedEvents()
    }
    return false
  }
}

function handleEventDelete() {
  if (!isConfirmOpen.value) {
    deleteSelectedEvents()
    return false
  }
}

function handleEventRightClick(args) {
  args.domEvent.preventDefault()
  selectedEvent.value = args.event
  menuAnchor.value = args.domEvent.target
  menuOpen.value = true
}

function handleSelectedEventsChange(args) {
  selectedEvents.value = args.events
}

function handleMenuChange(args) {
  menuAction.value = args.value
  if (args.value === 'update') {
    updateSelectedEvents()
  } else if (args.value === 'delete') {
    deleteSelectedEvents()
  }
}

function handleMenuClose() {
  menuAction.value = ''
  menuOpen.value = false
}

function handleDeleteKey(ev) {
  if (
    !isConfirmOpen.value &&
    (ev.code === 'Delete' || ev.code === 'Backspace' || ev.keyCode === 8 || ev.keyCode === 46)
  ) {
    deleteSelectedEvents()
  }
}

onMounted(() => {
  getJson(
    'https://trial.mobiscroll.com/events/?vers=5',
    (events) => {
      myEvents.value = events
    },
    'jsonp'
  )
})
</script>

<template>
  <MbscPage cssClass="mds-bulk-actions" @keydown="handleDeleteKey">
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div class="mds-bulk-actions-calendar mbsc-col-sm-9 mbsc-push-sm-3">
          <MbscEventcalendar
            ref="calRef"
            :data="myEvents"
            :view="myView"
            :selectMultipleEvents="true"
            :selectedEvents="selectedEvents"
            @event-delete="handleEventDelete"
            @event-update="handleEventUpdate"
            @event-right-click="handleEventRightClick"
            @selected-events-change="handleSelectedEventsChange"
          />
          <MbscSelect
            display="anchored"
            :anchor="menuAnchor"
            :data="menuData"
            :isOpen="menuOpen"
            :showInput="false"
            :touchUi="false"
            :value="menuAction"
            @change="handleMenuChange"
            @close="handleMenuClose"
          />
        </div>
        <div class="mbsc-col-sm-3 mbsc-pull-sm-9 mbsc-flex-col">
          <div class="mbsc-button-group-block">
            <MbscButton @click="selectAllEvents">Select all from view</MbscButton>
            <MbscButton @click="resetSelection">Reset selection</MbscButton>
            <MbscButton @click="updateSelectedEvents">Update selected</MbscButton>
          </div>
          <div class="mbsc-form-group-title">Currently selected</div>
          <div class="mds-bulk-actions-event-list mbsc-padding mbsc-flex-1-1">
            <ul>
              <li v-for="event in selectedEvents" :key="event.id">{{ event.title }}</li>
            </ul>
          </div>
        </div>
      </div>
    </div>
    <MbscConfirm
      okText="Delete"
      title="Are you sure you want to delete the following events?"
      :isOpen="isConfirmOpen"
      :message="confirmMessage"
      @close="handleConfirmClose"
    />
    <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="isToastOpen = false" />
  </MbscPage>
</template>

<style>
.mds-bulk-actions,
.mds-bulk-actions .mbsc-grid,
.mds-bulk-actions .mbsc-row,
.mds-bulk-actions .mbsc-col-sm-9,
.mds-bulk-actions .mbsc-col-sm-3 {
  height: 100%;
}

.mds-bulk-actions-calendar {
  border-left: 1px solid #ccc;
}

.mds-bulk-actions-event-list {
  overflow: auto;
  padding-top: 0;
  padding-bottom: 0;
}
</style>
```

### SFC TS

```vue
<script setup lang="ts">
import {
  formatDate,
  getJson,
  MbscButton,
  MbscConfirm,
  MbscEventcalendar,
  MbscPage,
  MbscSelect,
  MbscToast,
  setOptions
} from '@mobiscroll/vue'
import type {
  MbscCalendarEvent,
  MbscEventcalendarView,
  MbscEventClickEvent,
  MbscEventUpdateEvent,
  MbscSelectChangeEvent,
  MbscSelectedEventsChangeEvent
} from '@mobiscroll/vue'
import { onMounted, ref } from 'vue'

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

const calRef = ref<typeof MbscEventcalendar>()
const confirmMessage = ref('')
const menuAction = ref('')
const menuAnchor = ref<HTMLElement>()
const menuOpen = ref(false)
const myEvents = ref<MbscCalendarEvent[]>([])
const isConfirmOpen = ref(false)
const isToastOpen = ref(false)
const selectedEvent = ref<MbscCalendarEvent>()
const selectedEvents = ref<MbscCalendarEvent[]>([])
const toastMessage = ref('')

const myView: MbscEventcalendarView = { agenda: { type: 'month' } }

const menuData = [
  { text: 'Update', value: 'update' },
  { text: 'Delete', value: 'delete' }
]

function updateSelectedEvents() {
  const updatedEvents = [...myEvents.value]
  const events =
    selectedEvents.value.length === 0 && selectedEvent.value
      ? [selectedEvent.value]
      : selectedEvents.value
  for (const event of events) {
    const index = updatedEvents.findIndex((x) => x.id === event.id)
    // Handle recurring event occurrence
    if (event.recurring) {
      // Create a new event, with updated color and id
      const newEvent = {
        ...event,
        color: 'orange',
        id: event.id + '_' + formatDate('YYYY-MM-DD', event.start as Date),
        recurring: undefined
      }
      // Update the original event with a recurring exception
      const updatedEvent = event.original!
      const updatedExceptionDates = (updatedEvent.recurringException || []) as Date[]
      updatedExceptionDates.push(event.start as Date)
      updatedEvent.recurringException = updatedExceptionDates
      updatedEvents.splice(index, 1, updatedEvent)
      updatedEvents.push(newEvent)
    } else {
      // Update the event color
      const updatedEvent = { ...event, color: 'orange' }
      updatedEvents.splice(index, 1, updatedEvent)
    }
  }
  myEvents.value = updatedEvents
  selectedEvents.value = []
  toastMessage.value = "All selected event's color changed to orange"
  isToastOpen.value = true
}

function deleteSelectedEvents() {
  const events =
    selectedEvents.value.length === 0 && selectedEvent.value
      ? [selectedEvent.value]
      : selectedEvents.value
  confirmMessage.value = events.map((e) => e.title).join(', ')
  isConfirmOpen.value = true
}

function selectAllEvents() {
  selectedEvents.value = calRef.value!.instance.getEvents()
  toastMessage.value = 'All events selected from view'
  isToastOpen.value = true
}

function resetSelection() {
  selectedEvents.value = []
  toastMessage.value = 'Selection cleared'
  isToastOpen.value = true
}

function handleConfirmClose(result: boolean) {
  if (result) {
    const events =
      selectedEvents.value.length === 0 && selectedEvent.value
        ? [selectedEvent.value]
        : selectedEvents.value
    const updatedEvents = [...myEvents.value]
    for (const event of events) {
      const index = updatedEvents.findIndex((x) => x.id === event.id)
      // Handle recurring event occurrence
      if (event.recurring) {
        // Update the original event with a recurring exception
        const updatedEvent = event.original!
        const updatedExceptionDates = (updatedEvent.recurringException || []) as Date[]
        updatedExceptionDates.push(event.start as Date)
        updatedEvent.recurringException = updatedExceptionDates
        updatedEvents.splice(index, 1, updatedEvent)
      } else {
        // Remove the event
        updatedEvents.splice(index, 1)
      }
    }

    myEvents.value = updatedEvents
    selectedEvents.value = []
    toastMessage.value = 'Deleted'
    isToastOpen.value = true
  }
  isConfirmOpen.value = false
}

function handleEventUpdate(args: MbscEventUpdateEvent) {
  if (args.isDelete) {
    if (!isConfirmOpen.value) {
      deleteSelectedEvents()
    }
    return false
  }
}

function handleEventDelete() {
  if (!isConfirmOpen.value) {
    deleteSelectedEvents()
    return false
  }
}

function handleEventRightClick(args: MbscEventClickEvent) {
  args.domEvent.preventDefault()
  selectedEvent.value = args.event
  menuAnchor.value = args.domEvent.target
  menuOpen.value = true
}

function handleSelectedEventsChange(args: MbscSelectedEventsChangeEvent) {
  selectedEvents.value = args.events
}

function handleMenuChange(args: MbscSelectChangeEvent) {
  menuAction.value = args.value
  if (args.value === 'update') {
    updateSelectedEvents()
  } else if (args.value === 'delete') {
    deleteSelectedEvents()
  }
}

function handleMenuClose() {
  menuAction.value = ''
  menuOpen.value = false
}

function handleDeleteKey(ev: KeyboardEvent) {
  if (
    !isConfirmOpen.value &&
    (ev.code === 'Delete' || ev.code === 'Backspace' || ev.keyCode === 8 || ev.keyCode === 46)
  ) {
    deleteSelectedEvents()
  }
}

onMounted(() => {
  getJson(
    'https://trial.mobiscroll.com/events/?vers=5',
    (events) => {
      myEvents.value = events
    },
    'jsonp'
  )
})
</script>

<template>
  <MbscPage cssClass="mds-bulk-actions" @keydown="handleDeleteKey">
    <div class="mbsc-grid mbsc-no-padding">
      <div class="mbsc-row">
        <div class="mds-bulk-actions-calendar mbsc-col-sm-9 mbsc-push-sm-3">
          <MbscEventcalendar
            ref="calRef"
            :data="myEvents"
            :view="myView"
            :selectMultipleEvents="true"
            :selectedEvents="selectedEvents"
            @event-delete="handleEventDelete"
            @event-update="handleEventUpdate"
            @event-right-click="handleEventRightClick"
            @selected-events-change="handleSelectedEventsChange"
          />
          <MbscSelect
            display="anchored"
            :anchor="menuAnchor"
            :data="menuData"
            :isOpen="menuOpen"
            :showInput="false"
            :touchUi="false"
            :value="menuAction"
            @change="handleMenuChange"
            @close="handleMenuClose"
          />
        </div>
        <div class="mbsc-col-sm-3 mbsc-pull-sm-9 mbsc-flex-col">
          <div class="mbsc-button-group-block">
            <MbscButton @click="selectAllEvents">Select all from view</MbscButton>
            <MbscButton @click="resetSelection">Reset selection</MbscButton>
            <MbscButton @click="updateSelectedEvents">Update selected</MbscButton>
          </div>
          <div class="mbsc-form-group-title">Currently selected</div>
          <div class="mds-bulk-actions-event-list mbsc-padding mbsc-flex-1-1">
            <ul>
              <li v-for="event in selectedEvents" :key="event.id">{{ event.title }}</li>
            </ul>
          </div>
        </div>
      </div>
    </div>
    <MbscConfirm
      okText="Delete"
      title="Are you sure you want to delete the following events?"
      :isOpen="isConfirmOpen"
      :message="confirmMessage"
      @close="handleConfirmClose"
    />
    <MbscToast :message="toastMessage" :isOpen="isToastOpen" @close="isToastOpen = false" />
  </MbscPage>
</template>

<style>
.mds-bulk-actions,
.mds-bulk-actions .mbsc-grid,
.mds-bulk-actions .mbsc-row,
.mds-bulk-actions .mbsc-col-sm-9,
.mds-bulk-actions .mbsc-col-sm-3 {
  height: 100%;
}

.mds-bulk-actions-calendar {
  border-left: 1px solid #ccc;
}

.mds-bulk-actions-event-list {
  overflow: auto;
  padding-top: 0;
  padding-bottom: 0;
}
</style>
```

## Implementation notes

- Use `view: { agenda: { type: 'month' } }`. Enable multi-event selection with `selectMultipleEvents: true`. Users can select via `CTRL`/`SHIFT`/`CMD` + click. Track selection with the `selectedEvents` option and `onSelectedEventsChange`.
- Use `inst.getEvents()` (no arguments) to retrieve all visible events in the current view range - use this for "Select all from view". React/Angular/Vue: read and update the selection by binding to the `selectedEvents` prop directly (two-way binding). JS/jQuery: call `inst.getSelectedEvents()` and `inst.setSelectedEvents()` to programmatically read and update the selection.
- Wire `onEventRightClick` to open a Mobiscroll `Select` component anchored to `args.domEvent.target`, populated with "Update" and "Delete" actions.
- Intercept keyboard Delete/Backspace via `onEventDelete` and `onEventUpdate` (checking `args.isDelete`) to route deletion through a confirm dialog before removing events.
- For recurring event occurrences, handle update and delete by adding the occurrence date to `event.original.recurringException` and, for updates, pushing a new non-recurring event with the modified properties. For the imperative API, call `inst.updateEvent()`, `inst.addEvent()`, and `inst.removeEvent()`.
- Load events from `https://trial.mobiscroll.com/events/?vers=5` via JSONP using `getJson(url, callback, 'jsonp')`. Angular: use `HttpClient.jsonp()`. For the imperative API, call `inst.setEvents(events)` in the callback.

## 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
