web-dev-qa-db-ja.com

カスタムオブジェクトのaddEventListener

いくつかのメソッドを持つオブジェクトを作成しました。これらのメソッドの一部は非同期であるため、メソッドが完了したときにイベントを使用してアクションを実行できるようにします。これを行うために、オブジェクトにaddEventListenerを追加しようとしました。

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();

これはエラーを返します:Failed to add eventlisterens: TypeError: 'addEventListener' called on an object that does not implement interface EventTarget."

これで、このコードはphonegapアプリで使用され、実行するとAndroid/iosで動作します。ただし、テスト中に、少なくとも1つのブラウザーで動作するようになればいいですね。

PS>バブリングを有効にしてドキュメントルートをリッスンできることはわかっていますが、各オブジェクトが単独で機能する場所を少しだけOOPとする必要があります。

52
Hugo Delsing

addEventListenerは、特定のイベント関連インターフェイスを実装するDOM要素を対象としています。純粋なJavaScriptオブジェクトのイベントシステムが必要な場合は、カスタムイベントシステムを探しています。例は、Backbone.jsの_Backbone.Events_です。基本的な考え方は、オブジェクトをハッシュとして使用して、登録されたコールバックを追跡することです。

個人的に私はこれを使用します: エミッタ

on()off()、およびemit()などの甘い短いメソッド名を使用した、非常にシンプルでエレガントなソリューションです。 new Emitter()を使用して新しいインスタンスを作成するか、Emitter(obj)を使用してイベント機能を既存のオブジェクトに混在させることができます。このライブラリはCommonJSモジュールシステムで使用するために記述されていますが、_module.exports = ..._行を削除することで他の場所で使用できます。

46
Evan You

真のイベント機能(バブル、stopPropagationなど)が必要ない場合は、独自のイベントを実装できます。 addEventListenerはDOMの単なるAPIであるため、DOMの外部にある独自のオブジェクトには本当に必要ありません。オブジェクトの周りにイベント化されたパターンを作成したい場合、追加のブラウザAPIを必要とせず、非常に下位互換性のある、それを行う良い方法があります。

ディスパッチメソッドが呼び出されたときに多数のイベントをトリガーするオブジェクトがあるとします。

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();

たいていの場合、それ以上に複雑である必要はありません。これにより、イベントを適切に制御でき、すべてが広くサポートされているため、後方互換性や外部ライブラリについて心配する必要はありません。技術的には、setTimeoutなしでも実行でき、APIなしでコールバックを処理できます。 stopPropagation()のような他のものはすべて自分で処理する必要があります。

https://jsfiddle.net/ozsywxer/

もちろん、CustomEventにはポリフィルがありますが、高度なイベント機能が必要でない限り、自分のイベントシステムを「クラス」にラップし、それで他のクラス/関数を拡張することを好みます。

以下にCoffeeScriptバージョンを示します。これはJavaScriptの派生元です。 https://jsfiddle.net/vmkkbbxq/1/

^^わかりやすくなりました。

15
Ravenstine

Javascriptオブジェクトをリッスンする場合、次の3つの方法があります。

sup/pubパターンについて:

イベントを公開する必要があります。

ネイティブ実装について:

  • Object get/set operatorsは、追加、削除、変更、イベントの取得をリッスンするのに十分です。演算子には support があります。 IE8-のみの問題。ただし、IE8でget/setを使用する場合は、Object.definePropertyを使用しますが、DOMオブジェクトではObject.defineProperty sham を使用します。
  • Object.prototype.watchには優れたES5があります polyfill
  • Proxy APIにはES Harmonyサポートが必要です。

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
15
Pinal

2つの問題があります。

まず、iSubmit.addEventListener()メソッドは、実際にはEventTarget DOMインターフェースのメソッドです。

これらは、DOM要素でのみ使用するためのものです。メソッドとしてiSubmitオブジェクトに追加することにより、EventTargetではないオブジェクトで呼び出します。 Chrome=が_Uncaught TypeError: Illegal invocation_JavaScriptエラーをスローする理由です。

最初の問題は重大ですが、EventTarget#addEventListener()を使用できた場合、イベントはiSubmitに追加されますが、documentからディスパッチされるため、コードは機能しません。通常、イベントリスナーをアタッチしてイベントをディスパッチするときは、同じオブジェクトのメソッドを使用する必要があります( 異なるストーリー であるバブリングイベントを使用している場合を除きます):バブリングはJavaScriptまたはDOM関連に限定されませんイベント、 たとえば )。

独自のオブジェクトでカスタムイベントを使用するのはごく普通のことです。 Evan Yuの言及 のように、これにはライブラリがあります。ここにカップルがあります:

_js-signals_を使用しましたが、かなり気に入っています。 _Wolfy87/EventEmitter_を使用したことはありませんが、見た目はいいです。

