Sample code for implementing history in vuex

Sample code for implementing history in vuex

I have recently been developing a visual operation platform, which involves the ability to undo or redo user operations. I searched for some solutions online and perfected the solutions I envisioned.

Key points of historical record requirements

  • Can be stored in localStorage
  • Multiple undo and redo functions are possible
  • Click an item in the list to go back or forward the history to the specified position

Seemingly simple requirements, but errors in infrastructure design will lead to more workload in the future. Therefore, combining the requirements of the above two points, it is found that the basic idea of ​​​​vuex is very suitable for meeting this requirement, and the same is true for redux.

Implementation ideas

This project uses typescript to enhance the rigor of the code and facilitate future maintenance. Let's take a look at the idea.

1. First define the data structure of historical records

interface HistoryItem {
  timestrap: number; // record timestamp name: string; // record name redo: string; // redo mutation
  undo: string; // Undo Mutation
  redoParams: any[]; // Redo Mutation submission parameters undoParams: any[]; // Undo Mutation submission parameters }

interface HistoryStatus {
  historys: HistoryItem[]; // record history array_currentHistory: number; // current node index}

2. Write the History state module

Write a vuex module for basic operation history state, create recorded mutations, and redo and undo actions
A record contains the execution of redo operation and cancellation of undo operation for this step. So when the user clicks one of the items in the list, it should loop back to the previous item of the current item to undo, or loop redo to the current item.

Therefore, it is necessary to add an empty record to make it easier for users to click on the empty record to undo the initial operation.

Use vuex-module-decorators to write more maintainable code

import { VuexModule, Module, Mutation, Action } from "vuex-module-decorators";

@Module({ namespaced: true })
export class HistoryModule extends VuexModule<HistoryStatus> implements HistoryStatus {
  /** 
   * The reason for initializing an empty record is mainly to facilitate list operations:
   * When the user clicks on the earliest record, the first step of the user's operation can be undone normally**/
  public histories:HistoryItem[] = [
    {
      name: `Open`,
      timestrap: Date.now(),
      redo: "",
      redoParams: [],
      undo: "",
      undoParams: [],
    },
  ];
  public _currentHistory: number = 0;

  // getter
  get current(){
    return this._currentHistory;
  }

  // getter
  get historyList(): HistoryItem[] {
    return this.historys || [];
  }

  // Create history @Mutation
  public CREATE_HISTORY(payload: HistoryItem) {
    if (this._currentHistory < this.historys.length - 1) {
      this.historys = this.historys.slice(0, this._currentHistory);
    }
    // Due to the deep and shallow copy problem of js, the data needs to be deeply copied when creating // I want to try lodash's clone function, but I find that JSON.stringify's clone method should be faster. After all, our data does not exist in the function // I will not change it here, mainly to express the idea this.historys.push(_.cloneDeep(payload));
    this._currentHistory = this.historys.length - 1;
  }

  @Mutation
  public SET_CURRENT_HISTORY(index: number) {
    this._currentHistory = index < 0 ? 0 : index;
  }

  // Redo @Action
  public RedoHistory(times: number = 1) {
    let { state, commit } = this.context;
    let history: HistoryItem[] = state.historys;
    let current: number = state._currentHistory;
    if (current + times >= history.length) return;
    while (times > 0) {
      current++;
      let history = histories[current];
      if (history) {
        commit(history.redo, ...history.redoParams, { root: true });
      }
      times--;
    }
    commit("SET_CURRENT_HISTORY", current);
  }

