javascriptjquery

How to handle undo/redo event in javascript?


I'm trying to detect whenever a form input's value changes using Javascript & JQuery. Unfortunately, I find JQuery's $(elem).change() insufficient because it only fires the change event when elem loses focus. I have to immediately know when there are changes in the form input's value. To this end, I've narrowed down events associated with a possible change in an input's value to keyup, paste, cut, undo, and redo. However, neither javascript nor JQuery seem to have a way of dealing with undo or redo.

var onChange = function ()
{
    alert('Checking for changes...');
};

$(this).off('keyup').on('keyup', onChange);
$(this).off('paste').on('paste', onChange);
$(this).off('cut').on('cut', onChange);

$(this).off('undo').on('undo', onChange);  // undo ?
$(this).off('redo').on('redo', onChange);  // redo ?

I've googled for undo/redo event in Javascript/JQuery but didn't find anything helpful. Can someone help on how to deal with undo/redo events?


Solution

  • There is no undo or redo event in javascript. If you wanted such functionality, you'd either have to write it yourself in javascript or find a library that offered such functionality.

    If you're trying to trap all possible ways that an input control can be changed so you can see such a change immediately, then take a look at this sample code: http://jsfiddle.net/jfriend00/6qyS6/ which implemented a change callback for an input control. This code wasn't designed directly for a drop-down, but since it's a form of an input control, you can probably adapt this code to create your own change event for a drop-down.

    Well, StackOverflow in their infinite wisdom is prohibiting me from posting just a reference to a jsFiddle so I have to paste all the code in here (for some reason, jsFiddles are singled out as opposed to other web references). I'm not representing this as an exact solution, but as a template you could use for how to detect user changes to an input control:

    (function($) {
    
        var isIE = false;
        // conditional compilation which tells us if this is IE
        /*@cc_on
        isIE = true;
        @*/
    
        // Events to monitor if 'input' event is not supported
        // The boolean value is whether we have to 
        // re-check after the event with a setTimeout()
        var events = [
            "keyup", false,
            "blur", false,
            "focus", false,
            "drop", true,
            "change", false,
            "input", false,
            "textInput", false,
            "paste", true,
            "cut", true,
            "copy", true,
            "contextmenu", true
        ];
        // Test if the input event is supported
        // It's too buggy in IE so we never rely on it in IE
        if (!isIE) {
            var el = document.createElement("input");
            var gotInput = ("oninput" in el);
            if  (!gotInput) {
                el.setAttribute("oninput", 'return;');
                gotInput = typeof el["oninput"] == 'function';
            }
            el = null;
            // if 'input' event is supported, then use a smaller
            // set of events
            if (gotInput) {
                events = [
                    "input", false,
                    "textInput", false
                ];
            }
        }
    
        $.fn.userChange = function(fn, data) {
            function checkNotify(e, delay) {
                // debugging code
                if ($("#logAll").prop("checked")) {
                    log('checkNotify - ' + e.type);
                }
    
                var self = this;
                var this$ = $(this);
    
                if (this.value !== this$.data("priorValue")) {
                    this$.data("priorValue", this.value);
                    fn.call(this, e, data);
                } else if (delay) {
                    // The actual data change happens after some events
                    // so we queue a check for after.
                    // We need a copy of e for setTimeout() because the real e
                    // may be overwritten before the setTimeout() fires
                    var eCopy = $.extend({}, e);
                    setTimeout(function() {checkNotify.call(self, eCopy, false)}, 1);
                }
            }
    
            // hook up event handlers for each item in this jQuery object
            // and remember initial value
            this.each(function() {
                var this$ = $(this).data("priorValue", this.value);
                for (var i = 0; i < events.length; i+=2) {
                    (function(i) {
                        this$.on(events[i], function(e) {
                            checkNotify.call(this, e, events[i+1]);
                        });
                    })(i);
                }
            });
        }
    })(jQuery);    
    
    function log(x) {
        jQuery("#log").append("<div>" + x + "</div>");
    }
    
    // hook up our test engine    
    $("#clear").click(function() {
        $("#log").html("");
    });
    
    
    $("#container input").userChange(function(e) {
        log("change - " + e.type + " (" + this.value + ")");
    });