javascript 对输入值进行编程更改时触发操作

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19325938/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 15:10:50  来源:igfitidea点击:

Trigger action on programmatic change to an input value

javascripthtml-inputdefineproperty

提问by Christophe

My objective is to observe an input value and trigger a handler when its value gets changed programmatically. I only need it for modern browsers.

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

I have tried many combinations using definePropertyand this is my latest iteration:

我尝试了许多组合使用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

This seems to do what I expect, but it feels like a hack as I am overriding the existing value property and playing with the dual status of value(attribute and property). It also breaks the changeevent that doesn't seem to like the modified property.

这似乎符合我的预期,但感觉就像一个黑客,因为我覆盖了现有的 value 属性并玩弄value(attribute and property)的双重状态。它还破坏了change似乎不喜欢修改属性的事件。

My other attempts:

我的其他尝试:

  • a setTimeout/setInterval loop, but this is not clean either
  • various watchand observepolyfills, but they break for an input value property
  • setTimeout/setInterval 循环,但这也不干净
  • 各种watchobservepolyfill,但它们因输入值属性而中断

What would be a proper way to achieve the same result?

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

Live demo: http://jsfiddle.net/L7Emx/4/

现场演示:http: //jsfiddle.net/L7Emx/4/

[Edit] To clarify: My code is watching an input element where other applications can push updates (as a result of ajax calls for example, or as a result of changes in other fields). I have no control on how the other applications push updates, I am just an observer.

[编辑] 澄清:我的代码正在监视其他应用程序可以推送更新的输入元素(例如,由于 ajax 调用,或由于其他字段的更改)。我无法控制其他应用程序如何推送更新,我只是一个观察者。

[Edit 2] To clarify what I mean by "modern browser", I'd be very happy with a solution that works on IE 11 and Chrome 30.

[编辑 2] 为了澄清我所说的“现代浏览器”是什么意思,我对适用于 IE 11 和 Chrome 30 的解决方案感到非常满意。

[Update]Updated demo based on the accepted answer: http://jsfiddle.net/L7Emx/10/

[更新]根据接受的答案更新演示:http: //jsfiddle.net/L7Emx/10/

The trick suggested by @mohit-jain is to add a second input for user interaction.

@mohit-jain 建议的技巧是为用户交互添加第二个输入。

采纳答案by Mohit

if the only problem with your solution is breaking of change event on value set. thn you can fire that event manually on set. (But this wont monitor set in case a user makes a change to the input via browser -- see editbellow)

