触发对程序化更改为输入值的操作

时间:2013-10-11 19:37:49

标签: javascript html-input defineproperty

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

我使用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 polyfills,但它们会因输入值属性而中断

获得相同结果的正确方法是什么?

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

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

[编辑2]为了澄清“现代浏览器”的含义,我对能够在IE 11和Chrome 30上运行的解决方案感到非常满意。

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

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

7 个答案:

答案 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. 隐藏输入字段上的mutate value属性,以观察更改和触发手动onchange事件。设置值更改可见字段的值以提供用户反馈。
  2. 关于可见字段值更改,更新为观察者隐藏的值。

答案 1 :(得分:2)

以下在我尝试过的任何地方都可以使用,包括IE11(甚至是IE9仿真模式)。

通过在输入元素原型链中找到定义defineProperty setter的对象并修改此setter来触发事件(我称之为事件),您的.value想法会更进一步。 modified),同时仍然保持旧的行为。

当您运行下面的代码段时,您可以在文本输入框中输入/粘贴/诸如此类,或者您可以点击将" more"追加到输入元素.value的按钮。在任何一种情况下,<span>的内容都会同步更新。

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

&#13;
&#13;
// 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;
&#13;
&#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 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