Javascript History Operations Manager Undo/Redo  

by ne on 2021-09-29 under Javascript

This article will demonstrate a way of creating a javascript utility which can help in maintaining history operations.

For instance,f you want to undo, redo a series of operations, then you can use the following utility. A demonstration of how every operation takes on, is just after the code below.

 


var historyManager = function (undoCallBack, redoCallback, maxStackLimit) {
        var undoCommands = [], redoCommands = [], limitForUndoRedo = 20;        

        //set the max items for undo redo list
        if (maxStackLimit) {
            limitForUndoRedo = maxStackLimit;
        }

        return {
            //this function trigger undo
            executeUndo: function (obj) {
                var index, decompressedObj, compressedObj;

                this.addRedo(obj);
                //trigger undo
                if (undoCallBack) {
                    undoCallBack(obj);
                }

                //trigger event for enable/disbale undo button  
            },

            //this function trigger redo
            executeRedo: function (obj) {
                var index, decompressedObj, compressedObj;

                this.addUndo(obj, true);
 
                //trigger redo
                if (redoCallback) {
                    redoCallback(obj);
                }

                //trigger event for enable/disbale redo button  
            },

            //this function undo the last change and if index of undo list is passed then it will undo change till that parameter
            undo: function (undoCommandIndex) {
                var commandsForRedo, startIndex;
                if (!this.canUndo()) {
                    return false;
                }
                if (!undoCommandIndex) {
                    this.executeUndo(undoCommands.pop());
                }
                else {
                    startIndex = undoCommands.length - (undoCommandIndex + 1);
                    commandsForRedo = undoCommands.splice(startIndex, (undoCommandIndex + 1));
                    commandsForRedo.reverse();
                    this.executeUndo(commandsForRedo);
                }
            },

            //this function redo the last change
            redo: function (redoCommandIndex) {
                var startIndex, commandsForUndo;
                if (!this.canRedo()) {
                    return false;
                }

                if (!redoCommandIndex) {
                    this.executeRedo(redoCommands.pop());
                } else {
                    startIndex = redoCommands.length - (redoCommandIndex + 1);
                    commandsForUndo = redoCommands.splice(startIndex, (redoCommandIndex + 1));
                    commandsForUndo.reverse();
                    this.executeRedo(commandsForUndo);
                }
            },

            //this function adds a command to undo commands list
            addUndo: function (obj, calledFromRedo) {
                var compressedObj, index;
                if (limitForUndoRedo === undoCommands.length) {
                    undoCommands.splice(0, 1);
                }
             
                //push the change into the undo command
                if (obj && obj.length) {
                    undoCommands = undoCommands.concat(obj);
                } else {
                    undoCommands.push(obj);
                }
                if (!calledFromRedo) {
                    this.clearRedo();
                }
            },

            //this function adds a command to redo commands list
            addRedo: function (obj) {
                if (limitForUndoRedo === redoCommands.length) {
                    redoCommands.splice(0, 1);
                }
                if (obj && obj.length) {
                    redoCommands = redoCommands.concat(obj);
                } else {
                    redoCommands.push(obj);
                }
            },

            //it returns the list of the undo commands
            getUndoCommandsList: function () {
                return undoCommands;
            },

            //it returns the list of the redo commands
            getRedoCommandsList: function () {
                return redoCommands;
            },

            //it clears the unedo commands lists
            clearUndo: function () {
                undoCommands = [];
                //trigger event for enable/disbale undo button  
            },

            //it clears the redo commands lists
            clearRedo: function () {
                redoCommands = [];
                //trigger event for enable/disbale redo button  
            },

            //this functions returns true if we can perform the undo action otherwise it will return false
            canUndo: function () {
                return undoCommands.length > 0;
            },

            //this functions returns true if we can perform the redo action otherwise it will return false
            canRedo: function () {
                return redoCommands.length > 0;
            },

            //resets the undo-redo lists
            reset: function () {
                undoCommands = [];
                redoCommands = [];

                //trigger event for enable/disbale redo button  

                //trigger event for enable/disbale undo button  
            }
        };
    };

 

 

Here's a demonstration:

 


// creating an instance, with callback operations.
// here in this example, we are simply consoling the undo and redo commands.
// and max history we are using here is 5

const historyMan=new historyManager(function(){console.log("called undo");},function(){console.log("called redo");},5);


// adding some undo operations
historyMan.addUndo("dev");
historyMan.addUndo("melly");

// displaying the possible undo operations pending
manager.getUndoCommandsList();

// returns => ?["dev", "melly"]

// trying an undo
manager.undo();

// console output:
// called undo

// displaying the possible undo operations pending now
manager.getUndoCommandsList()

// returns => ["dev"]

// displaying the possible redo operations 
manager.getRedoCommandsList()

// returns => ["melly"]


// trying a redo operations
manager.redo()

// consoles => called redo

// displaying undo operations now
manager.getUndoCommandsList()

// returns =>?["dev", "melly"]

// you can also call clear operations, or a reset operation to reset the pending queue of operations.
manager.reset()


 

Happy Coding !