私の目的は、入力値を観察し、その値が変更されたときにハンドラーをトリガーすることですプログラムで。最新のブラウザでのみ必要です。
私は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
イベントを壊します。
私の他の試み:
watch
およびobserve
ポリフィル。ただし、入力値プロパティでは機能しません。同じ結果を達成するための適切な方法は何でしょうか?
ライブデモ: http://jsfiddle.net/L7Emx/4/
[編集]明確にする:私のコードは、他のアプリケーションが更新をプッシュできる入力要素を監視しています(たとえば、ajax呼び出しの結果として、または他のフィールドの変更の結果として)。他のアプリケーションが更新をプッシュする方法を制御できません。私は単なるオブザーバーです。
[編集2]「最新のブラウザ」の意味を明確にするために、IE 11 and Chrome 30 。
[Update]承認された回答に基づいてデモを更新しました: http://jsfiddle.net/L7Emx/10/
@ mohit-jainが提案するトリックは、ユーザーとの対話のために2番目の入力を追加することです。
ソリューションの唯一の問題が値セットの変更イベントの中断である場合。セットでそのイベントを手動で起動できます。 (ただし、ユーザーがブラウザーを介して入力に変更を加えた場合、これは設定を監視しません-以下の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つは、ユーザーが操作できるものです。この検討後のアプローチは十分簡単です。
以下は、IE11(IE9エミュレーションモードまでも含む)を含め、私が試したすべての場所で機能します。
.value
セッターを定義する入力要素プロトタイプチェーンでオブジェクトを検索し、このセッターを変更してイベントをトリガーすることで、defineProperty
のアイデアを少し先に進めます(これをmodified
(例では)、以前の動作を維持しています。
以下のスニペットを実行する場合、テキスト入力ボックスに/貼り付け/ whatnotと入力するか、入力要素の" more"
に.value
を追加するボタンをクリックできます。どちらの場合でも、<span>
のコンテンツは同期的に更新されます。
ここで処理されない唯一のものは、属性の設定によって引き起こされる更新です。必要に応じてMutationObserver
で処理できますが、.value
とvalue
属性の間に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>
最新のブラウザでのみ必要です。
どのくらいモダンに行きたいですか? Ecmaスクリプト7(6月は最終的に作成されます)might含まれる Object.observe 。これにより、ネイティブのオブザーバブルを作成できます。そして、はい、それを実行できます!どうやって?
この機能を試すには、Chrome Canaryの[JavaScriptを有効にする]フラグを有効にしてブラウザーを再起動する必要があります。このフラグは
'about:flags’
詳細: これを読む 。
ええ、これは非常に実験的なものであり、現在のブラウザのセットでは準備ができていません。また、ES7に対応する場合、まだ完全には準備されておらず、100%でもありません。また、ES7の最終日はまだ設定されていません。それでも、今後の使用のためにお知らせしておきたいと思います。
あなたはすでにウォッチ/観察などのためにポリフィルを使用しているので、あなたに提案する機会を与えましょう 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');
}
});
これは古い質問ですが、新しい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
これを行う方法があります。これには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ソリューションがブラウザー間で機能するかどうかはわかりませんが、入力のプロパティeventlistener
に偽のvalue
を設定し、value
の値が返されたらコールバックを実行できます。 onpropertychange
によってトリガーされた変更。