Mon objectif est d'observer une valeur d'entrée et de déclencher un gestionnaire lorsque sa valeur est modifiée de manière programmatique . Je n'en ai pas besoin que pour les navigateurs modernes.
J'ai essayé de nombreuses combinaisons en utilisant defineProperty
et c'est ma dernière itération:
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
Cela semble faire ce que j'attends, mais cela ressemble à un piratage car je remplace la propriété de valeur existante et que je joue avec le double statut de value
(attribut et propriété). Il brise également l'événement change
qui ne semble pas aimer la propriété modifiée.
Mes autres tentatives:
watch
et observe
polyfills, mais ils se cassent pour une propriété de valeur d'entréeQuelle serait une bonne façon d'obtenir le même résultat?
Démo en direct: http://jsfiddle.net/l7emx/4/
[EDIT] Pour clarifier: Mon code regarde un élément d'entrée dans lequel d'autres applications peuvent appuyer des mises à jour (à la suite d'appels AJAX, par exemple, ou à la suite de changements dans d'autres domaines). Je n'ai aucun contrôle sur la façon dont les autres applications poussent les mises à jour, je ne suis qu'un observateur.
[Modifier 2] pour clarifier ce que je veux dire par "navigateur moderne", je serais très satisfait d'une solution qui fonctionne sur IE 11 et Chrome =. .
[Mettre à jour] Démo mise à jour basée sur la réponse acceptée: http://jsfiddle.net/l7emx/10/
L'astuce suggérée par @ Mohit-Jain est d'ajouter une deuxième entrée pour l'interaction utilisateur.
si le seul problème avec votre solution est la rupture de l'événement de changement sur la valeur définie. Thn vous pouvez tirer cette épreuve manuellement sur ensemble. (Mais ce moniteur Wont est défini dans le cas où un utilisateur modifie l'entrée via navigateur - voir Modifier ci-dessous)
<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>
Plus d'événements de tir manuellement
[~ # ~] Edit [~ # ~ ~]:
Le problème avec la cuisson manuelle d'événement et l'approche de mutateur est que la propriété de valeur ne changera pas lorsque l'utilisateur modifie la valeur du champ du navigateur. Le travail autour est d'utiliser deux champs. un caché avec lequel nous pouvons avoir une interaction programmatique. Un autre est visible avec lequel l'utilisateur peut interagir. Après cette approche de considération est assez simple.
Les œuvres suivantes partout où je l'ai essayée, y compris IE11 (même en mode d'émulation IE9).
Il prend votre idée defineProperty
une idée plus loin en trouvant l'objet dans la chaîne prototype d'élément d'entrée qui définit le .value
Setter et modifier ce setter pour déclencher un événement (je l'ai appelé modified
dans l'exemple), tout en conservant l'ancien comportement.
Lorsque vous exécutez l'extrait ci-dessous, vous pouvez taper/coller dans la zone de saisie de texte, ou vous pouvez cliquer sur le bouton qui ajoute " more"
à l'élément d'entrée .value
. Dans les deux cas, le <span>
Le contenu est mis à jour de manière synchrone.
La seule chose qui n'est pas traitée ici est une mise à jour causée par la définition de l'attribut. Vous pouvez gérer cela avec un MutationObserver
si vous le souhaitez, mais noter qu'il n'y a pas une relation individuelle entre .value
et l'attribut value
(ce dernier n'est que la valeur par défaut pour l'ancien).
// 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>
J'en ai seulement besoin pour les navigateurs modernes.
Comment aimeriez-vous vivre? ECMA Script 7 (6 sera effectué final en décembre) Peut-être contenir ((( objet.observe =. Cela vous permettrait de créer des observables natifs. Et oui, vous pouvez l'exécuter! Comment?
Pour expérimenter cette fonctionnalité, vous devez activer l'indicateur JavaScript Activer dans Chrome Canary et redémarrer le navigateur. Le drapeau peut être trouvé sous
'about:flags’
Plus d'infos: Lisez ceci .
Donc, oui, c'est très expérimental et n'est pas prêt dans l'ensemble actuel des navigateurs. En outre, il n'est toujours pas entièrement prêt et pas à 100% s'il arrive à ES7, et la date finale de l'ES7 n'est même pas encore fixée. Néanmoins, je voulais vous le faire savoir pour une utilisation future.
Puisque vous utilisez déjà des polyfillés pour regarder/observer, etc., permettez-moi de vous suggérer de vous suggérer Angularjs .
Il offre exactement cette fonctionnalité sous la forme de ses modèles NG. Vous pouvez mettre des observateurs sur la valeur du modèle et quand il change, vous pouvez ensuite appeler d'autres fonctions.
Voici une solution très simple, mais de travail à ce que vous voulez:
http://jsfiddle.net/reddevil/jv8pk/
Fondamentalement, faites une entrée de texte et liez-la à un modèle:
<input type="text" data-ng-model="variable">
ensuite, mettez un observateur sur le modèle Angularjs sur cette entrée dans le contrôleur.
$scope.$watch(function() {
return $scope.variable
}, function(newVal, oldVal) {
if(newVal !== null) {
window.alert('programmatically changed');
}
});
C'est une question ancienne, mais avec le nouvel objet proxy JS, déclenchant un événement sur un changement de valeur est assez facile:
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">
Cela crée un objet proxy JS basé sur l'objet DOM d'entrée d'origine. Vous pouvez interagir avec l'objet proxy de la même manière que vous interagiriez avec l'objet DOM Origin. La différence est que nous avons remplacé le setter. Lorsque la "valeur" est modifiée, nous effectuons l'opération normale, attendue, obj[prop] = value
, mais si la valeur de la PROP est "valeur", nous déclenchons un invenant personnalisé.
Notez que vous pouvez tirer le type d'événement que vous voudriez avec new CustomEvent
ou new Event
. "Changement" pourrait être plus approprié.
En savoir plus sur les proxies et les événements personnalisés ici: https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_Objects/proxy
https://developer.mozilla.org/en-us/docs/web/guide/events/create_and_triggering_events
Caniuse: https://caniuse.com/#search=proxy
Il y a un moyen de faire cela. Il n'y a pas d'événement DOM pour cela, mais il existe un événement JavaScript qui déclenche un changement de propriété objet.
document.form1.textfield.watch("value", function(object, oldval, newval){})
^ Object watched ^ ^ ^
|_ property watched | |
|________|____ old and new value
Dans le rappel, vous pouvez faire ce problème.
Dans cet exemple, nous pouvons voir cet effet (cochez la case 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;
lorsque obj
Modifier, il active l'auditeur watch
.
Vous avez plus d'informations dans ce lien: http://james.padolsey.com/javascript/monitoring-dom-properties/
J'ai écrit ce qui suit Gist Il y a un peu de temps, ce qui permet d'écouter les événements personnalisés Cross Naviger (y compris IE8 +).
Regardez comment je suis écoutant pour onpropertychange
sur 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();
}
}
};
Je ne sais pas que la solution IE8 fonctionne le navigateur croisée, mais vous pouvez définir un faux eventlistener
sur la propriété value
de votre entrée et exécutez un rappel une fois la valeur de value
Changements déclenchés par onpropertychange
.