web-dev-qa-db-ja.com

プログラムによる入力値の変更時にアクションをトリガーする

私の目的は、入力値を観察し、その値が変更されたときにハンドラーをトリガーすることですプログラムで。最新のブラウザでのみ必要です。

私は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ループですが、これもクリーンではありません
  • さまざまなwatchおよびobserveポリフィル。ただし、入力値プロパティでは機能しません。

同じ結果を達成するための適切な方法は何でしょうか?

ライブデモ: http://jsfiddle.net/L7Emx/4/

[編集]明確にする:私のコードは、他のアプリケーションが更新をプッシュできる入力要素を監視しています(たとえば、ajax呼び出しの結果として、または他のフィールドの変更の結果として)。他のアプリケーションが更新をプッシュする方法を制御できません。私は単なるオブザーバーです。

[編集2]「最新のブラウザ」の意味を明確にするために、IE 11 and Chrome 30 。

[Update]承認された回答に基づいてデモを更新しました: http://jsfiddle.net/L7Emx/10/

@ mohit-jainが提案するトリックは、ユーザーとの対話のために2番目の入力を追加することです。

29
Christophe

ソリューションの唯一の問題が値セットの変更イベントの中断である場合。セットでそのイベントを手動で起動できます。 (ただし、ユーザーがブラウザーを介して入力に変更を加えた場合、これは設定を監視しません-以下の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>

イベントの手動での起動の詳細


[〜#〜]編集[〜#〜]

手動イベントの発生とミューテーターアプローチの問題は、ユーザーがブラウザーからフィールド値を変更しても、valueプロパティが変更されないことです。回避策は2つのフィールドを使用することです。プログラムで操作できる非表示のもの。もう1つは、ユーザーが操作できるものです。この検討後のアプローチは十分簡単です。

  1. 非表示の入力フィールドの値プロパティを変更して、変更を監視し、手動のonchangeイベントを発生させます。設定された値で可視フィールドの値を変更して、ユーザーにフィードバックを提供します。
  2. 可視フィールド値の変更時に、オブザーバーの非表示の値を更新します。
16
Mohit

以下は、IE11(IE9エミュレーションモードまでも含む)を含め、私が試したすべての場所で機能します。

.valueセッターを定義する入力要素プロトタイプチェーンでオブジェクトを検索し、このセッターを変更してイベントをトリガーすることで、definePropertyのアイデアを少し先に進めます(これをmodified(例では)、以前の動作を維持しています。

以下のスニペットを実行する場合、テキスト入力ボックスに/貼り付け/ whatnotと入力するか、入力要素の" more".valueを追加するボタンをクリックできます。どちらの場合でも、<span>のコンテンツは同期的に更新されます。

ここで処理されない唯一のものは、属性の設定によって引き起こされる更新です。必要に応じてMutationObserverで処理できますが、.valuevalue属性の間に1対1の関係がないことに注意してください(後者は単なる- デフォルト前者の値)。

// 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>
2
balpha

最新のブラウザでのみ必要です。

どのくらいモダンに行きたいですか? Ecmaスクリプト7(6月は最終的に作成されます)might含まれる Object.observe 。これにより、ネイティブのオブザーバブルを作成できます。そして、はい、それを実行できます!どうやって?

この機能を試すには、Chrome Canaryの[JavaScriptを有効にする]フラグを有効にしてブラウザーを再起動する必要があります。このフラグは'about:flags’

詳細: これを読む

ええ、これは非常に実験的なものであり、現在のブラウザのセットでは準備ができていません。また、ES7に対応する場合、まだ完全には準備されておらず、100%でもありません。また、ES7の最終日はまだ設定されていません。それでも、今後の使用のためにお知らせしておきたいと思います。

1
MarcoK

あなたはすでにウォッチ/観察などのためにポリフィルを使用しているので、あなたに提案する機会を与えましょう 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
kumarharsh

これは古い質問ですが、新しい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プロキシオブジェクトが作成されます。 Origin DOMオブジェクトを操作するのと同じ方法で、プロキシオブジェクトを操作できます。違いは、セッターをオーバーライドしたことです。 '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

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

0
SethWhite

これを行う方法があります。これには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/

0
Donovan Charpin

私は少し前に次の 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を設定し、valueの値が返されたらコールバックを実行できます。 onpropertychangeによってトリガーされた変更。

0
frequent