Overview

  • Extend the Vue Scheduler component with unlimited undo/redo functionality.

  • The Undo and Redo buttons are only enabled if the action is allowed.

  • See the history of all actions and the current position.

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.

Introduction to the Vue Scheduler component

For an introduction to using the Vue Scheduler component please see the tutorial:

This undo/redo tutorial assumes that you are familiar with the Vue Scheduler component basics.

How to implement undo/redo for the Vue Scheduler?

vue scheduler undo redo implementation

The attached projects includes an UndoService class which contains the implementation of the undo/redo logic.

  • You need to feed it with the initial event set and record all actions.

  • The service maintains a history of all actions performed.

  • The service can return information about the last action so you can perform the “undo” action.

  • It also maintains the current position in the history and if available, you can get information about the next action and perform “redo”.

Each history item stores the type of operation (add/edit/remove) and the event state before (previous) and after (current) the action. It also stores a text comment that contains details about the action.

How to record drag and drop actions?

vue scheduler record drag and drop actions for undo redo

In order to perform the undo/redo, you need to use the UndoService class and record all changes performed by the user.

First, you need to create an instance of the UndoService:

data: function () {
  return {
    service: new UndoService(),
    // ...
  }
},

Before you start recording the user drag and drop actions using the UndoService, you need to save the initial state of the event data set using the initialize() method:

export default {
  methods: {
    loadEvents() {
      const events = [
        {id: 1, start: "2021-08-03T00:00:00", end: "2021-08-08T00:00:00", text: "Event 1", resource: "R4"},
        {id: 2, start: "2021-08-04T00:00:00", end: "2021-08-10T00:00:00", text: "Event 2", resource: "R2"}
      ];
      this.config.events = events;
      this.service.initialize(events);
    },
    // ...
  },
  mounted: function () {
    // ...
    this.loadEvents();
  }
}

Now you can record the actions. You need to record every change made to the events, either using the UI or using the direct API.

To record changes made by the user using the Scheduler UI, you can use the following events handlers:

Here is the logic that uses the drag and drop event handlers to record the actions:

export default {
  name: 'Scheduler',
  data: function () {
    return {
      service: new UndoService(),
      config: {
        onTimeRangeSelected: async (args) => {
          const dp = args.control;
          const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
          dp.clearSelection();
          if (modal.canceled) {
            return;
          }

          const data = {
            start: args.start,
            end: args.end,
            id: DayPilot.guid(),
            resource: args.resource,
            text: modal.result
          };
          dp.events.add(data);
          this.service.add(data, "New event created");
        },
        onEventMoved: (args) => {
          this.service.update(args.e.data, "Event moved");
          args.control.message("Event moved: " + args.e.text());
        },
        onEventResized: (args) => {
          this.service.update(args.e.data, "Event resized");
          args.control.message("Event resized: " + args.e.text());
        },
        eventDeleteHandling: "Update",
        onEventDeleted: (args) => {
          this.service.remove(args.e.data, "Event removed");
          args.control.message("Event deleted: " + args.e.text());
        },
        // ...
      },
    }
  },
  
  // ...

}

The UndoService provides the following methods for recording the changes:

  • add()

  • update()

  • remove()

These methods have two parameters:

  • item - the changed object (with the exception of remove(), it is the new state of the object); the object has to be serializable using JSON.stringify()

  • text - a text description of the change (optional)

The UndoService saves the change in the history, keeping the old and new state. It also advances the current position in the history.

How to add an Undo button to the Vue Scheduler?

vue scheduler how to add undo button

The “Undo” button state is set to disabled if the UndoService doesn’t allow performing the “undo” action. You can read the state using canUndo property.

<template>
  <button v-on:click="undo" :disabled="!service.canUndo">Undo</button>
  <DayPilotScheduler id="dp" :config="config" ref="scheduler"/>
</template>

Once the user clicks the “Undo” button you need to get the previous operation from the undo history using undo() method and revert the operation:

  • If the action was "add" you need to remove the event using events.remove() method.

  • If the action was "update" you need to restore the previous event state that is stored in the previous property of the history item.

  • If the action was "remove" you need to add the event back to the Vue Scheduler using events.add() method. The original event is stored in the previous property of the history item.

The undo service automatically updates the history and the current position within the history.

undo() {
  const item = this.service.undo();

  switch (item.type) {
    case "add":
      // added, need to delete now
      this.scheduler.events.remove(item.id);
      break;
    case "remove":
      // removed, need to add now
      this.scheduler.events.add(item.previous);
      break;
    case "update":
      // updated
      this.scheduler.events.update(item.previous);
      break;
  }
},

How to add a Redo button to the Vue Scheduler?

vue scheduler how to add redo button

The Redo button is only enabled if the history has at least item and the current position isn’t after the last item.

<template>
  <!-- ... -->
  <button v-on:click="redo" :disabled="!service.canRedo">Redo</button>
  <DayPilotScheduler id="dp" :config="config" ref="scheduler"/>
</template>

After clicking the “Redo” button, you need to replay the action returned by the redo() method of the UndoService:

redo() {
  let item = this.service.redo();

  switch (item.type) {
    case "add":
      // added, need to re-add
      this.scheduler.events.add(item.current);
      break;
    case "remove":
      // removed, need to remove again
      this.scheduler.events.remove(item.id);
      break;
    case "update":
      // updated, use the new version
      this.scheduler.events.update(item.current);
      break;
  }
}