在以编程方式更改为输入值时触发操作
Trigger action on programmatic change to an input value
我的目标是观察一个输入值,并在它的值以编程方式更改时触发处理程序。我只需要现代浏览器。
我已经使用defineProperty
尝试了许多组合,这是我的最新版本:
var myInput=document.getElementById("myInput");
Object.defineProperty(myInput,"value",{
get:function(){
return this.getAttribute("value");
},
set:function(val){
console.log("set");
// handle value change here
this.setAttribute("value",val);
}
});
myInput.value="new value"; // should trigger console.log and handler
这似乎符合我的预期,但感觉就像一个黑客,因为我正在覆盖现有的值属性并玩弄value
(属性和属性)的双重状态。它还会破坏似乎不喜欢修改后的属性的change
事件。
我的其他尝试:
- 一个 setTimeout/setInterval 循环,但这也不干净
- 各种
watch
和observe
填充,但它们会中断输入值属性
达到相同结果的正确方法是什么?
现场演示:http://jsfiddle.net/L7Emx/4/
[编辑] 澄清一下:我的代码正在监视一个输入元素,其他应用程序可以在其中推送更新(例如,由于 ajax 调用,或者由于其他字段的更改)。我无法控制其他应用程序如何推送更新,我只是一个观察者。
[编辑2] 为了澄清我所说的"现代浏览器"是什么意思,我对适用于IE 11和Chrome 30的解决方案感到非常满意。
[更新] 根据接受的答案更新了演示:http://jsfiddle.net/L7Emx/10/
@mohit-jain建议的技巧是为用户交互添加第二个输入。
如果解决方案的唯一问题是在值集上中断更改事件。 您可以在片场手动触发该事件。(但这不会监视设置,以防用户通过浏览器更改输入 - 请参阅下面的编辑)
<html>
<body>
<input type='hidden' id='myInput' />
<input type='text' id='myInputVisible' />
<input type='button' value='Test' onclick='return testSet();'/>
<script>
//hidden input which your API will be changing
var myInput=document.getElementById("myInput");
//visible input for the users
var myInputVisible=document.getElementById("myInputVisible");
//property mutation for hidden input
Object.defineProperty(myInput,"value",{
get:function(){
return this.getAttribute("value");
},
set:function(val){
console.log("set");
//update value of myInputVisible on myInput set
myInputVisible.value = val;
// handle value change here
this.setAttribute("value",val);
//fire the event
if ("createEvent" in document) { // Modern browsers
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, false);
myInput.dispatchEvent(evt);
}
else { // IE 8 and below
var evt = document.createEventObject();
myInput.fireEvent("onchange", evt);
}
}
});
//listen for visible input changes and update hidden
myInputVisible.onchange = function(e){
myInput.value = myInputVisible.value;
};
//this is whatever custom event handler you wish to use
//it will catch both the programmatic changes (done on myInput directly)
//and user's changes (done on myInputVisible)
myInput.onchange = function(e){
console.log(myInput.value);
};
//test method to demonstrate programmatic changes
function testSet(){
myInput.value=Math.floor((Math.random()*100000)+1);
}
</script>
</body>
</html>
详细了解如何手动触发事件
编辑:
手动事件触发和突变器方法的问题在于,当用户从浏览器更改字段值时,value 属性不会更改。 解决方法是使用两个字段。 一个隐藏的,我们可以与之进行编程交互。另一个是可见的,用户可以与之交互。在此考虑之后,方法非常简单。
- 更改隐藏输入字段上的 VARIABLE 值属性,以观察更改并触发手动更改事件。 在设置值上更改可见字段的值以向用户提供反馈。
- 在可见字段值更改上,更新观察者隐藏的值。
以下内容适用于我尝试过的任何地方,包括IE11(甚至到IE9仿真模式)。
通过在定义.value
setter 的输入元素原型链中找到对象并修改此 setter 以触发事件(我在示例中将其称为 modified
),同时仍保留旧行为,从而使您的defineProperty
想法更进一步。
当您运行下面的代码片段时,您可以在文本输入框中键入/粘贴/其他内容,也可以单击将" more"
附加到输入元素.value
的按钮。无论哪种情况,<span>
的内容都会同步更新。
这里唯一没有处理的是设置属性引起的更新。如果需要,您可以使用MutationObserver
来处理这个问题,但请注意,.value
和 value
属性之间没有一对一的关系(后者只是前者的默认值)。
// make all input elements trigger an event when programmatically setting .value
monkeyPatchAllTheThings();
var input = document.querySelector("input");
var span = document.querySelector("span");
function updateSpan() {
span.textContent = input.value;
}
// handle user-initiated changes to the value
input.addEventListener("input", updateSpan);
// handle programmatic changes to the value
input.addEventListener("modified", updateSpan);
// handle initial content
updateSpan();
document.querySelector("button").addEventListener("click", function () {
input.value += " more";
});
function monkeyPatchAllTheThings() {
// create an input element
var inp = document.createElement("input");
// walk up its prototype chain until we find the object on which .value is defined
var valuePropObj = Object.getPrototypeOf(inp);
var descriptor;
while (valuePropObj && !descriptor) {
descriptor = Object.getOwnPropertyDescriptor(valuePropObj, "value");
if (!descriptor)
valuePropObj = Object.getPrototypeOf(valuePropObj);
}
if (!descriptor) {
console.log("couldn't find .value anywhere in the prototype chain :(");
} else {
console.log(".value descriptor found on", "" + valuePropObj);
}
// remember the original .value setter ...
var oldSetter = descriptor.set;
// ... and replace it with a new one that a) calls the original,
// and b) triggers a custom event
descriptor.set = function () {
oldSetter.apply(this, arguments);
// for simplicity I'm using the old IE-compatible way of creating events
var evt = document.createEvent("Event");
evt.initEvent("modified", true, true);
this.dispatchEvent(evt);
};
// re-apply the modified descriptor
Object.defineProperty(valuePropObj, "value", descriptor);
}
<input><br><br>
The input contains "<span></span>"<br><br>
<button>update input programmatically</button>
我只需要现代浏览器。
你想去多现代?Ecma 脚本 7(6 将在 12 月最终版本发布)可能包含Object.observe。这将允许您创建本机可观察量。是的,你可以运行它!如何?
要试验此功能,您需要启用启用 Chrome Canary 中的实验性 JavaScript 标志,然后重新启动浏览器。 该标志可以在
'about:flags’
更多信息:阅读此内容。
所以是的,这是高度实验性的,在当前的浏览器集中还没有准备好。此外,它还没有完全准备好,如果它来到 ES7,也不是 100%,ES7 的最终日期甚至还没有确定。不过,我想让你知道以备将来使用。
既然你已经在使用polyfills来观察/观察等,让我借此机会向你推荐Angularjs。
它以ng模型的形式提供了此功能。您可以将观察程序放在模型的值上,当它发生变化时,您可以调用其他函数。
这是一个非常简单但有效的解决方案:
http://jsfiddle.net/RedDevil/jv8pK/
基本上,进行文本输入并将其绑定到模型:
<input type="text" data-ng-model="variable">
然后在控制器中的此输入上放置一个观察器。
$scope.$watch(function() {
return $scope.variable
}, function(newVal, oldVal) {
if(newVal !== null) {
window.alert('programmatically changed');
}
});
有一种方法可以做到这一点。没有 DOM 事件,但是有一个 javascript 事件会在对象属性更改时触发。
document.form1.textfield.watch("value", function(object, oldval, newval){})
^ Object watched ^ ^ ^
|_ property watched | |
|________|____ old and new value
在回调中,您可以执行任何操作。
在这个例子中,我们可以看到这个效果(检查jsFiddle):
var obj = { prop: 123 };
obj.watch('prop', function(propertyName, oldValue, newValue){
console.log('Old value is '+oldValue); // 123
console.log('New value is '+newValue); // 456
});
obj.prop = 456;
当obj
更改时,它会激活watch
侦听器。
您在此链接中提供了更多信息:http://james.padolsey.com/javascript/monitoring-dom-properties/
我不久前写了以下 Gist,它允许跨浏览器(包括 IE8+)侦听自定义事件。
看看我是如何在IE8上收听onpropertychange
的。
util.listenToCustomEvents = function (event_name, callback) {
if (document.addEventListener) {
document.addEventListener(event_name, callback, false);
} else {
document.documentElement.attachEvent('onpropertychange', function (e) {
if(e.propertyName == event_name) {
callback();
}
}
};
我不确定IE8解决方案是否适用于跨浏览器,但是您可以在输入的属性value
上设置假eventlistener
,并在onpropertychange
触发value
的值更改后运行回调。
这是一个古老的问题,但是对于新的JS代理对象,在值更改时触发事件非常容易:
let proxyInput = new Proxy(input, {
set(obj, prop, value) {
obj[prop] = value;
if(prop === 'value'){
let event = new InputEvent('input', {data: value})
obj.dispatchEvent(event);
}
return true;
}
})
input.addEventListener('input', $event => {
output.value = `Input changed, new value: ${$event.data}`;
});
proxyInput.value = 'thing'
window.setTimeout(() => proxyInput.value = 'another thing', 1500);
<input id="input">
<output id="output">
这将基于原始输入 DOM 对象创建一个 JS 代理对象。您可以与代理对象进行交互,就像与源 DOM 对象交互一样。不同的是,我们已经覆盖了二传手。当 'value' prop 被更改时,我们执行正常的、预期的操作,obj[prop] = value
,但如果 prop 的值是 'value',我们就会触发一个自定义 InputEvent。
请注意,您可以使用new CustomEvent
或new Event
触发任何类型的事件。"改变"可能更合适。
在此处阅读有关代理和自定义事件的更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
犬类:https://caniuse.com/#search=proxy
- 从输入时提交到jQuery,无需提交按钮,无需表单操作
- 从表单中获取用户输入执行计算(操作顺序)并输出回该表单
- 选中复选框的Jquery/Javascript问题使用输入框操作将行从一个表添加到另一个表
- JSF/PrimeFaces客户端输入操作
- 操作输入字段
- jQuery表单验证(如果==“执行此操作”)仅适用于第一次输入
- 如何获取html中输入框的值并对其进行操作
- 改变“;onClick"这个jQuery的操作从清除输入文本改为将输入文本添加到下面的列表中
- 如何将生成的字符串放入Form操作和输入中
- 当输入为变量时,.replace.不执行任何操作
- JavaScript 表单操作更改RC方法发布文本输入提交
- JavaScript 对输入框执行操作
- 如果未输入任何内容,则使 JavaScript 按钮不执行任何操作
- 如何在我更改输入时添加javascript操作'的内容
- Ember输入模糊操作
- 只有当输入文本是example.com这样的域时才执行操作
- IE7在按压时”;输入“;键不会阻止默认操作
- 由代码操作输入时不触发主干视图更改事件
- Jquery操作输入字段
- 使用下拉框更改操作符以操作输入框