web-dev-qa-db-fra.com

addEventListener sur un objet personnalisé

J'ai créé un objet qui a plusieurs méthodes. Certaines de ces méthodes sont asynchrones et je souhaite donc utiliser des événements pour pouvoir effectuer des actions lorsque les méthodes sont terminées. Pour ce faire, j'ai essayé d'ajouter addEventListener à l'objet.

jsfiddle

var iSubmit = {
    addEventListener: document.addEventListener || document.attachEvent,
    dispatchEvent: document.dispatchEvent,
    fireEvent: document.fireEvent,   


    //the method below is added for completeness, but is not causing the problem.

    test: function(memo) {
        var name = "test";
        var event;
        if (document.createEvent) {
            event = document.createEvent("HTMLEvents");
            event.initEvent(name, true, true);
        } else {
            event = document.createEventObject();
            event.eventType = name;
        }
        event.eventName = name;
        event.memo = memo || { };

        if (document.createEvent) {
            try {
                document.dispatchEvent(event);
            } catch (ex) {
                iAlert.debug(ex, 'iPushError');
            }
        } else {
            document.fireEvent("on" + event.eventType, event);
        }
    }
}

iSubmit.addEventListener("test", function(e) { console.log(e); }, false);


//This call is added to have a complete test. The errors are already triggered with the line before this one.

iSubmit.test();

Cela retournera une erreur: Failed to add eventlisterens: TypeError: 'addEventListener' called on an object that does not implement interface EventTarget."

Maintenant, ce code sera utilisé dans une application phonegap et, lorsque je le ferai, il fonctionnera sous Android/iOS. Lors des tests, cependant, ce serait bien si je pouvais le faire fonctionner dans au moins un navigateur.

PS> Je sais que je pourrais activer le bouillonnement, puis écouter la racine du document, mais j'aimerais simplement avoir un peu OOP où chaque objet peut fonctionner seul.

51
Hugo Delsing

addEventListener est destiné aux éléments DOM qui implémentent certaines interfaces liées aux événements. Si vous souhaitez un système d’événements sur des objets JavaScript pur, vous recherchez un système d’événements personnalisé. Un exemple serait Backbone.Events dans Backbone.js. L'idée de base est d'utiliser un objet comme un hachage pour garder une trace des rappels enregistrés.

Personnellement j'utilise ceci: https://github.com/component/emitter

C'est une solution assez simple et élégante - avec des noms de méthode succincts comme on(), off() et emit(). vous pouvez créer de nouvelles instances avec new Emitter() ou utiliser Emitter(obj) pour combiner les fonctionnalités d'événement dans des objets existants. Notez que cette bibliothèque est écrite pour être utilisée avec un système de module CommonJS, mais vous pouvez l'utiliser n'importe où en supprimant la ligne module.exports = ....

44
Evan You

Si vous voulez écouter un objet javascript, vous avez trois possibilités:

À propos du motif sup/pub:

Vous devez publier des événements.

À propos des implémentations natives:

  • Object get/set operators suffit pour écouter, ajouter, supprimer, modifier, obtenir des événements. Les opérateurs ont un bon support . Problèmes uniquement dans IE8 -. Mais si vous voulez utiliser get/set dans IE8, utilisez Object.defineProperty mais Sur des objets DOM ou utilisez Object.defineProperty sham .
  • Object.prototype.watch a le bon ES5 polyfill .
  • Proxy API nécessite le support ES Harmony.

Exemple Object.observe

var o = {};
Object.observe(o, function (changes) {
  changes.forEach(function (change) {
    // change.object contains changed object version
    console.log('property:', change.name, 'type:', change.type);
  });
});
o.x = 1     // property: x type: add
o.x = 2     // property: x type: update
delete o.x  // property: x type: delete
14
Pinal

Si vous n'avez pas besoin de fonctionnalités d'événement réel (telles que bubbling, stopPropagation), vous pouvez alors implémenter vos propres événements. addEventListener est juste une API du DOM, vous n'en avez donc pas vraiment besoin pour vos propres objets en dehors du DOM. Si vous souhaitez créer un modèle événementiel autour d'un objet, voici une bonne méthode qui ne nécessite aucune API de navigateur supplémentaire et qui doit être très compatible avec les versions antérieures. 

Supposons que vous ayez un objet sur lequel vous souhaitez déclencher un groupe d'événements lorsque la méthode de répartition est appelée:

var OurDispatcher, dispatcher;

OurDispatcher = (function() {
  function OurDispatcher() {
    this.dispatchHandlers = [];
  }

  OurDispatcher.prototype.on = function(eventName, handler) {
    switch (eventName) {
      case "dispatch":
        return this.dispatchHandlers.Push(handler);
      case "somethingElse":
        return alert('write something for this event :)');
    }
  };

  OurDispatcher.prototype.dispatch = function() {
    var handler, i, len, ref;
    ref = this.dispatchHandlers;
    for (i = 0, len = ref.length; i < len; i++) {
      handler = ref[i];
      setTimeout(handler, 0);
    }
  };

  return OurDispatcher;

})();

