如何在javascript中处理撤消/重做事件?
我试图使用Javascript和JQuery检测表单输入的值何时发生变化。不幸的是,我发现JQuery的如何在javascript中处理撤消/重做事件?,javascript,jquery,Javascript,Jquery,我试图使用Javascript和JQuery检测表单输入的值何时发生变化。不幸的是,我发现JQuery的$(elem).change()不够,因为它只在elem失去焦点时触发更改事件。我必须立即知道表单输入值何时发生变化。为此,我缩小了与输入值的可能更改相关的事件范围,以设置关键帧、粘贴、剪切、撤消和重做。然而,javascript和JQuery似乎都没有处理撤消或重做的方法 var onChange = function () { alert('Checking for changes
$(elem).change()不够,因为它只在elem
失去焦点时触发更改事件。我必须立即知道表单输入值何时发生变化。为此,我缩小了与输入值的可能更改相关的事件范围,以设置关键帧、粘贴、剪切、撤消和重做。然而,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中的undo/redo事件,但没有找到任何有用的东西。有人能帮助处理撤销/重做事件吗?约翰·雷斯格(JQuery的创建者)的热键可能会有所帮助
从自述文件
如果要使用多个修改器(例如alt+ctrl+z),应按字母顺序定义它们,例如alt+ctrl+shift
javascript中没有撤消或重做事件。如果您想要这样的功能,您要么自己用javascript编写,要么找一个提供这种功能的库
如果您试图捕获所有可能的方式来更改输入控件,以便可以立即看到这样的更改,那么请查看以下示例代码:它为输入控件实现了更改回调。这段代码不是直接为下拉菜单设计的,但由于它是一种输入控件的形式,您可能可以修改这段代码来为下拉菜单创建自己的更改事件
StackOverflow以其无限的智慧禁止我只发布对JSFIDLE的引用,因此我必须将所有代码粘贴到这里(出于某种原因,jsFiddle与其他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 + ")");
});
(函数($){
var-isIE=false;
//条件编译,它告诉我们这是否是IE
/*@抄送
伊西=真;
@*/
//如果不支持“输入”事件,则要监视的事件
//布尔值是我们是否必须
//使用setTimeout()在事件发生后重新检查
var事件=[
“keyup”,假的,
“模糊”,假,
“聚焦”,错误,
“滴”,没错,
“改变”,错,
“输入”,错误,
“文本输入”,错误,
“粘贴”,没错,
“切”,没错,
“复制”,没错,
“上下文菜单”,对
];
//测试是否支持输入事件
//它在IE中太多了,所以我们在IE中从不依赖它
如果(!isIE){
var el=document.createElement(“输入”);
var gotInput=(el中的“oninput”);
如果(!gotInput){
el.setAttribute(“oninput”,“return;”);
gotInput=el的类型[“oninput”]=“函数”;
}
el=零;
//如果支持“输入”事件,则使用较小的
//一组事件
如果(输入){
事件=[
“输入”,错误,
“文本输入”,错误
];
}
}
$.fn.userChange=函数(fn,数据){
功能检查通知(e,延迟){
//调试代码
如果($(“#logAll”).prop(“选中”)){
日志('checkNotify-'+e.type);
}
var self=这个;
var this$=$(this);
if(this.value!==此$.data(“priorValue”)){
此$.data(“priorValue”,此.value);
fn.调用(此,e,数据);
}否则,如果(延迟){
//实际数据更改发生在某些事件之后
//所以我们把支票排在后面。
//我们需要setTimeout()的e的副本,因为实际的e
//可能在触发setTimeout()之前被覆盖
var eCopy=$.extend({},e);
setTimeout(函数(){checkNotify.call(self,eCopy,false)},1);
}
}
//此jQuery对象中每个项的连接事件处理程序
//记住初始值
这个。每个(函数(){
var this$=$(this.data)(“priorValue”,this.value);
对于(变量i=0;i
您可以使用MutationObserver监控所有更改。这不会为每个keydown和keydup提供事件,但它会整合多个更改并将其作为单个事件提供给您
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的更多信息
var newInput=“”;
var oldInput=[$('input').val();
$('input')。在('input',function()上{
newInput=$(this.val();
重做=假;
$(oldInput).each(函数(i){if(newInput==oldInput[i]){redo=true;返回false});
如果(重做){
log('do code for a undo或redo');
}
oldInput.push(newInput);
log([oldInput,newInput]);
});
基本概念是存储以前的输入值,并检查新的输入值是否等于以前的输入值之一。它不是完美的(例如,退格触发它)并且有点低效(参见下一段),但您应该能够获得所需的结果
与保留所有以前的输入不同,您可以查看撤销代码以查看它实际保留的内容(我认为它只是将大多数输入保持为在彼此的时间范围内丢失)。曾经有一段时间,我在正在进行的项目中需要类似的内容。标记的解决方案对我来说似乎没有那么优雅
<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>
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();
}
}
var catcher = new UndoListener({
el: document.querySelector('.container'),
callback: function(val){
console.log('callback fired', val);
}
});
<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>