UNPKG

decks

Version:

JavaScript UI library for viewing collections of items.

172 lines (148 loc) 6.06 kB
var _ = require("lodash"); var Emitter = require("./emitter"); var validate = require("../utils/validate"); var browser = require("../utils/browser"); /** * Mixin for objects that need to bind to events from a source object, * and wish to handle events with member functions. * * @mixin */ var binder = { /** * Binds events from the source object to a handler method on 'this' object. * The handler method is automatically bound to 'this', and stored on 'this' using * a prefixed method name, based on the original method name. * * @instance * @param {!*} source object which emits an event, and can be subscribed to. * @param {!Object} eventToMethodMap hash of event name to method name of this. * @return {undefined} */ bindEvents: function bindEvents(source, eventToMethodMap) { validate(source, "source", { isRequired: true }); validate(eventToMethodMap, "eventToMethodMap", { isPlainObject: true }); _.each(eventToMethodMap, function(methodName, eventName) { if (!_.isFunction(this[methodName])) { throw new Error("binder#bindEvents: event target object does not have a method named " + methodName); } var eventNames = this.getEventNames(eventName); _.each(eventNames, function(eventName) { var onMethodName = this.getOnMethodName(source); if (source instanceof Emitter) { // Assuming the on method supports a context arg, so we don't need to _.bind the method source[onMethodName](eventName, this[methodName], this); } else { // Not assuming the on method supports a context arg, so we need to _.bind the method var boundMethodName = this.getBoundMethodName(methodName); if (!_.isFunction(this[boundMethodName])) { this[boundMethodName] = _.bind(this[methodName], this); } // IE8 prefixes the event name with "on" if (onMethodName === "attachEvent") { eventName = "on" + eventName; } source[onMethodName](eventName, this[boundMethodName]); } }, this); }, this); }, /** * Unbinds from events from a given source object, which were previously bound using bindEvents. * * @instance * @param {!*} source source object that emits events * @param {!Object} eventToMethodMap hash of event names to method names of 'this' * @return {undefined} */ unbindEvents: function unbindEvents(source, eventToMethodMap) { validate(source, "source", { isRequired: true }); validate(eventToMethodMap, "eventToMethodMap", { isPlainObject: true }); _.each(eventToMethodMap, function(methodName, eventName) { var eventNames = this.getEventNames(eventName); _.each(eventNames, function(eventName) { var offMethodName = this.getOffMethodName(source, eventName); if (source instanceof Emitter) { // Assuming the off method supports a context arg, so we don't need to _.bind the method source[offMethodName](eventName, this[methodName], this); } else { // Not assuming the off method supports a context arg, get the _.bind copy of the method var boundMethodName = this.getBoundMethodName(methodName); if (!this[boundMethodName]) { return; } // IE8 prefixes event names with "on" (e.g. onmouseup) if (offMethodName === "detachEvent") { eventName = "on" + eventName; } source[offMethodName](eventName, this[boundMethodName]); } }, this); }, this); }, /** * Tries to find a method on the source object which can be used to bind events. * * @instance * @param {!*} source object that emits events * @return {String} first method name that could be used to bind events. */ getOnMethodName: function getOnMethodName(source) { var name = _.find(["on", "addListener", "addEventListener", "attachEvent"], function(name) { // "attachEvent" is not a "function" in IE8 (WTF) return _.isFunction(source[name]) || (browser.isIE8 && source[name]); }); if (!name) { throw new Error("binder#getOnMethodName: event source object does not have an event binding method"); } return name; }, /** * Tries to find a method on the source object which can be used to unbind events. * * @instance * @param {!*} source object which emits events * @return {String} first method name that could be used to unbind events. */ getOffMethodName: function getOffMethodName(source) { var name = _.find(["off", "removeListener", "removeEventListener", "detachEvent"], function(name) { // "detachEvent" is not a "function" in IE8 (WTF) return _.isFunction(source[name]) || (browser.isIE8 && source[name]); }); if (!name) { throw new Error("binder#getOffMethodName: event source object does not have an event unbinding method"); } return name; }, /** * Splits an event name by " " into possibly multiple event names * * @instance * @param {String} eventName - single or space-delimited event name(s) * @return {String[]} - array of event names */ getEventNames: function getEventNames(eventName) { eventName = eventName || ""; return _(eventName.split(" ")) .map(function(name) { return name.trim(); }) .filter(function(name) { return name.length > 0; }) .value(); }, /** * Creates a method name to use for binding a member event handler function to * the target instance. E.g. if your target has a method named "onItemChanged", this method * will return a new function name like "_bound_onItemChanged" which can be used as a new * member name to store the bound member function. * * @param {String} methodName method name to prefix * @return {String} prefixed method name */ getBoundMethodName: function getBoundMethodName(methodName) { return "_bound_" + methodName; } }; module.exports = binder;