dispatcher = new OurDispatcher();

dispatcher.on("dispatch", function() {
  return document.body.innerHTML += "DISPATCHED</br>";
});

dispatcher.on("dispatch", function() {
  return document.body.innerHTML += "DISPATCHED AGAIN</br>";
});

dispatcher.dispatch();

Cela n’a pas besoin d’être plus compliqué que cela. De cette façon, vous avez un contrôle décent sur vos événements et vous n'avez pas à vous soucier de la compatibilité ascendante ou des bibliothèques externes car tout y est largement supporté. Techniquement, vous pouvez même vous passer de setTimeout et gérer vos rappels sans aucune API. Tout ce qui est comme stopPropagation () devrait être manipulé vous-même.

https://jsfiddle.net/ozsywxer/

Il existe bien sûr des polyfills pour CustomEvent, mais à moins d’avoir besoin de fonctionnalités d’événement avancées, je préfère envelopper mon propre système d’événement dans une "classe" et en étendre d’autres classes/fonctions.

Voici la version de CoffeeScript, dont le code JavaScript est dérivé: https://jsfiddle.net/vmkkbbxq/1/

^^ Un peu plus facile à comprendre.

13
Ravenstine

Il y a deux problèmes.

Premièrement, la méthode iSubmit.addEventListener() est en réalité une méthode de l’interface EventTarget DOM:

Celles-ci sont destinées à être utilisées uniquement sur des éléments DOM. En l'ajoutant à l'objet iSubmit en tant que méthode, vous l'appelez sur un objet qui n'est pas un EventTarget. C'est pourquoi Chrome génère une erreur JavaScript Uncaught TypeError: Illegal invocation.

Le premier problème est critique, mais si vous pouviez utiliser EventTarget#addEventListener(), votre code ne fonctionnerait pas car l'événement est ajouté à iSubmit mais distribué à partir de document. En règle générale, les méthodes du même objet doivent être utilisées lors de l'attachement d'écouteurs d'événement et de la répartition d'événements (sauf si vous utilisez des événements bouillonnants, qui sont un histoire différente - Remarque: le bouillonnement n'est pas limité aux événements JavaScript ou liés à DOM , par exemple ).

Utiliser des événements personnalisés avec vos propres objets est très normal. Comme Evan Yu a mentionné , il existe des bibliothèques pour cela. Voici un couple:

J'ai utilisé js-signals et l'aime un peu. Je n'ai jamais utilisé Wolfy87/EventEmitter, mais il a un aspect agréable.

Votre exemple pourrait ressembler à ceci si vous avez utilisé js-signals

jsFiddle

var iSubmit = {
    finished: new signals.Signal(),
    test: function test(memo) {
        this.finished.dispatch(memo || {});
    }
};

iSubmit.finished.add(function(data) {
    console.log('finished:', data);
});

iSubmit.test('this is the finished data');


// alternatively
iSubmit.finished.dispatch('this is dispatched directly from the signal');
10
tiffon

Si vous êtes dans un environnement Node.js, vous pouvez utiliser la classe Node's EventEmitter :

CustomObject.js

const EventEmitter = require('events');

class CustomObject extends EventEmitter {
  constructor() {
    super();
  }

  doSomething() {
    const event = {message: 'Hello World!'};
    this.emit('myEventName', event);
  }
}

module.exports = CustomObject;

Utilisation:

const CustomObject = require('./CustomObject');

// 1. Create a new instance
const myObject = new CustomObject();

// 2. Subscribe to events with ID "myEventName"
myObject.on('myEventName', function(event) {
  console.log('Received event', event);
});

// 3. Trigger the event emitter
myObject.doSomething();

Si vous souhaitez utiliser EventEmitter de Node en dehors d'un environnement Node.js, vous pouvez utiliser webpack (de préférence version 2.2 ou ultérieure) pour obtenir un paquet de votre CustomClass avec un polyfill EventEmitter (construit par webpack).

