在以编程方式更改为输入值时触发操作

Trigger action on programmatic change to an input value

本文关键字:操作 输入 编程 方式更      更新时间:2023-09-26

我的目标是观察一个输入值,并在它的值以编程方式更改时触发处理程序。我只需要现代浏览器。

我已经使用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 循环,但这也不干净
  • 各种watchobserve填充,但它们会中断输入值属性

达到相同结果的正确方法是什么?

现场演示: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 属性不会更改。 解决方法是使用两个字段。 一个隐藏的,我们可以与之进行编程交互。另一个是可见的,用户可以与之交互。在此考虑之后,方法非常简单。

  1. 更改隐藏输入字段上的 VARIABLE 值属性,以观察更改并触发手动更改事件。 在设置值上更改可见字段的值以向用户提供反馈。
  2. 在可见字段值更改上,更新观察者隐藏的值。

以下内容适用于我尝试过的任何地方,包括IE11(甚至到IE9仿真模式)。

通过在定义.value setter 的输入元素原型链中找到对象并修改此 setter 以触发事件(我在示例中将其称为 modified),同时仍保留旧行为,从而使您的defineProperty想法更进一步。

当您运行下面的代码片段时,您可以在文本输入框中键入/粘贴/其他内容,也可以单击将" more"附加到输入元素.value的按钮。无论哪种情况,<span>的内容都会同步更新。

这里唯一没有处理的是设置属性引起的更新。如果需要,您可以使用MutationObserver来处理这个问题,但请注意,.valuevalue 属性之间没有一对一的关系(后者只是前者的默认值)。

// 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 CustomEventnew 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