index.js
/*!
* dual-emitter <https://github.com/tunnckoCore/dual-emitter>
*
* Copyright (c) 2015 Charlike Mike Reagent <@tunnckoCore> (http://www.tunnckocore.tk)
* Released under the MIT license.
*/
/* jshint asi:true */
'use strict'
var util = require('util')
/**
* Expose `DualEmitter`
*/
module.exports = DualEmitter
/**
* > Create a new instance of `DualEmitter`.
*
* **Example**
*
* ```js
* var DualEmitter = require('dual-emitter')
* var emitter = new DualEmitter()
* ```
*
* @param {Object} `[events]` Initialize with default events.
* @api public
*/
function DualEmitter (events) {
if (!(this instanceof DualEmitter)) {
return new DualEmitter(events)
}
this._events = events && typeof events === 'object' ? events : {}
}
/**
* > Add/bind event listener to custom or DOM event.
* Notice that `this` in event handler function vary - it can be the DOM element
* or DualEmitter instance.
*
* **Example**
*
* ```js
* function handler (a, b) {
* console.log('hi', a, b) //=> hi 123 bar
* }
*
* function onclick (evt) {
* console.log(evt, 'clicked')
* }
*
* var element = document.body.querySelector('a.link')
*
* emitter.on('custom', handler).emit('custom', 123, 'bar')
* emitter.on('click', onclick, element).off('click', onclick, element)
* ```
*
* @param {String} `<name>` event name
* @param {Function} `<fn>` event handler
* @param {Object} `[el]` optional DOM element
* @return {DualEmitter} DualEmitter for chaining
* @api public
*/
DualEmitter.prototype.on = function on (name, fn, el) {
if (typeof name !== 'string') {
throw new TypeError('DualEmitter#on expect `name` be string')
}
if (typeof fn !== 'function') {
throw new TypeError('DualEmitter#on expect `fn` be function')
}
this._events[name] = this._hasOwn(this._events, name) ? this._events[name] : []
this._events[name].push(fn)
if (el && this._isDom(el)) {
fn.outerHTML = el.outerHTML
this._element = el
el.addEventListener ? el.addEventListener(name, fn, false) : el.attachEvent('on' + name, fn)
}
return this
}
/**
* > Remove/unbind event listener of custom or DOM event.
*
* **Example**
*
* ```js
* var element = document.body.querySelector('a.link')
* emitter.off('custom', handler)
* emitter.off('click', onclick, element)
* ```
*
* @param {String} `<name>` event name
* @param {Function} `<fn>` event handler
* @param {Object} `[el]` optional DOM element
* @return {DualEmitter} DualEmitter for chaining
* @api public
*/
DualEmitter.prototype.off = function off (name, fn, el) {
if (typeof name !== 'string') {
throw new TypeError('DualEmitter#off expect `name` be string')
}
if (typeof fn !== 'function') {
throw new TypeError('DualEmitter#off expect `fn` be function')
}
if (!this._hasOwn(this._events, name)) {return this}
this._events[name].splice(this._events[name].indexOf(fn), 1)
if (el && this._isDom(el)) {
el.removeEventListener ? el.removeEventListener(name, fn, false) : el.detachEvent('on' + name, fn)
}
return this
}
/**
* > Add one-time event listener to custom or DOM event.
* Notice that `this` in event handler function vary - it can be the DOM element
* or DualEmitter instance.
*
* **Example**
*
* ```js
* emitter
* .once('custom', function () {
* console.log('executed one time')
* })
* .emit('custom')
* .emit('custom')
*
* var element = document.body.querySelector('a.link')
* emitter.once('click', function () {
* console.log('listen for click event only once')
* }, element)
* ```
*
* @param {String} `<name>` event name
* @param {Function} `<fn>` event handler
* @param {Object} `[el]` optional DOM element
* @return {DualEmitter} DualEmitter for chaining
* @api public
*/
DualEmitter.prototype.once = function once (name, fn, el) {
var self = this
function handler () {
self.off(name, handler, el)
return fn.apply(el, arguments)
}
return this.on(name, handler, el)
}
/**
* > Emit/execute some type of event listener.
* You also can emit DOM events if last argument
* is the DOM element that have attached event listener.
*
* **Example**
*
* ```js
* var i = 0
*
* emitter
* .on('custom', function () {
* console.log('i ==', i++, arguments)
* })
* .emit('custom')
* .emit('custom', 123)
* .emit('custom', 'foo', 'bar', 'baz')
* .emit('custom', [1, 2, 3], 4, 5)
*
* // or even emit DOM events, but you should
* // give the element as last argument to `.emit` method
* var element = document.body.querySelector('a.link')
* var clicks = 0
*
* emitter
* .on('click', function (a) {
* console.log(a, 'clicked', clicks++)
* console.log(this.textContent) // content of <a> tag
* }, element)
* .emit('click', 123, element)
* .emit('click', element)
* .emit('click', foo, element)
* ```
*
* @param {String} `<name>` event name
* @param {Mixed} `[args...]` context to pass to event listeners
* @param {Object} `[el]` optional DOM element
* @return {DualEmitter} DualEmitter for chaining
* @api public
*/
DualEmitter.prototype.emit = function emit (name) {
if (!this._hasOwn(this._events, name)) {return this}
var args = Array.prototype.slice.call(arguments, 1)
var el = args[args.length - 1]
var isdom = this._isDom(el)
el = isdom ? el : this
args = isdom ? args.slice(0, -1) : args
for (var i = 0; i < this._events[name].length; i++) {
var fn = this._events[name][i]
if (isdom && fn.outerHTML !== el.outerHTML) {
continue
}
fn.apply(el, args)
}
return this
}
/**
* > Check that given `val` is DOM element. Used internally.
*
* **Example**
*
* ```js
* var element = document.body.querySelector('a.link')
*
* emitter._isDom(element) //=> true
* emitter._isDom({a: 'b'}) //=> false
* ```
*
* @param {Mixed} `val`
* @return {Boolean}
* @api public
*/
DualEmitter.prototype._isDom = function isDom (val) {
val = Object.prototype.toString.call(val).slice(8, -1)
return /(?:HTML)?(?:.*)Element/.test(val)
}
/**
* > Check that `key` exists in the given `obj`.
*
* **Example**
*
* ```js
* var obj = {a: 'b'}
*
* emitter._hasOwn(obj, 'a') //=> true
* emitter._hasOwn(obj, 'foo') //=> false
* ```
*
* @param {Object} `obj`
* @param {String} `key`
* @return {Boolean}
* @api public
*/
DualEmitter.prototype._hasOwn = function hasOwn (obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key)
}
/**
* Static method for inheriting both the prototype and
* static methods of the `DualEmitter` class.
*
* ```js
* function MyApp(options) {
* DualEmitter.call(this)
* }
* DualEmitter.extend(MyApp)
*
*
* // Optionally pass another object to extend onto `MyApp`
* function MyApp(options) {
* DualEmitter.call(this)
* Foo.call(this, options)
* }
* DualEmitter.extend(MyApp, Foo.prototype)
* ```
*
* @param {Function} `Ctor` The constructor to extend.
* @api public
*/
DualEmitter.extend = function (Ctor, proto) {
util.inherits(Ctor, DualEmitter)
for (var key in DualEmitter) {
Ctor[key] = DualEmitter[key]
}
if (typeof proto === 'object') {
var obj = Object.create(proto)
for (var k in obj) {
Ctor.prototype[k] = obj[k]
}
}
}