undo-redo-vuex
A Vuex plugin for module namespaced undo and redo functionality. This plugin takes inspiration from and extends the work of vuex-undo-redo.
Installation
yarn add undo-redo-vuex
Browser
Module
;
Usage
As a standard plugin for Vuex, undo-redo-vuex
can be used with the following setup:
How to use it in your store module
The scaffoldStore
helper function will bootstrap a vuex store to setup the state
, actions
and mutations
to work with the plugin.
; const state = list: /** * 'resetList' is a placeholder (initially the same as 'list') to * fast-forward 'list' during a 'reset()' */ resetList: // Define vuex state properties as normal;const actions = // Define vuex actions as normal;const mutations = /* * 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. */ { // Sets some state prop to the 'reset placeholder' value statelist = ...stateresetList; } { // Sets the 'reset placeholder' (see state.resetList) prop to the current state stateresetList = ...statelist; } // Define vuex mutations as normal; state actions mutations namespaced: true // NB: do not include this is non-namespaced stores;
emptyState
and resetState
mutations
Setting up The undo and redo functionality requires the emptyState
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. The clear()
action also commits emptyState
to re-initialize the store to its original state. The reset()
action additionally requires the resetState
mutation to be defined, allowing the state to 'fast-forward' to its point when reset()
was called before other mutations are 'replayed'.
Alternatively, the scaffoldState
, scaffoldActions
, and scaffoldMutations
helper functions can be individually required to bootstrap the vuex store. This will expose canUndo
and canRedo
as vuex state properties which can be used to enable/disable UI controls (e.g. undo/redo buttons).
; const state = list: resetList: // Define vuex state properties as normal;const actions = // Define vuex actions as normal;const mutations = { statelist = ...stateresetList || ; } { // Sets the 'reset placeholder' (see state.resetList) prop to the current state stateresetList = ...statelist; } // Define vuex mutations as normal; // Use the respective helper function to scaffold state, actions and mutations state: actions: mutations: namespaced: true // NB: do not include this is non-namespaced stores;
Setup the store plugin
- Namespaced modules
;; // NB: The following config is used for namespaced store modules.// Please see below for configuring a non-namespaced (basic) vuex store Vuex;
- Non-namespaced (basic) vuex store
;; Vuex;
canUndo
and canRedo
properties
Accessing - Vue SFC (.vue)
; const MyComponent = computed: ... ;
actionGroups
Undoing actions with In certain scenarios, undo/redo is required on store actions which may consist of one or more mutations. This feature is accessible by including a actionGroup
property in the payload
object of the associated vuex action. Please refer to test/test-action-group-undo.js
for more comprehensive scenarios.
- vuex module
const actions = { // An arbitrary label to identify the group of mutations to undo/redo const actionGroup = "myAction"; // All mutation payloads should contain the actionGroup property ; ; };
- 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 to PUT https://<your-rest-api>/v1/list
.
When undoing the commit("list/addItem", item)
mutation, an appropriate API call is required to DELETE
this item. The inverse also applies if the first API call is the DELETE
method (PUT
would have to be called when the commit("list/removeItem", item)
is undone).
View the unit test for this feature here.
This scenario can be implemented by providing the respective action names as the undoCallback
and redoCallback
fields in the mutation payload (NB: the payload object should be parameterized as an object literal):
const actions = saveItem: async commit item await axios; ; deleteItem: async commit item await axios; ; ; const mutations = // NB: payload parameters as object literal props { // adds the item to the list } { // removes the item from the list };
clear
action
Clearing the undo/redo stacks with the The internal done
and undone
stacks used to track mutations in the vuex store/modules can be cleared (i.e. emptied) with the clear
action. This action is scaffolded when using scaffoldActions(actions)
of scaffoldStore(store)
. This enhancement is described further in issue #7, with accompanying unit tests.
/** * Current done stack: [mutationA, mutation B] * Current undone stack: [mutationC] **/this$store; await this;/** * Current done stack: [] * Current undone stack: [] **/
reset
action
Resetting the current state with the Unlike the clear
action, reset
empties the done
and undone
stacks, but maintains the state of the store up to this particular point. This action is scaffolded when using scaffoldActions(actions)
of scaffoldStore(store)
. This enhancement is described further in issue #13, with accompanying unit tests.
/** * Current done stack: [mutationA, mutation B] * Current undone stack: [mutationC] **/this$store; await this;/** * Current done stack: [] * Current undone stack: [] * state: resetState (i.e. initial state + mutationA + mutationB) **/
done
and undone
mutations
Inspecting Some vuex powered applications may require knowledge of the done
and undone
stacks, e.g. to preserve undo/redo functionality between page loads. The following configuration exposes the stacks by scaffoling a undoRedoConfig
object in the store or module which uses the plugin:
;; // state, getters, actions & mutations ... // boolean flag to expose done and undone stacksconst exposeUndoRedoConfig = true; const storeConfig = ; // 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. */ const store = ...storeConfig plugins: // Pass boolean flag as an named option ;
To access the exposed done
and undone
stacks, e.g. in a component:
const done undone = this$storestate;
This enhancement is described further in issue #45, with accompanying unit tests.
Testing and test scenarios
Development tests are run using the Jest test runner. The ./tests/store
directory contains a basic Vuex store with a namespaced list
module.
The test blocks (each it()
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.
yarn test
tests/vue-undo-redo-demo
Demo Vue.js app in A demo Vue.js TODO application featuring this plugin is included in the tests/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 undone stack by popping the done stack and 'replays' all mutations in the done stack
function
store/plugins/undoRedo(options) ⇒ The Undo-Redo plugin module
Returns: function
- plugin - the plugin function which accepts the store parameter
Param | Type | Description |
---|---|---|
options | Object |
|
options.namespace | String |
The named vuex store module |
options.ignoreMutations | Array.<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 undone stack 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.reduce to chain and initial, empty promise
- store/plugins/undoRedo:getConfig(namespace) ⇒
Object
Piping async action calls sequentially using Array.prototype.reduce to chain and initial, empty promise
- store/plugins/undoRedo:setConfig(namespace, config)
Piping async action calls sequentially using Array.prototype.reduce to chain and initial, empty promise
store/plugins/undoRedo:pipeActions(actions)
Piping async action calls sequentially using Array.prototype.reduce to chain and initial, empty promise
Param | Type | Description |
---|---|---|
actions | Array.<Object> |
The array of objects containing the each action's name and payload |
Object
store/plugins/undoRedo:getConfig(namespace) ⇒ Piping async action calls sequentially using Array.prototype.reduce to chain and initial, empty promise
Returns: Object
- config - The object containing the undo/redo stacks of the store module
Param | Type | Description |
---|---|---|
namespace | String |
The name of the store module |
store/plugins/undoRedo:setConfig(namespace, config)
Piping async action calls sequentially using Array.prototype.reduce to chain and initial, empty promise
Param | Type | Description |
---|---|---|
namespace | String |
The name of the store module |
config | String |
The object containing the updated undo/redo stacks of the store module |