_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

Node.js環境にいる場合、 Nodeの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;

使用法:

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();

Node.js環境外でNodeのEventEmitterを使用する場合は、 webpack (できればv2.2以降)を使用してCustomClassのバンドルを取得し、 EventEmitterポリフィル(webpackにより構築)。

仕組みは次のとおりです(npm install -g webpackを使用してwebpackをグローバルにインストールしたと仮定):

  1. webpack CustomObject.js bundle.js --output-library=CustomObjectを実行します
  2. HTMLページにbundle.jsを含めます(window.CustomObjectを公開します)
  3. ステップ3はありません!

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

単なる憶測です。私は自分で試したことはありません。ただし、ダミー要素を作成し、ダミー要素のイベントを発火/聞くことができます。また、私はライブラリなしで行くことを好みます。

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!!!"});
4
Rey

この記事では、カスタムイベントの作成について説明します。 http://www.sitepoint.com/javascript-custom-events/

以下に例を示します。

イベントを作成する-

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

イベントを何かに割り当てる-

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

イベントを購読する-

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

ブラウザーでNode.jsスタイルの構文を使用してこれを行う方法を次に示します。

Eventsクラス:

  • イベントキーに関連付けられたハッシュにコールバックを格納します
  • 指定されたパラメーターでコールバックをトリガーします

独自のカスタムクラスに動作を追加するには、Eventsオブジェクトを拡張するだけです(以下の例)。

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}`)
})

Github Gistとこのコードのリンクを次に示します。 https://Gist.github.com/alextaujenis/0dc81cf4d56513657f685a22bf74893d

0
alextaujenis

Object $ Deferredとpromiseを使用できると思います。次のようなことができます。

スタック:アプリケーション内の複数のハンドラーを同じpromiseイベントにバインドします。

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

//アプリケーション内の別の場所

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

並列タスク:相互の完了を警告するpromiseを返すように複数のpromiseに依頼します。

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

順次タスク:タスクを順次実行します。

 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);
 });

ここのソース: http://blog.mediumequalsmessage.com/promise-deferred-objects-in-javascript-pt2-practical-use

0
JSG33kC0d3

使用法: jsfiddle

これは単純なアプローチですが、一部のアプリケーションでは機能する場合があります。

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

簡単な答えを探している人のために。私は このドキュメント にアクセスしましたが、これはほとんどのブラウザがサポートしていないことを知るためだけです。しかし、ページの下部に このGitHubページ へのリンクがあり、これは基本的にObject.watch()とObject.unwatch()が行うことを行い、私にとってはうまくいきます!

変更を監視する方法は次のとおりです

/*
 * object.watch polyfill
 *
 * 2012-04-03
 *
 * By Eli Grey, http://eligrey.com
 * Public Domain.
 * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
 * https://Gist.github.com/eligrey/384583
 */

// object.watch
if (!Object.prototype.watch) {
    Object.defineProperty(Object.prototype, "watch", {
          enumerable: false
        , configurable: true
        , writable: false
        , value: function (prop, handler) {
            var
              oldval = this[prop]
            , newval = oldval
            , getter = function () {
                return newval;
            }
            , setter = function (val) {
                oldval = newval;
                return newval = handler.call(this, prop, oldval, val);
            }
            ;

            if (delete this[prop]) { // can't watch constants
                Object.defineProperty(this, prop, {
                      get: getter
                    , set: setter
                    , enumerable: true
                    , configurable: true
                });
            }
        }
    });
}

// object.unwatch
if (!Object.prototype.unwatch) {
    Object.defineProperty(Object.prototype, "unwatch", {
          enumerable: false
        , configurable: true
        , writable: false
        , value: function (prop) {
            var val = this[prop];
            delete this[prop]; // remove accessors
            this[prop] = val;
        }
    });
}

そして、これはあなたのコードでなければなりません:

var object = {
    value: null,
    changeValue: function(newValue) {
        this.value = newValue;
    },
    onChange: function(callback) {
        this.watch('value', function(obj, oldVal, newVal) {
            // obj will return the object that received a change
            // oldVal is the old value from the object
            // newVal is the new value from the object

            callback();
            console.log("Object "+obj+"'s value got updated from '"+oldValue+"' to '"+newValue+"'");
            // object.changeValue("hello world");
            // returns "Object object.value's value got updated from 'null' to 'hello world'";

            // and if you want the function to stop checking for
            // changes you can always unwatch it with:
            this.unwatch('value');

            // you can retrieve information such as old value, new value
            // and the object with the .watch() method, learn more here:
            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch
        })
    }
};

または次のように短い:

var object = { user: null };

// add a watch to 'user' value from object
object.watch('user', function() {
    // object user value changed
});
0
iStudLion