如果您的解决方案的唯一问题是在值集上中断更改事件。然后您可以在现场手动触发该事件。(但如果用户通过浏览器更改输入,则不会监视设置 - 请参阅下面的编辑

<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

更多关于手动触发事件



EDIT:

编辑

The problem with manual event firing and the mutator approach is that the value property won't change when user changes the field value from browser. the work around is to use two fields. one hidden with which we can have programmatic interaction. Another is visible with which user can interact. After this consideration approach is simple enough.

手动事件触发和 mutator 方法的问题在于,当用户从浏览器更改字段值时,value 属性不会更改。解决方法是使用两个字段。一个隐藏的,我们可以用它来进行程序化的交互。另一个是可见的,用户可以与之交互。经过这样的考虑,方法就足够简单了。

  1. mutate value property on hidden input-field to observe the changes and fire manual onchange event. on set value change the value of visible field to give user feedback.
  2. on visible field value change update the value of hidden for observer.
  1. 改变隐藏输入字段上的 value 属性以观察更改并触发手动 onchange 事件。在设置值上更改可见字段的值以提供用户反馈。
  2. 在可见字段值更改时更新观察者隐藏的值。

回答by balpha

The following works everywhere I've tried it, including IE11 (even down to IE9 emulation mode).

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

It takes your definePropertyidea a bit further by finding the object in the input element prototype chain that defines the .valuesetter and modifying this setter to trigger an event (I've called it modifiedin the example), while still keeping the old behavior.

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

When you run the snippet below, you can type / paste / whatnot in the text input box, or you can click the button that appends " more"to the input element's .value. In either case, the <span>'s content is synchronously updated.

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

The only thing that's not handled here is an update caused by setting the attribute. You could handle that with a MutationObserverif you want, but note that there's not a one-to-one relationship between .valueand the valueattribute (the latter is just the defaultvalue for the former).

这里唯一没有处理的是设置属性引起的更新。MutationObserver如果需要,您可以使用 a 处理它,但请注意,.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>

回答by kumarharsh

Since you are already using polyfills for watch/observe, etc, let me take the opportunity to suggest to you Angularjs.

由于您已经在使用 polyfills 进行观察/观察等,让我借此机会向您推荐 Angularjs

It offers exactly this functionality in the form of it's ng-models. You can put watchers on the model's value, and when it changes, you can then call other functions.

它以 ng-models 的形式提供了这个功能。您可以将观察者放在模型的值上,当它发生变化时,您可以调用其他函数。

Here is a very simple, but working solution to what you want:

这是一个非常简单但可行的解决方案:

http://jsfiddle.net/RedDevil/jv8pK/

http://jsfiddle.net/RedDevil/jv8pK/

Basically, make a text input and bind it to a model:

基本上,进行文本输入并将其绑定到模型:

<input type="text" data-ng-model="variable">

then put a watcher on the angularjs model on this input in the controller.

然后在控制器的这个输入上的 angularjs 模型上放置一个观察者。

$scope.$watch(function() {
  return $scope.variable
}, function(newVal, oldVal) {
  if(newVal !== null) {
    window.alert('programmatically changed');
  }
});

回答by MarcoK

I only need it for modern browsers.

我只需要它用于现代浏览器。

How modern would you like to go? Ecma Script 7 (6 will be made final in December) mightcontain Object.observe. This would allow you to create native observables. And yes, you can run it! How?

你想要多现代?Ecma Script 7(6 将在 12 月定稿)可能包含Object.observe。这将允许您创建本机可观察对象。是的,你可以运行它!如何?

To experiment with this feature, you need to enable the Enable Experimental JavaScript flag in Chrome Canary and restart the browser. The flag can be found under 'about:flags'

要试验此功能,您需要在 Chrome Canary 中启用 Enable Experimental JavaScript 标志并重新启动浏览器。该标志可以在下面找到'about:flags'

More info: read this.

更多信息:阅读这个

So yeah, this is highly experimental and not ready in the current set of browsers. Also, it's still not fully ready and not 100% if it's coming to ES7, and the final date for ES7 isn't even set yet. Still, I wanted to let you know for future use.

所以是的,这是高度实验性的,在当前的浏览器中还没有准备好。此外,它仍然没有完全准备好,如果它来到 ES7,也不是 100%,而且 ES7 的最终日期甚至还没有确定。不过,我想让您知道以备将来使用。

回答by Donovan Charpin

There is a way to do this. There is no DOM event for this, however there is a javascript event that triggers on an object property change.

有一种方法可以做到这一点。没有针对此的 DOM 事件,但是有一个 javascript 事件会在对象属性更改时触发。

document.form1.textfield.watch("value", function(object, oldval, newval){})
                ^ Object watched  ^                      ^        ^
                                  |_ property watched    |        |
                                                         |________|____ old and new value

In the callback you can do whatever.

在回调中你可以做任何事情。



In this example, we can see this effect (Check the jsFiddle) :

在这个例子中,我们可以看到这个效果(检查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;

When objchange, it activates the watchlistener.

obj更改时,它会激活watch侦听器。

You have more information in this link : http://james.padolsey.com/javascript/monitoring-dom-properties/

您在此链接中有更多信息:http: //james.padolsey.com/javascript/monitoring-dom-properties/

回答by SethWhite

This is an old question, but with the newish JS Proxy object, triggering an event on a value change is pretty easy:

这是一个老问题,但使用新的 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">

This creates a JS proxy object based on the original input DOM object. You can interact with the proxy object in the same way you'd interact with the origin DOM object. The difference is that we've overridden the setter. When the 'value' prop is changed, we carry out the normal, expected operation, obj[prop] = value, but then if the prop's value is 'value' we fire a custom InputEvent.

这将基于原始输入 DOM 对象创建一个 JS 代理对象。您可以使用与原始 DOM 对象交互的相同方式与代理对象交互。不同之处在于我们已经覆盖了 setter。当“value”道具更改时,我们执行正常的预期操作obj[prop] = value,但是如果道具的值为“value”,我们将触发自定义 InputEvent。

Note that you can fire whatever kind of event you'd like with new CustomEventor new Event. "change" might be more appropriate.

请注意,您可以使用new CustomEvent或触发任何类型的事件new Event。“改变”可能更合适。

Read more on proxies and custom events here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

在此处阅读有关代理和自定义事件的更多信息: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://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events

Caniuse: https://caniuse.com/#search=proxy

Caniuse:https://caniuse.com/#search=proxy

回答by frequent

I wrote the following Gista little while ago, which allows to listen for custom events cross browser (including IE8+).

不久前我写了以下Gist,它允许跨浏览器(包括 IE8+)监听自定义事件。

Have a look at how I'm listening for onpropertychangeon IE8.

看看我是如何onpropertychange在 IE8 上收听的。

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();
    }
  }
};

I'm not sure the IE8 solution works cross browser, but you could set a fake eventlisteneron the property valueof your input and run a callback once the value of valuechanges triggered by onpropertychange.

我不确定 IE8 解决方案是否适用于跨浏览器,但是您可以eventlistenervalue输入的属性上设置一个假值,并valueonpropertychange.