Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A Vuex plugin for module namespaced undo and redo functionality

License

NotificationsYou must be signed in to change notification settings

factorial-io/undo-redo-vuex

Repository files navigation

A Vuex plugin for module namespaced undo and redo functionality. This plugin takes inspiration from and extends the work ofvuex-undo-redo.

Installation

yarnaddundo-redo-vuex

Browser

<scripttype="text/javascript"src="node_modules/undo-redo-vuex/dist/undo-redo-vuex.min.js"></script>

Module

importundoRedofrom"undo-redo-vuex";

Usage

As a standardplugin for Vuex,undo-redo-vuex can be used with the following setup:

How to use it in your store module

ThescaffoldStore helper function will bootstrap a vuex store to setup thestate,actions andmutations to work with the plugin.

import{scaffoldStore}from"undo-redo-vuex";conststate={list:[],/**   * 'resetList' is a placeholder (initially the same as 'list') to   * fast-forward 'list' during a 'reset()'   */resetList:[],// Define vuex state properties as normal};constactions={// Define vuex actions as normal};constmutations={/*   * NB: The emptyState mutation HAS to be implemented.   * This mutation resets the state props to a "base" state,   * on top of which subsequent mutations are "replayed"   * whenever undo/redo is dispatched.   */emptyState:state=>{// Sets some state prop to the 'reset placeholder' valuestate.list=[...state.resetList];},resetState:state=>{// Sets the 'reset placeholder' (see state.resetList) prop to the current statestate.resetList=[...state.list];},// Define vuex mutations as normal};exportdefaultscaffoldStore({  state,  actions,  mutations,namespaced:true,// NB: do not include this is non-namespaced stores});

Setting upemptyState andresetState mutations

The undo and redo functionality requires theemptyState mutation (in the example above) to be defined by the developer. This mutation (which is not tracked in the undo and redo stacks) allows state to be 'replayed' in the chronological order mutations are executed. Theclear() action also commitsemptyState to re-initialize the store to its original state. Thereset() action additionally requires theresetState mutation to be defined, allowing the state to 'fast-forward' to its point whenreset() was called before other mutations are 'replayed'.

Alternatively, thescaffoldState,scaffoldActions, andscaffoldMutations helper functions can be individually required to bootstrap the vuex store. This will exposecanUndo andcanRedo as vuex state properties which can be used to enable/disable UI controls (e.g. undo/redo buttons).

import{scaffoldState,scaffoldActions,scaffoldMutations,}from"undo-redo-vuex";conststate={list:[],resetList:[],// Define vuex state properties as normal};constactions={// Define vuex actions as normal};constmutations={emptyState:state=>{state.list=[...(state.resetList||[])];},resetState:state=>{// Sets the 'reset placeholder' (see state.resetList) prop to the current statestate.resetList=[...state.list];},// Define vuex mutations as normal};exportdefault{// Use the respective helper function to scaffold state, actions and mutationsstate:scaffoldState(state),actions:scaffoldActions(actions),mutations:scaffoldMutations(mutations),namespaced:true,// NB: do not include this is non-namespaced stores};

Setup the store plugin

  • Namespaced modules
importVuexfrom"vuex";importundoRedofrom"undo-redo-vuex";// NB: The following config is used for namespaced store modules.// Please see below for configuring a non-namespaced (basic) vuex storeexportdefaultnewVuex.Store({plugins:[undoRedo({// The config object for each store module is defined in the 'paths' arraypaths:[{namespace:"list",// Any mutations that you want the undo/redo mechanism to ignoreignoreMutations:["addShadow","removeShadow"],},],}),],/*   * For non-namespaced stores:   * state,   * actions,   * mutations,   */// Modules for namespaced stores:modules:{    list,},});
  • Non-namespaced (basic) vuex store
importVuexfrom"vuex";importundoRedofrom"undo-redo-vuex";exportdefaultnewVuex.Store({  state,  getters,  actions,  mutations,plugins:[undoRedo({// NB: Include 'ignoreMutations' at root level, and do not provide the list of 'paths'.ignoreMutations:["addShadow","removeShadow"],}),],});

AccessingcanUndo andcanRedo properties

  • Vue SFC (.vue)
import{mapState}from"vuex";constMyComponent={computed:{    ...mapState({undoButtonEnabled:"canUndo",redoButtonEnabled:"canRedo",}),},};

Undoing actions withactionGroups

In certain scenarios, undo/redo is required on store actions which may consist of one or more mutations. This feature is accessible by including aactionGroup property in thepayload object of the associated vuex action. Please refer totest/test-action-group-undo.js for more comprehensive scenarios.

  • vuex module
constactions={myAction({ commit},payload){// An arbitrary label to identify the group of mutations to undo/redoconstactionGroup="myAction";// All mutation payloads should contain the actionGroup propertycommit("mutationA",{      ...payload,      actionGroup,});commit("mutationB",{someProp:true,      actionGroup,});},};
  • Undo/redo stack illustration
// After dispatching 'myAction' oncedone=[{type:"mutationA",payload:{ ...payload,actionGroup:"myAction"}},{type:"mutationB",payload:{someProp:true,actionGroup:"myAction"}},];undone=[];// After dispatching 'undo'done=[];undone=[{type:"mutationA",payload:{ ...payload,actionGroup:"myAction"}},{type:"mutationB",payload:{someProp:true,actionGroup:"myAction"}},];

Working with undo/redo on mutations produced by side effects (i.e. API/database calls)

In Vue.js apps, Vuex mutations are often committed inside actions that contain asynchronous calls to an API/database service:For instance,commit("list/addItem", item) could be called after an axios request toPUT https://<your-rest-api>/v1/list.When undoing thecommit("list/addItem", item) mutation, an appropriate API call is required toDELETE this item. The inverse also applies if the first API call is theDELETE method (PUT would have to be called when thecommit("list/removeItem", item) is undone).View the unit test for this featurehere.

This scenario can be implemented by providing the respective action names as theundoCallback andredoCallback fields in the mutation payload (NB: the payload object should be parameterized as an object literal):

constactions={saveItem:async(({ commit}),item)=>{awaitaxios.put(PUT_ITEM,item);commit("addItem",{      item,// dispatch("deleteItem", { item }) on undo()undoCallback:"deleteItem",// dispatch("saveItem", { item }) on redo()redoCallback:"saveItem"});},deleteItem:async(({ commit}),item)=>{awaitaxios.delete(DELETE_ITEM,item);commit("removeItem",{      item,// dispatch("saveItem", { item }) on undo()undoCallback:"saveItem",// dispatch("deleteItem", { item }) on redo()redoCallback:"deleteItem"});}};constmutations={// NB: payload parameters as object literal propsaddItem:(state,{ item})=>{// adds the item to the list},removeItem:(state,{ item})=>{// removes the item from the list}};

Clearing the undo/redo stacks with theclear action

The internaldone andundone stacks used to track mutations in the vuex store/modules can be cleared (i.e. emptied) with theclear action. This action is scaffolded when usingscaffoldActions(actions) ofscaffoldStore(store). This enhancement is described further inissue #7, with accompanyingunit tests.

/** * Current done stack: [mutationA, mutation B] * Current undone stack: [mutationC] **/this.$store.dispatch("list/clear");awaitthis.$nextTick();/** * Current done stack: [] * Current undone stack: [] **/

Resetting the current state with thereset action

Unlike theclear action,reset empties thedone andundone stacks, but maintains the state of the store up to this particular point. This action is scaffolded when usingscaffoldActions(actions) ofscaffoldStore(store). This enhancement is described further inissue #13, with accompanyingunit tests.

/** * Current done stack: [mutationA, mutation B] * Current undone stack: [mutationC] **/this.$store.dispatch("list/reset");awaitthis.$nextTick();/** * Current done stack: [] * Current undone stack: [] * state: resetState (i.e. initial state + mutationA + mutationB) **/

Inspectingdone andundone mutations

Some vuex powered applications may require knowledge of thedone andundone stacks, e.g. to preserve undo/redo functionality between page loads. The following configuration exposes the stacks by scaffoling aundoRedoConfig object in the store or module which uses the plugin:

importVuexfrom"vuex";importundoRedo,{scaffoldStore}from"undo-redo-vuex";// state, getters, actions & mutations ...// boolean flag to expose done and undone stacksconstexposeUndoRedoConfig=true;conststoreConfig=scaffoldStore({  state,  actions,  mutations},exposeUndoRedoConfig);// boolean flag as the second optional param/** * NB: When configuring state, actions or mutations with scaffoldState, * scaffoldActions or scaffoldMutations, the exposeUndoRedoConfig = true * flag should be passed as the second param. */conststore=newVuex.Store({  ...storeConfig,plugins:[// Pass boolean flag as an named optionundoRedo({ exposeUndoRedoConfig})]});

To access the exposeddone andundone stacks, e.g. in a component:

const{ done, undone}=this.$store.state;

This enhancement is described further inissue #45, with accompanyingunit tests.

Testing and test scenarios

Development tests are run using theJest test runner. The./tests/store directory contains a basic Vuex store with a namespacedlist module.

The test blocks (eachit() declaration) in./tests/unit directory are grouped to mimic certain user interactions with the store, making it possible to track the change in state over time.

yarntest

Demo Vue.js app intests/vue-undo-redo-demo

A demo Vue.js TODO application featuring this plugin is included in thetests/vue-undo-redo-demo directory. Example test specs for Vue.js unit testing with Jest, and E2E testing with Cypress are also provided.

API documentation and reference

Public API

store/plugins/undoRedo(options)function

The Undo-Redo plugin module

store/plugins/undoRedo:redo()

The Redo function - commits the latest undone mutation to the store,and pushes it to the done stack

store/plugins/undoRedo:undo()

The Undo function - pushes the latest done mutation to the top of the undonestack by popping the done stack and 'replays' all mutations in the done stack

store/plugins/undoRedo(options) ⇒function

The Undo-Redo plugin module

Returns:function - plugin - the plugin function which accepts the store parameter

ParamTypeDescription
optionsObject
options.namespaceStringThe named vuex store module
options.ignoreMutationsArray.<String>The list of store mutations (belonging to the module) to be ignored

store/plugins/undoRedo:redo()

The Redo function - commits the latest undone mutation to the store,and pushes it to the done stack

store/plugins/undoRedo:undo()

The Undo function - pushes the latest done mutation to the top of the undonestack by popping the done stack and 'replays' all mutations in the done stack

Internal functions (reference only)

store/plugins/undoRedo:pipeActions(actions)

Piping async action calls sequentially using Array.prototype.reduceto chain and initial, empty promise

store/plugins/undoRedo:getConfig(namespace)Object

Piping async action calls sequentially using Array.prototype.reduceto chain and initial, empty promise

store/plugins/undoRedo:setConfig(namespace, config)

Piping async action calls sequentially using Array.prototype.reduceto chain and initial, empty promise

store/plugins/undoRedo:pipeActions(actions)

Piping async action calls sequentially using Array.prototype.reduceto chain and initial, empty promise

ParamTypeDescription
actionsArray.<Object>The array of objects containing the each action's name and payload

store/plugins/undoRedo:getConfig(namespace) ⇒Object

Piping async action calls sequentially using Array.prototype.reduceto chain and initial, empty promise

Returns:Object - config - The object containing the undo/redo stacks of the store module

ParamTypeDescription
namespaceStringThe name of the store module

store/plugins/undoRedo:setConfig(namespace, config)

Piping async action calls sequentially using Array.prototype.reduceto chain and initial, empty promise

ParamTypeDescription
namespaceStringThe name of the store module
configStringThe object containing the updated undo/redo stacks of the store module

[8]ページ先頭

©2009-2025 Movatter.jp