---
title: "Javascript Agenda Multi-Event Selection & Bulk Actions Example | Mobiscroll"
component: "Agenda"
framework: "JavaScript"
library_version: "6.1.5"
url: "https://demo.mobiscroll.com/javascript/agenda/event-bulk-actions-edit-delete-update"
---

# Multi-Event Selection & Bulk Actions - JavaScript Agenda demo

Select multiple events in a Javascript agenda view with keyboard shortcuts, or programmatically — bulk delete/update, or right-click for per-event edits. Plain JavaScript API.

- [Live demo](https://demo.mobiscroll.com/javascript/agenda/event-bulk-actions-edit-delete-update)
- [View code on GitHub](https://github.com/acidb/mobiscroll-demos-javascript/tree/main/src/demos/eventcalendar/agenda/event-bulk-actions-edit-delete-update)
- This demo in other frameworks: [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), [Vue](https://demo.mobiscroll.com/vue/agenda/event-bulk-actions-edit-delete-update.md)

## Description

Multiple event selection can be turned on with the [`selectMultipleEvents`](https://mobiscroll.com/docs/javascript/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 [`getSelectedEvents`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-getSelectedEvents) and [`setSelectedEvents`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-setSelectedEvents) method.

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/javascript/eventcalendar/api#opt-theme) - option
- [`themeVariant`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-themeVariant) - option
- [`setSelectedEvents`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-setSelectedEvents) - method
- [`getSelectedEvents`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-getSelectedEvents) - method
- [`updateEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-updateEvent) - method
- [`addEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-addEvent) - method
- [`removeEvent`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-removeEvent) - method
- [`selectMultipleEvents`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-selectMultipleEvents) - option
- [`view`](https://mobiscroll.com/docs/javascript/eventcalendar/api#opt-view) - option
- [`onEventUpdate`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventUpdate) - event
- [`onEventDelete`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventDelete) - event
- [`onEventRightClick`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onEventRightClick) - event
- [`setOptions`](https://mobiscroll.com/docs/javascript/select/api#method-setOptions) - method
- [`anchor`](https://mobiscroll.com/docs/javascript/select/api#opt-anchor) - option
- [`open`](https://mobiscroll.com/docs/javascript/select/api#method-open) - method
- [`onSelectedEventsChange`](https://mobiscroll.com/docs/javascript/eventcalendar/api#event-onSelectedEventsChange) - event
- [`buttons`](https://mobiscroll.com/docs/javascript/select/api#opt-buttons) - option
- [`data`](https://mobiscroll.com/docs/javascript/select/api#opt-data) - option
- [`display`](https://mobiscroll.com/docs/javascript/select/api#opt-display) - option
- [`touchUi`](https://mobiscroll.com/docs/javascript/select/api#opt-touchUi) - option
- [`onChange`](https://mobiscroll.com/docs/javascript/select/api#event-onChange) - event
- [`onClose`](https://mobiscroll.com/docs/javascript/select/api#event-onClose) - event
- [`setVal`](https://mobiscroll.com/docs/javascript/select/api#method-setVal) - method
- [`getEvents`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-getEvents) - method
- [`setEvents`](https://mobiscroll.com/docs/javascript/eventcalendar/api#method-setEvents) - method

## Code

### JS

```js
mobiscroll.setOptions({
  theme: 'ios',
  themeVariant: 'light'
});

var confirmOpen;
var selectedEvent;
var list = document.getElementById('demo-bulk-actions-event-list');
var formatDate = mobiscroll.formatDate;

function selectEvents(events) {
  list.innerHTML = events
    .map(function (e) {
      return '<li>' + e.title + '</li>';
    })
    .join('');
  calendar.setSelectedEvents(events);
}

function updateSelectedEvents() {
  var selectedEvents = calendar.getSelectedEvents();
  var events = selectedEvents.length === 0 && selectedEvent ? [selectedEvent] : selectedEvents;
  var newEvents = [];
  var updatedEvents = [];

  events.forEach(function (event) {
    var newEvent;
    var updatedEvent;
    if (event.recurring) {
      newEvent = Object.assign({}, event);
      newEvent.color = 'orange';
      newEvent.id += '_' + formatDate('YYYY-MM-DD', event.start);
      newEvent.recurring = undefined;
      newEvents.push(newEvent);
      updatedEvent = event.original;
      updatedEvent.recurringException = updatedEvent.recurringException || [];
      updatedEvent.recurringException.push(event.start);
      updatedEvents.push(updatedEvent);
    } else {
      updatedEvent = Object.assign({}, event);
      updatedEvent.color = 'orange';
      updatedEvents.push(updatedEvent);
    }
  });

  calendar.updateEvent(updatedEvents);
  calendar.addEvent(newEvents);
  selectEvents([]);

  mobiscroll.toast({
    message: "All selected event's color changed to orange",
  });
}

function deleteSelectedEvents() {
  var selectedEvents = calendar.getSelectedEvents();
  var events = selectedEvents.length === 0 && selectedEvent ? [selectedEvent] : selectedEvents;

  confirmOpen = true;

  mobiscroll.confirm({
    title: 'Are you sure you want to delete the following events?',
    message: events
      .map(function (e) {
        return e.title;
      })
      .join(', '),
    okText: 'Delete',
    callback: function (result) {
      if (result) {
        var updatedEvents = [];
        var deletedEvents = [];

        events.forEach(function (event) {
          if (event.recurring) {
            var updatedEvent = event.original;
            updatedEvent.recurringException = updatedEvent.recurringException || [];
            updatedEvent.recurringException.push(event.start);
            updatedEvents.push(updatedEvent);
          } else {
            deletedEvents.push(event);
          }
        });

        calendar.updateEvent(updatedEvents);
        calendar.removeEvent(deletedEvents);
        selectEvents([]);

        mobiscroll.toast({
          message: 'Deleted',
        });
      }
      confirmOpen = false;
    },
  });
}

var calendar = mobiscroll.eventcalendar('#demo-bulk-actions', {
  selectMultipleEvents: true,
  view: {
    agenda: { type: 'month' },
  },
  onEventUpdate: function (args) {
    if (args.isDelete) {
      if (!confirmOpen) {
        deleteSelectedEvents();
      }
      return false;
    }
  },
  onEventDelete: function () {
    if (!confirmOpen) {
      deleteSelectedEvents();
      return false;
    }
  },
  onEventRightClick: function (args) {
    args.domEvent.preventDefault();
    selectedEvent = args.event;
    menu.setOptions({
      anchor: args.domEvent.target,
    });
    setTimeout(function () {
      menu.open();
    });
  },
  onSelectedEventsChange: function (args) {
    selectEvents(args.events);
  },
});

var menu = mobiscroll.select('#demo-bulk-actions-menu', {
  buttons: [],
  data: [
    { value: 'update', text: 'Update' },
    { value: 'delete', text: 'Delete' },
  ],
  display: 'anchored',
  touchUi: false,
  onChange: function (args) {
    if (args.value === 'update') {
      updateSelectedEvents();
    } else if (args.value === 'delete') {
      deleteSelectedEvents();
    }
  },
  onClose: function () {
    // Clear selection
    menu.setVal(null);
  },
});

document.getElementById('demo-bulk-actions-select-all').addEventListener('click', function () {
  selectEvents(calendar.getEvents());
  mobiscroll.toast({
    message: 'All events selected from view',
  });
});

document.getElementById('demo-bulk-actions-reset').addEventListener('click', function () {
  selectEvents([]);
  mobiscroll.toast({
    message: 'Selection cleared',
  });
});

document.getElementById('demo-bulk-actions-update').addEventListener('click', function () {
  updateSelectedEvents();
});

document.getElementById('demo-bulk-actions-page').addEventListener('keydown', function (ev) {
  if (!confirmOpen && (ev.code === 'Delete' || ev.code === 'Backspace' || ev.keyCode === 8 || ev.keyCode === 46)) {
    deleteSelectedEvents();
  }
});

mobiscroll.getJson(
  'https://trial.mobiscroll.com/events/?vers=5',
  function (events) {
    calendar.setEvents(events);
  },
  'jsonp',
);
```

### HTML

```html
<div id="demo-bulk-actions-page" mbsc-page class="mds-bulk-actions">
  <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">
        <div id="demo-bulk-actions"></div>
        <div id="demo-bulk-actions-menu" class="mds-select"></div>
      </div>
      <div class="mbsc-col-sm-3 mbsc-pull-sm-9 mbsc-flex-col">
        <div class="mbsc-button-group-block">
          <button id="demo-bulk-actions-select-all" mbsc-button>Select all from view</button>
          <button id="demo-bulk-actions-reset" mbsc-button>Reset selection</button>
          <button id="demo-bulk-actions-update" mbsc-button>Update selected</button>
        </div>
        <div class="mbsc-form-group-title">Currently selected</div>
        <div class="mds-bulk-actions-event-list mbsc-padding mbsc-flex-1-1">
          <ul id="demo-bulk-actions-event-list">
          </ul>
        </div>
      </div>
    </div>
  </div>
</div>
```

### CSS

```css
  .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;
  }
```

## 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-javascript.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 javascript
   ```

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 javascript --trial`.

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