7

我正在尝试使用 Javascript 和 JQuery 检测表单输入的值何时发生变化。不幸的是,我发现 JQuery 的$(elem).change()不足之处在于它只在elem失去焦点时触发更改事件。我必须立即知道表单输入的值何时发生变化。为此,我将与输入值可能更改相关的事件范围缩小到keyuppastecutundoredo。但是,javascript 和 JQuery 似乎都没有处理撤消或重做的方法。

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 ?

我在 Javascript/JQuery 中搜索了撤消/重做事件,但没有发现任何有用的信息。有人可以帮助处理撤消/重做事件吗?

4

6 回答 6

7

javascript 中没有撤消或重做事件。如果你想要这样的功能,你要么必须自己用 javascript 编写它,要么找到一个提供这种功能的库。

如果您试图捕获可以更改输入控件的所有可能方式,以便您可以立即看到此类更改,请查看此示例代码:http: //jsfiddle.net/jfriend00/6qyS6/它实现了更改输入控件的回调。此代码不是直接为下拉菜单设计的,但由于它是一种输入控件形式,您可以调整此代码来为下拉菜单创建自己的更改事件。

好吧,StackOverflow 以其无限的智慧禁止我仅发布对 jsFiddle 的引用,因此我必须将所有代码粘贴到此处(出于某种原因,jsFiddles 被单独列出,而不是其他 Web 引用)。我并不是将其表示为一个精确的解决方案,而是作为一个模板,您可以使用它来检测用户对输入控件的更改:

(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 + ")");
});
于 2013-03-20T05:51:55.657 回答
5

您可以使用 MutationObserver 监控所有更改。这不会为每个 keydown 和 keyup 提供事件,但它会合并多个更改并将其作为单个事件提供给您。

  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  var observer = new MutationObserver(function(mutations) {  
    mutations.forEach(function(mutation) {
        // mutation.target will give you element which has been modified.
        // mutation.addedNodes and mutation.removedNodes will give you operations that were performed on the node
        // happy coding :)
      });
  });
  observer.observe(elementsToMonitor, {
    attributes: true, 
    childList: true, 
    characterData: true 
   });

有关 MutationObserver 的更多信息 https://developer.mozilla.org/en/docs/Web/API/MutationObserver

于 2014-03-27T07:45:42.230 回答
2

John Resig(JQuery 的创建者)的热键可能会有所帮助

https://github.com/jeresig/jquery.hotkeys

从自述文件

如果您想使用多个修饰符(例如 alt+ctrl+z),您应该按字母顺序定义它们,例如 alt+ctrl+shift