Voici comment cela fonctionne (en supposant que vous avez installé Webpack globalement à l'aide de npm install -g webpack):

  1. Exécuter webpack CustomObject.js bundle.js --output-library=CustomObject
  2. Incluez bundle.js dans votre page HTML (cela exposera window.CustomObject)
  3. Il n'y a pas de troisième étape!

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Title</title>
    <script src="bundle.js"></script>
  </head>
  <body>
    <script>
      // 1. Create a new instance
      const myObject = new window.CustomObject();

      // 2. Subscribe to events with ID "myEventName"
      myObject.on('myEventName', function(event) {
        console.log('Received event', event);
      });

      // 3. Trigger the event emitter
      myObject.doSomething();
    </script>
  </body>
</html>
4
Benny Neugebauer

Juste spéculation; Je n'ai pas essayé moi-même. Mais vous pouvez créer un élément factice et déclencher/écouter des événements sur cet élément factice . En outre, je préfère utiliser des bibliothèques.

function myObject(){
    //create "dummy" element
    var dummy = document.createElement('dummy');
    //method for listening for events
    this.on = function(event, func){dummy.addEventListener(event, func);};
    //you need a way to fire events
    this.fireEvent = function(event, obj){
      dummy.dispatchEvent(new CustomEvent(event, {detail: obj}));
    }
}
//now you can use the methods in the object constructor
var obj = new myObject();
obj.on("custom", function(e){console.log(e.detail.result)});
obj.fireEvent("custom", {result: "hello world!!!"});
2
Rey

Cet article explique la création d'événements personnalisés: http://www.sitepoint.com/javascript-custom-events/

voici un exemple:

créer l'événement -

var event = new CustomEvent(
    "newMessage",
    {
        detail: {
            message: "Hello World!",
            time: new Date(),
        },
        bubbles: true,
        cancelable: true
    }
);

attribuer l'événement à quelque chose - 

document.getElementById("msgbox").dispatchEvent(event);

s'inscrire à l'événement - 

document.addEventListener("newMessage", newMessageHandler, false);
1
Dawson Loudon

Voici comment procéder avec la syntaxe de style Node.js dans le navigateur.

La classe Events:

  • stocke les rappels dans un hachage associé aux clés d'événement
  • déclenche les callbacks avec les paramètres fournis

Pour ajouter le comportement à vos propres classes personnalisées, développez simplement l'objet Events (exemple ci-dessous).

class Events {
  constructor () {
    this._callbacks = {}
  }

  on (key, callback) {
    // create an empty array for the event key
    if (this._callbacks[key] === undefined) { this._callbacks[key] = [] }
    // save the callback in the array for the event key
    this._callbacks[key].Push(callback)
  }

  emit (key, ...params) {
    // if the key exists
    if (this._callbacks[key] !== undefined) {
      // iterate through the callbacks for the event key
      for (let i=0; i<this._callbacks[key].length; i++) {
        // trigger the callbacks with all provided params
        this._callbacks[key][i](...params)
      }
    }
  }
}


// EXAMPLE USAGE

class Thing extends Events {
  constructor () {
    super()
    setInterval(() => {
      this.emit('hello', 'world')
    }, 1000)
  }
}

const thing = new Thing()

thing.on('hello', (data) => {
  console.log(`hello ${data}`)
})

Voici un lien gistub Gist avec ce code: https://Gist.github.com/alextaujenis/0dc81cf4d56513657f685a22bf74893d

0
alextaujenis

Je pense que vous pouvez utiliser Object $ Deferred et promesses . Cela vous permettra de faire quelque chose comme ceci:

Stacked: lier plusieurs gestionnaires n'importe où dans l'application au même événement de promesse.

  var request = $.ajax(url);
  request.done(function () {
  console.log('Request completed');
});

// Quelque part ailleurs dans l'application

   request.done(function (retrievedData) {
     $('#contentPlaceholder').html(retrievedData);
   });

Tâches parallèles: demander à plusieurs promesses de retourner une promesse qui indique leur achèvement mutuel.

$.when(taskOne, taskTwo).done(function () {
  console.log('taskOne and taskTwo are finished');
});

Tâches séquentielles: exécuter les tâches dans un ordre séquentiel.

 var step1, step2, url;

 url = 'http://fiddle.jshell.net';

 step1 = $.ajax(url);

 step2 = step1.then(
   function (data) {
       var def = new $.Deferred();

       setTimeout(function () {
           console.log('Request completed');
           def.resolve();
       },2000);

     return def.promise();

 },
   function (err) {
       console.log('Step1 failed: Ajax request');
   }
 );
 step2.done(function () {
     console.log('Sequence completed')
     setTimeout("console.log('end')",1000);
 });

Source ici: http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use

0
JSG33kC0d3

Utilisation: jsfiddle

C'est une approche naïve mais qui pourrait fonctionner pour certaines applications:

CustomEventTarget.prototype = {

    'constructor': CustomEventTarget,

    on:   function( ev, el ) { this.eventTarget.addEventListener( ev, el ) },
    off:  function( ev, el ) { this.eventTarget.removeEventListener( ev, el ) },
    emit: function( ev ) { this.eventTarget.dispatchEvent( ev ) }

}

function CustomEventTarget() { this.eventTarget = new EventTarget }
0
flcoder