2013-10-11 30 views
28

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

我曾尝试使用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建议的技巧是为用户交互添加第二个输入。

+0

是它永远是你做的改变或者是你看别人改变呢? – stevemarvell

+0

@stevemarvell我的代码正在观察其他应用程序可以推送更新的输入元素(例如,由于ajax调用,或者由于其他字段的更改)。 – Christophe

+0

我认为你最好的解决方案是实现一个超时监视它 – stevemarvell

回答

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隐藏输入字段的值属性以观察更改和防火墙手动onchange事件。在设定值上改变可见区域的值以给用户反馈。
  2. 关于可见字段值更改更新隐藏的观察者的值。
+0

THX。对,一个问题是没有检测到用户输入。 – Christophe

+0

我们确实收到了更改事件。可以用来确定用户行为。我们正在努力实现通常不被支持的事情。输入字段是为了在程序员或用户不知道它的情况下改变它们的值。 – Mohit

+0

问题是,如果您使用此方法,则无法再使用更改事件来确定用户输入的内容。这是我的问题的重点(比较演示)。 – Christophe

0

由于您已经在使用polyfills进行手表观察等,请让我借此机会向您建议Angularjs

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

这是一个非常简单的方法,但有效的解决方案,以你想要的东西:

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'); 
    } 
}); 
+1

@Hash我肯定会对更多关于Angularjs如何做的信息感兴趣(请注意我刚添加的赏金,我正在寻找一个“详细的规范答案”)。 – Christophe

+0

@Christophe:我用一些基本的代码来解释核心逻辑,并添加了小提琴。 –

+0

演示似乎没有做我想做的事。这里是我尝试以编程方式更改值:http://jsfiddle.net/jv8pK/1/ – Christophe

1

我只需要它的现代浏览器。

你想现代多么现代? Ecma Script 7(6月份将在12月份完成)可能包含Object.observe。这将允许你创建本地观察者。是的,你可以运行它!怎么样?

要试验此功能,您需要在Chrome Canary中启用启用 实验性JavaScript标志并重新启动浏览器。 该标志可以在'about:flags’

更多信息找到:read this

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

+0

+1谢谢,很高兴知道!但肯定太“现代”在这一点上...我需要支持至少IE 11 – Christophe

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/

+1

这不适合我。从MDN网站:_警告:通常应尽可能避免使用watch()和unwatch()。这两种方法实现只在壁虎,他们正在主要用于调试use._来源:Mozilla开发者网络(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ Global_Objects /对象/表) – Christophe

+0

是的,你是对的,但在我的链接,他写他自己的方法观看和取消监视在使用setInterval?试过你了吗? –

+1

对不起,我错过了最后一个链接。是的,我想这一点,我希望得到的东西比一个更好的setInterval(参见问题和评论从赏金) – Christophe

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的解决方案能跨浏览器,但你可以设置您的输入的特性valueeventlistener并运行一个回调一旦value变化由onpropertychange触发值。

+0

不幸的是,这不适用于IE 11或Chrome等浏览器的程序化更改。 – Christophe

1

以下工作我试过它,包括IE11(甚至下到IE9仿真模式)。

它需要你的想法defineProperty远一点通过寻找定义.value setter和修改此setter触发事件的输入元素的原型链中的对象(我已经在本例中把它称为modified),同时仍保持旧的行为。

运行下面的代码片段时,您可以在文本输入框中键入/粘贴/ whatnot,或者您可以单击将" 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>