/*! * 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} `` event name * @param {Function} `` 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} `` event name * @param {Function} `` 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} `` event name * @param {Function} `` 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 tag * }, element) * .emit('click', 123, element) * .emit('click', element) * .emit('click', foo, element) * ``` * * @param {String} `` 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] } } }