对输入值进行编程更改时触发操作
Posted
技术标签:
【中文标题】对输入值进行编程更改时触发操作【英文标题】:Trigger action on programmatic change to an input value 【发布时间】:2013-10-19 23:51:32 【问题描述】:我的目标是观察输入值,并在其值以编程方式发生变化时触发处理程序。我只在现代浏览器中需要它。
我使用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
这似乎符合我的预期,但感觉就像是一个 hack,因为我正在覆盖现有的 value 属性并使用value
(属性和属性)的双重状态。它还破坏了似乎不喜欢修改后的属性的change
事件。
我的其他尝试:
一个 setTimeout/setInterval 循环,但这也不干净 各种watch
和 observe
填充,但它们会因输入值属性而中断
获得相同结果的正确方法是什么?
现场演示:http://jsfiddle.net/L7Emx/4/
[编辑]澄清一下:我的代码正在监视一个输入元素,其他应用程序可以在其中推送更新(例如,由于 ajax 调用,或由于其他字段的更改)。我无法控制其他应用程序如何推送更新,我只是一个观察者。
[编辑 2] 为了澄清我所说的“现代浏览器”的含义,我很高兴有一个适用于 IE 11 和 Chrome 30 的解决方案。
[更新]根据接受的答案更新了演示:http://jsfiddle.net/L7Emx/10/
@mohit-jain 建议的技巧是为用户交互添加第二个输入。
【问题讨论】:
是你一直在做改变,还是在观察别人改变它? @stevemarvell 我的代码正在监视其他应用程序可以推送更新的输入元素(例如,由于 ajax 调用或其他字段的更改)。 我认为你最好的解决方案是实现一个监控它的超时 请注意,您正在更改的value
属性 等同于.defaultValue
属性。而不是[sg]etAttribute
你应该使用闭包变量
@Qantas94Heavy 在我的演示中:如果您在输入字段中输入内容,在模糊时会触发更改事件,但无法访问您刚刚输入的值。
【参考方案1】:
如果您的解决方案的唯一问题是破坏值集上的更改事件。然后您可以在现场手动触发该事件。 (但如果用户通过浏览器更改输入,这不会监控设置 - 请参阅下面的 edit)
<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 属性不会改变。解决方法是使用两个字段。一个隐藏的,我们可以与之进行程序化交互。另一个是可见的,用户可以与之交互。经过这种考虑方法就足够简单了。
-
改变隐藏输入字段的值属性以观察更改并触发手动 onchange 事件。在设置值上更改可见字段的值以提供用户反馈。
在可见字段值更改时为观察者更新隐藏值。
【讨论】:
我们确实收到了更改事件。可用于了解用户行为。我们正在尝试实现通常不支持的目标。输入字段旨在在程序员或用户不知情的情况下更改其值。 问题是如果你使用这个方法,你就不能再使用change事件来判断用户输入了什么。这就是我的问题的重点(参见演示)。 就是这样。但有一个解决方法。您可以使用一对输入。一个隐藏的,一个可见的。编辑答案。 好的,请记住我只是一个观察者。我可以根据需要创建第二个输入,但不能替换现有的。 这会使事情复杂化。您不能更改现有字段的类型是硬性规则吗?检查答案。看看那是否可以接受。可以使用一个简单的 jquery 代码来更改现有字段的类型并在其位置放置一个新字段。【参考方案2】:我尝试过的所有地方都可以使用以下方法,包括 IE11(甚至低至 IE9 仿真模式)。
通过在定义 .value
setter 的输入元素原型链中找到对象并修改此 setter 以触发事件(我在示例中将其称为 modified
),同时仍然保持旧的行为。
运行下面的sn-p时,可以在文本输入框中输入/粘贴/whatnot,也可以点击输入元素的.value
后面追加" more"
的按钮。无论哪种情况,<span>
的内容都会同步更新。
这里唯一没有处理的是由设置属性引起的更新。如果需要,您可以使用 MutationObserver
处理它,但请注意 .value
和 value
属性之间没有一对一的关系(后者只是 default 值前者)。
// 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>
【讨论】:
【参考方案3】:我只需要现代浏览器。
你想去多现代? Ecma Script 7(6 个将在 12 月完成)可能包含Object.observe。这将允许您创建本机可观察对象。是的,你可以运行它!如何?
要试用此功能,您需要启用启用 Chrome Canary 中的实验性 javascript 标志并重新启动浏览器。 该标志可以在
'about:flags’
下找到
更多信息:read this。
是的,这是高度实验性的,在当前的浏览器中还没有准备好。此外,如果它来到 ES7,它还没有完全准备好,也不是 100%,而且 ES7 的最终日期甚至还没有确定。尽管如此,我还是想让您知道以备将来使用。
【讨论】:
+1 谢谢,很高兴知道!但在这一点上绝对太“现代”了……我至少需要支持 IE 11。【参考方案4】:既然你已经在使用 polyfills 进行 watch/observe 等,让我借此机会向你推荐 Angularjs。
它以 ng-models 的形式提供了这个功能。您可以将观察者放在模型的值上,当它发生变化时,您可以调用其他函数。
这是一个非常简单但有效的解决方案:
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');
);
【讨论】:
@Hash 我肯定会对有关 Angularjs 如何做到这一点的更多信息感兴趣(注意我刚刚添加的赏金,我正在寻找“详细的规范答案”)。 @Christophe:我已经制作并添加了一些解释核心逻辑的基本代码。 演示似乎没有做我想要的。这是我尝试以编程方式更改值:jsfiddle.net/jv8pK/1 @Christophe:我的演示确实以编程方式更改了输入值。当您单击“更改”时,与输入绑定的模型的值会发生变化。它是程序化的。您也可以直接在代码中更改它,但是您这样做的方式在 Angularjs 中是不正确的。对模型的任何更改都必须在 控制器 内。这些是您在使用 Angular 时必须学习的一些内容。看到这个更新的小提琴:jsfiddle.net/RedDevil/jv8pK/4 在旁注中,我建议您尝试 angular 的教程以轻松理解所有这些:docs.angularjs.org/tutorial【参考方案5】:有办法做到这一点。 没有 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/
【讨论】:
这对我不起作用。来自 MDN 站点:警告:一般情况下,您应该尽可能避免使用 watch() 和 unwatch()。这两种方法仅在 Gecko 中实现,主要用于调试。 来源:Mozilla Developer Network 是的,你是对的,但是在我的链接中,他用 setInterval 编写了自己的方法 watch 和 unwatch?这个试过了吗? 抱歉,我错过了最后一个链接。是的,我试过了,我希望得到比 setInterval 更好的东西(参见来自赏金的问题和评论)【参考方案6】:我刚才写了以下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
,并在 value
的值由 onpropertychange
触发时运行回调.
【讨论】:
不幸的是,这不适用于 IE 11 或 Chrome 等浏览器上的编程更改。【参考方案7】:这是一个老问题,但使用新的 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 对象交互一样与代理对象交互。不同之处在于我们已经覆盖了 setter。当 'value' 属性改变时,我们会执行正常的预期操作,obj[prop] = value
,但如果属性的值为 '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
Canuse: https://caniuse.com/#search=proxy
【讨论】:
以上是关于对输入值进行编程更改时触发操作的主要内容,如果未能解决你的问题,请参考以下文章