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

Recommend

Record a pitfall of MySQL update statement update

background Recently, I executed a DML statement d...

Web Standard Application: Redesign of Tencent QQ Home Page

Tencent QQ’s homepage has been redesigned, and Web...

How to use Docker Compose to implement nginx load balancing

Implement Nginx load balancing based on Docker ne...

A brief discussion of several browser compatibility issues encountered

background Solving browser compatibility issues i...

URL representation in HTML web pages

In HTML, common URLs are represented in a variety ...

Interviewer asked how to achieve a fixed aspect ratio in CSS

You may not have had any relevant needs for this ...

Tutorial on installing MYSQL8.X on Centos

MySQL installation (4, 5, 6 can be omitted) State...

Detailed explanation of global parameter persistence in MySQL 8 new features

Table of contents Preface Global parameter persis...

CSS inheritance method

Given a div with the following background image: ...

jQuery implements nested tab function

This article example shares the specific code of ...

Sample code for CSS dynamic loading bar effect

Using the knowledge of CSS variables, I will dire...

TABLE tags (TAGS) detailed introduction

Basic syntax of the table <table>...</tab...

Javascript Basics: Detailed Explanation of Operators and Flow Control

Table of contents 1. Operator 1.1 Arithmetic oper...