  // Undo @Action
  public UndoHistory(times: number = 1) {
    let { state, commit } = this.context;
    let history: HistoryItem[] = state.historys;
    let current: number = state._currentHistory;
    if (current - times < 0) return;
    while (times > 0) {
      let history = histories[current];
      if (history) {
        commit(history.undo, ...history.undoParams, { root: true });
      }
      times--;
      current--;
    }
    commit("SET_CURRENT_HISTORY", current);
  }
}

3. Write undo and redo functions

After completing the above two steps, we can write various operations

Write mutations for basic operations on data

@Mutation
public CREATE_PAGE(payload: { page: PageItem; index: number }) {
  this.pages.splice(payload.index, 0, _.cloneDeep(payload.page));
  this._currentPage = this.pages.length - 1;
}

@Mutation
public REMOVE_PAGE(id: string) {
  let index = this.pages.findIndex((p) => p.id == id);
  index > -1 && this.pages.splice(index, 1);
  if (this._currentPage == index) {
    this._currentPage = this.pages.length > 0 ? 0 : -1;
  }
}

Encapsulate basic operations into actions with save->record->execute as required

//Package create page function @Action
public CreatePage(type: "page" | "dialog") {
  let { state, commit } = this.context;
  
  //Record and save the page to be created let id = _.uniqueId(type) + Date.now();
  let pageName = pageType[type];
  let page: PageItem = {
    id,
    name: `${pageName}${state.pages.length + 1}`,
    type,
    layers: [],
    style: { width: 720, height: 1280 },
  };

  //Create history let history: HistoryItem = {
    name: `Create ${pageName}`,
    timestrap: Date.now(),
    redo: "Page/CREATE_PAGE",
    redoParams: [{ index: state.pages.length - 1, page }],
    undo: "Page/REMOVE_PAGE",
    undoParams: [id],
  };
  // Save and record this history commit("Histroy/CREATE_HISTORY", history, { root: true });

  commit(history.redo, ...history.redoParams, { root: true });
}

@Action
public RemovePage(id: string) {
  // Record and save the on-site status let index = this.pages.findIndex((p) => p.id == id);
  if (index < 0) return;
  let page: PageItem = this.context.state.pages[index];

  //Create history let history: HistoryItem = {
    name: `Delete ${page.name}`,
    timestrap: Date.now(),
    redo: "Page/REMOVE_PAGE",
    redoParams: [id],
    undo: "Page/CREATE_PAGE",
    undoParams: [{ page, index }],
  };

  // Save this history record this.context.commit("Histroy/CREATE_HISTORY", history, { root: true });
  this.context.commit(history.redo, ...history.redoParams, { root: true });
}

The above, the undo and redo functions are basically completed.

4. Use

1. Now we only need to use the encapsulated `Action` when creating or deleting pages

  private create(type: "page" | "dialog") {
    this.$store.dispatch("Page/CreatePage", type);
  }

  private remove(id: number) {
    this.$store.dispatch("Page/RemovePage", id);
  }

2. Configure global hotkeys

typescript App.vue

private mounted() {
    let self = this;
    hotkeys("ctrl+z", function (event, handler) {
      self.$store.dispatch("History/UndoHistory");
    });
    hotkeys("ctrl+y", function (event, handler) {
      self.$store.dispatch("History/RedoHistory");
    });
  }

Effect

This concludes this article about the sample code for implementing history records in vuex. For more relevant vuex history records, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • In vue, it is forbidden to go back to the previous step, and the route does not store history
  • Vue implements the new search history function in the input box

<<:  Summary of the installation process of MySql 8.0.11 and the problems encountered when linking with Navicat

>>:  How to run Spring Boot application in Docker

Blog    

Recommend

Some wonderful uses of URL objects in JavaScript

Table of contents Preface Parsing parameters Modi...

JavaScript - Using slots in Vue: slot

Table of contents Using slots in Vue: slot Scoped...

Introduction to Sublime Text 2, a web front-end tool

Sublime Text 2 is a lightweight, simple, efficien...

How to use http and WebSocket in CocosCreator

Table of contents 1. HttpGET 2. HTTP POST WebSock...

Repair solution for inconsistent MySQL GTID master and slave

Table of contents Solution 1: Rebuild Replicas Pr...

HTML line spacing setting methods and problems

To set the line spacing of <p></p>, us...

The specific use and difference between attribute and property in Vue

Table of contents As attribute and property value...

Box-shadow and drop-shadow to achieve irregular projection example code

When we want to add a shadow to a rectangle or ot...

MySQL online deadlock analysis practice

Preface I believe that everyone has had a simple ...

Detailed explanation of JSONObject usage

JSONObject is just a data structure, which can be...

Specific example of MySQL multi-table query

1. Use the SELECT clause to query multiple tables...