我的目标是观察输入值,并在其值变为以编程方式时触发处理程序。我只需要它用于现代浏览器。
我使用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
事件。
我的其他尝试:
watch
和observe
polyfills,但它们会因输入值属性而中断获得相同结果的正确方法是什么?
现场演示:http://jsfiddle.net/L7Emx/4/
[编辑]澄清:我的代码正在观看其他应用程序可以推送更新的输入元素(例如,由于ajax调用,或者由于其他字段的更改)。我无法控制其他应用程序如何推送更新,我只是一个观察者。
[编辑2]为了澄清“现代浏览器”的含义,我对能够在IE 11和Chrome 30上运行的解决方案感到非常满意。
[更新] 根据接受的答案更新了演示:http://jsfiddle.net/L7Emx/10/
@ mohit-jain建议的技巧是为用户交互添加第二个输入。
答案 0 :(得分:16)
如果解决方案的唯一问题是在值集上打破更改事件。你可以在集合上手动触发该事件。 (但是如果用户通过浏览器对输入进行更改,则不会监视此设置 - 请参阅编辑以下文字)
<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>
more on firing events manually
修改强>:
手动事件触发和mutator方法的问题是当用户从浏览器更改字段值时,value属性不会更改。解决方法是使用两个字段。隐藏的一个我们可以与程序化交互。另一个是可见的,用户可以与之交互。在这种考虑方法之后很简单。
答案 1 :(得分:2)
以下在我尝试过的任何地方都可以使用,包括IE11(甚至是IE9仿真模式)。
通过在输入元素原型链中找到定义defineProperty
setter的对象并修改此setter来触发事件(我称之为事件),您的.value
想法会更进一步。
当您运行下面的代码段时,您可以在文本输入框中输入/粘贴/诸如此类,或者您可以点击将" 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);
}
&#13;
<input><br><br>
The input contains "<span></span>"<br><br>
<button>update input programmatically</button>
&#13;
答案 2 :(得分:1)
由于您已经在使用polyfills观看/观察等,让我借此机会向您推荐Angularjs。
它以ng-model的形式提供了这种功能。您可以将观察者放在模型的值上,当它发生变化时,您可以调用其他函数。
这是一个非常简单但有效的解决方案:
http://jsfiddle.net/RedDevil/jv8pK/
基本上,进行文本输入并将其绑定到模型:
<input type="text" data-ng-model="variable">
然后在控制器中的此输入上的angularjs模型上放置一个观察者。
$scope.$watch(function() {
return $scope.variable
}, function(newVal, oldVal) {
if(newVal !== null) {
window.alert('programmatically changed');
}
});
答案 3 :(得分:1)
我只需要现代浏览器。
你想去现代吗? Ecma脚本7(6将在12月决赛)可能包含Object.observe。这将允许您创建本机可观察对象。是的,你可以运行它!怎么样?
要试用此功能,您需要启用“启用” Chrome Canary中的实验性JavaScript标记,然后重新启动浏览器。 该标志位于
下'about:flags’
更多信息:read this。
所以是的,这是高度实验性的,并没有在当前的浏览器集中准备好。此外,它仍然没有完全准备好,如果它来到ES7则不是100%,而且ES7的最终日期还没有确定。不过,我想让你知道以备将来使用。
答案 4 :(得分:0)
有一种方法可以做到这一点。 没有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/
答案 5 :(得分:0)
我不久前写了下面的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解决方案是否可以跨浏览器工作,但您可以在输入的属性eventlistener
上设置假value
,并在{{1}的值后运行回调由value
触发的更改。
答案 6 :(得分:0)
这是一个古老的问题,但是使用新的JS Proxy对象,在值更改时触发事件非常容易:
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对象进行交互的方式来与代理对象进行交互。不同之处在于我们已经覆盖了二传手。更改“值”属性后,我们将执行正常的预期操作obj[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