Angular Undo

by mslocum

HTML

<div ng-controller="Ctrl">
    <p>Hit save to save the current value of the input box. Then change it. Then hit undo.</p>
    <br/>
    <input type="text" ng-model="value"/><br/>
    <button ng-click="save()">save</button>
    <button ng-click="saveFunc()">save function</button>
    <button sx-do-undo sx-has-undo>undo</button>
    <button sx-do-redo sx-has-redo>redo</button>
</div>

JavaScript

angular.module('sxUndo', [])
    .service('$undo', [ function(){
        var aHistory = [],
            aRedo = [],
            bRedoEnabled = true,
            oScopeCache = null;

        /**
         * Set a cached scope which will make the push of oScope optional
         * @param oScopeIn
         */
        this.setScope = function(oScopeIn) {
            oScope = oScopeIn;
        };
        this.add = function(strLocation, oScope, func) {
            aRedo = [];
            push(strLocation,
                oScope || oScopeCache,
                func,
                oScope[strLocation],
                aHistory);
            return this;
        };
        function push(strLocation, oScope, func, value, aStack) {
            var oUndo = aStack[aStack.length -1];
            if (!oUndo ||
                oUndo.value != value ||
                oUndo.location != strLocation ||
                oUndo.scope != oScope ||
                oUndo.func != func)
            {
                aStack.push({
                    scope: oScope,
                    location: strLocation,
                    value: angular.copy(value),
                    func: func
                });
            }
        }
        this.undo = function(iNth) {
            pop(iNth, aHistory);
        };
        this.redo = function(iNth) {
            pop(iNth, aRedo);
        };
        function pop(iNth, aStack) {
            var oUndo, oldValue;
            iNth = parseInt(iNth, 10) || aStack.length - nextUndo(aStack);
            if (aStack.length >= iNth) {
                oUndo = aStack.splice(iNth * -1)[0];
                oldValue = oUndo.scope[oUndo.location];
                if (typeof oUndo.func == "function") {
                    oUndo.func.call(oUndo.scope, oUndo.value);
                } else {
                    oUndo.scope[oUndo.location] = oUndo.value;
                }
                if (bRedoEnabled) {
                    push(oUndo.location,
                       ...