web-dev-qa-db-fra.com

Action de déclenchement sur le changement de programmation à une valeur d'entrée

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:

  • une boucle de sécurité/seinterval, mais ce n'est pas propre non plus
  • divers watch et observe polyfills, mais ils se cassent pour une propriété de valeur d'entrée

Quelle 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.

29
Christophe

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.

  1. mutate Valeur Propriété sur le champ d'entrée caché Pour observer les modifications et les événements manuels d'incendie. Sur la valeur définie, modifiez la valeur du champ visible pour donner des commentaires de l'utilisateur.
  2. sur la valeur visible de la valeur change de mise à jour de la valeur de cachée pour l'observateur.
16
Mohit

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

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.

1
MarcoK

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

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

0
SethWhite

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/

0
Donovan Charpin

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.

0
frequent