于 2013-03-20T05:50:17.770 回答
0
<input type="text"/>
<script>
var newInput = "";
var oldInput = [$('input').val()];
$('input').on('input',function(){
    newInput = $(this).val();
    redo = false;
    $(oldInput).each(function(i){if(newInput==oldInput[i]){redo = true; return false});
    if(redo){
        console.log('do code for an undo or redo');
    }
oldInput.push(newInput);
console.log([oldInput,newInput]);
});
</script>

基本概念是存储以前的输入值并检查新输入值是否等于以前的输入值之一。它并不完美(例如,退格触发它)并且效率有点低(参见下一段),但您应该能够获得您想要的结果。

您可以查看撤消代码以查看它实际保留的内容,而不是保留所有先前的输入(我认为它只是使大多数输入保持丢失,因为它们在彼此的时间范围内)。

于 2015-05-16T22:29:37.770 回答
0

曾经有一段时间,我正在从事的项目中需要这样的东西。对我来说,获得结果的标记解决方案似乎并不那么优雅。我结合了这里回答的几件事来做到这一点。

function UndoListener(options){
    if(!options.el instanceof HTMLElement) return;
    this.el = options.el;
    this.callback = options.callback || function(){};
    this.expectedChange = false;
    this.init();
}

UndoListener.prototype = {
    constructor: UndoListener,
    addListeners: function(){
        this.el.addEventListener('keydown', (e) => this.expectedChange = this.eventChecker(e));
        this.el.addEventListener('cut', (e) => this.expectedChange = true);
        this.el.addEventListener('paste', (e) => this.expectedChange = true);
    },
    addObserver: function(){
        this.observer = new MutationObserver((mt) => {
            if(!this.expectedChange){
                this.expectedChange = true;
                this.observer.disconnect();
                this.callback.call(this.el, {
                    original: [...mt].shift().oldValue,
                    current: this.el.innerText
                });
                this.addObserver();
            }
            this.expectedChange = false;
        });

        this.observer.observe(this.el, {
            characterData: true,
            subtree: true,
            characterDataOldValue: true
        });
    },
    eventChecker: function(event) {
        return !(~['z','y'].indexOf(event.key) && (event.ctrlKey || event.metaKey));
    },
    init: function(){
        this.addListeners();
        this.addObserver();
    }
}

这使用MutationObserver来“捕获”撤消事件。这样做是因为 MutationObserver 在事件触发后触发。我们检查事件是否是预期事件,如 keydown 或 cut 并允许更改发生而不触发回调。如果事件是意外的,我们假设发生了撤消。这不能区分撤消和重做;回调将触发。用法:

var catcher = new UndoListener({
    el: document.querySelector('.container'),
    callback: function(val){
        console.log('callback fired', val);
    }
});

在 codepen 上有这个工作

于 2018-01-08T16:12:24.297 回答
0

我创建了一个状态撤消/重做快照管理器类,它非常适合跟踪带有子输入的整个 HTML 元素的更改历史记录。如果您想在更精细的级别上拍摄快照,您可以绑定按键事件,而不是我在示例中使用的更改事件。

  <div id="buttons">
     <button type="button" id="undo_btn">Undo</button>
     <button type="button" id="redo_btn">Redo</button>
  </div>
  <br/><br/>
  <div id="content">
     <label>
        Input1:
        <input type="text" value="" />
     </label>
     <br/><br/>
     <label>
        Input2:
        <input type="text" value="" />
     </label>
     <br/><br/>
     <label>
        Input3:
        <input type="text" value="" />
     </label>
     <br/><br/>
     <label>
        Input4:
        <input type="text" value="" />
     </label>
     <br/><br/>
  </div>

  <script type="text/javascript">
  var StateUndoRedo = function() {
     var init = function(opts) {
        var self = this;
        self.opts = opts;
        if(typeof(self.opts['undo_disabled']) == 'undefined') {
           self.opts['undo_disabled'] = function() {};
        }
        if(typeof(self.opts['undo_enabled']) == 'undefined') {
           self.opts['undo_enabled'] = function() {};
        }
        if(typeof(self.opts['redo_disabled']) == 'undefined') {
           self.opts['redo_disabled'] = function() {};
        }
        if(typeof(self.opts['redo_enabled']) == 'undefined') {
           self.opts['redo_enabled'] = function() {};
        }
        if(typeof(self.opts['restore']) == 'undefined') {
           self.opts['restore'] = function() {};
        }
        self.opts['undo_disabled']();
        self.opts['redo_disabled']();
     }

     var add = function(state) {
        var self = this;
        if(typeof(self.states) == 'undefined') {
           self.states = [];
        }
        if(typeof(self.state_index) == 'undefined') {
           self.state_index = -1;
        }
        self.state_index++;
        self.states[self.state_index] = state;
        self.states.length = self.state_index + 1;
        if(self.state_index > 0) {
           self.opts['undo_enabled']();
        }
        self.opts['redo_disabled']();
     }

     var undo = function() {
        var self = this;
        if(self.state_index > 0) {
           self.state_index--;
           if(self.state_index == 0) {
              self.opts['undo_disabled']();
           } else {
              self.opts['undo_enabled']();
           }
           self.opts['redo_enabled']();

           self.opts['restore'](self.states[self.state_index]);
       }
     }

     var redo = function() {
        var self = this;
        if(self.state_index < self.states.length) {
           self.state_index++;
           if(self.state_index == self.states.length - 1) {
              self.opts['redo_disabled']();
           } else {
              self.opts['redo_enabled']();
           }
           self.opts['undo_enabled']();

           self.opts['restore'](self.states[self.state_index]);
       }
     }

     var restore = function() {
        var self = this;
        self.opts['restore'](self.states[self.state_index]);
     }

     var clear = function() {
        var self = this;
        self.state_index = 0;
        //self.states = [];
     }

     return {
        init: init,
        add: add,
        undo: undo,
        redo: redo,
        restore: restore,
        clear: clear
     };
  };

  //initialize object
  var o = new StateUndoRedo();
  o.init({
     'undo_disabled': function() {
        //make the undo button hidden
        document.getElementById("undo_btn").disabled = true;
     },
     'undo_enabled': function() {
        //make the undo button visible
        document.getElementById("undo_btn").disabled = false;
     },
     'redo_disabled': function() {
        //make the redo button hidden
        document.getElementById("redo_btn").disabled = true;
     },
     'redo_enabled': function() {
        //make the redo button visible
        document.getElementById("redo_btn").disabled = false;
     },
     'restore': function(state) {
        //replace the current content with the restored state content
        document.getElementById("content").innerHTML = state;
     }
  });

  //initialize first state
  o.add(document.getElementById("content").innerHTML);
  o.restore();
  o.clear();

  //bind click events for undo/redo buttons
  document.getElementById("undo_btn").addEventListener("click", function() {
     o.undo();
  });
  document.getElementById("redo_btn").addEventListener("click", function() {
     o.redo();
  });

  //bind change events for content element
  document.getElementById('content').addEventListener("change", function(event) {
     // the following is required since vanilla JS innerHTML 
     // does not capture user-changed values of inputs
     // so we set the attributes explicitly (use jQuery to avoid this)
     var elems = document.querySelectorAll("#content input");
     for(var i = 0; i < elems.length; i++) {
        elems[i].setAttribute("value", elems[i].value);
     }

     //take a snapshot of the current state of the content element
     o.add(document.getElementById("content").innerHTML);
  });
  </script>

看到这个 JSFiddle:https ://jsfiddle.net/up73q4t0/56/

于 2018-03-25T20:19:32.420 回答