backbone.radio.js (10363B)
1 /*! elementor - v0.7.1 - 18-08-2016 */ 2 // Backbone.Radio v1.0.4 3 4 (function (global, factory) { 5 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('underscore'), require('backbone')) : 6 typeof define === 'function' && define.amd ? define(['underscore', 'backbone'], factory) : 7 (global.Backbone = global.Backbone || {}, global.Backbone.Radio = factory(global._,global.Backbone)); 8 }(this, function (_,Backbone) { 'use strict'; 9 10 _ = 'default' in _ ? _['default'] : _; 11 Backbone = 'default' in Backbone ? Backbone['default'] : Backbone; 12 13 var babelHelpers = {}; 14 babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 15 return typeof obj; 16 } : function (obj) { 17 return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; 18 }; 19 babelHelpers; 20 21 var previousRadio = Backbone.Radio; 22 23 var Radio = Backbone.Radio = {}; 24 25 Radio.VERSION = '1.0.4'; 26 27 // This allows you to run multiple instances of Radio on the same 28 // webapp. After loading the new version, call `noConflict()` to 29 // get a reference to it. At the same time the old version will be 30 // returned to Backbone.Radio. 31 Radio.noConflict = function () { 32 Backbone.Radio = previousRadio; 33 return this; 34 }; 35 36 // Whether or not we're in DEBUG mode or not. DEBUG mode helps you 37 // get around the issues of lack of warnings when events are mis-typed. 38 Radio.DEBUG = false; 39 40 // Format debug text. 41 Radio._debugText = function (warning, eventName, channelName) { 42 return warning + (channelName ? ' on the ' + channelName + ' channel' : '') + ': "' + eventName + '"'; 43 }; 44 45 // This is the method that's called when an unregistered event was called. 46 // By default, it logs warning to the console. By overriding this you could 47 // make it throw an Error, for instance. This would make firing a nonexistent event 48 // have the same consequence as firing a nonexistent method on an Object. 49 Radio.debugLog = function (warning, eventName, channelName) { 50 if (Radio.DEBUG && console && console.warn) { 51 console.warn(Radio._debugText(warning, eventName, channelName)); 52 } 53 }; 54 55 var eventSplitter = /\s+/; 56 57 // An internal method used to handle Radio's method overloading for Requests. 58 // It's borrowed from Backbone.Events. It differs from Backbone's overload 59 // API (which is used in Backbone.Events) in that it doesn't support space-separated 60 // event names. 61 Radio._eventsApi = function (obj, action, name, rest) { 62 if (!name) { 63 return false; 64 } 65 66 var results = {}; 67 68 // Handle event maps. 69 if ((typeof name === 'undefined' ? 'undefined' : babelHelpers.typeof(name)) === 'object') { 70 for (var key in name) { 71 var result = obj[action].apply(obj, [key, name[key]].concat(rest)); 72 eventSplitter.test(key) ? _.extend(results, result) : results[key] = result; 73 } 74 return results; 75 } 76 77 // Handle space separated event names. 78 if (eventSplitter.test(name)) { 79 var names = name.split(eventSplitter); 80 for (var i = 0, l = names.length; i < l; i++) { 81 results[names[i]] = obj[action].apply(obj, [names[i]].concat(rest)); 82 } 83 return results; 84 } 85 86 return false; 87 }; 88 89 // An optimized way to execute callbacks. 90 Radio._callHandler = function (callback, context, args) { 91 var a1 = args[0], 92 a2 = args[1], 93 a3 = args[2]; 94 switch (args.length) { 95 case 0: 96 return callback.call(context); 97 case 1: 98 return callback.call(context, a1); 99 case 2: 100 return callback.call(context, a1, a2); 101 case 3: 102 return callback.call(context, a1, a2, a3); 103 default: 104 return callback.apply(context, args); 105 } 106 }; 107 108 // A helper used by `off` methods to the handler from the store 109 function removeHandler(store, name, callback, context) { 110 var event = store[name]; 111 if ((!callback || callback === event.callback || callback === event.callback._callback) && (!context || context === event.context)) { 112 delete store[name]; 113 return true; 114 } 115 } 116 117 function removeHandlers(store, name, callback, context) { 118 store || (store = {}); 119 var names = name ? [name] : _.keys(store); 120 var matched = false; 121 122 for (var i = 0, length = names.length; i < length; i++) { 123 name = names[i]; 124 125 // If there's no event by this name, log it and continue 126 // with the loop 127 if (!store[name]) { 128 continue; 129 } 130 131 if (removeHandler(store, name, callback, context)) { 132 matched = true; 133 } 134 } 135 136 return matched; 137 } 138 139 /* 140 * tune-in 141 * ------- 142 * Get console logs of a channel's activity 143 * 144 */ 145 146 var _logs = {}; 147 148 // This is to produce an identical function in both tuneIn and tuneOut, 149 // so that Backbone.Events unregisters it. 150 function _partial(channelName) { 151 return _logs[channelName] || (_logs[channelName] = _.partial(Radio.log, channelName)); 152 } 153 154 _.extend(Radio, { 155 156 // Log information about the channel and event 157 log: function log(channelName, eventName) { 158 if (typeof console === 'undefined') { 159 return; 160 } 161 var args = _.drop(arguments, 2); 162 console.log('[' + channelName + '] "' + eventName + '"', args); 163 }, 164 165 // Logs all events on this channel to the console. It sets an 166 // internal value on the channel telling it we're listening, 167 // then sets a listener on the Backbone.Events 168 tuneIn: function tuneIn(channelName) { 169 var channel = Radio.channel(channelName); 170 channel._tunedIn = true; 171 channel.on('all', _partial(channelName)); 172 return this; 173 }, 174 175 // Stop logging all of the activities on this channel to the console 176 tuneOut: function tuneOut(channelName) { 177 var channel = Radio.channel(channelName); 178 channel._tunedIn = false; 179 channel.off('all', _partial(channelName)); 180 delete _logs[channelName]; 181 return this; 182 } 183 }); 184 185 /* 186 * Backbone.Radio.Requests 187 * ----------------------- 188 * A messaging system for requesting data. 189 * 190 */ 191 192 function makeCallback(callback) { 193 return _.isFunction(callback) ? callback : function () { 194 return callback; 195 }; 196 } 197 198 Radio.Requests = { 199 200 // Make a request 201 request: function request(name) { 202 var args = _.rest(arguments); 203 var results = Radio._eventsApi(this, 'request', name, args); 204 if (results) { 205 return results; 206 } 207 var channelName = this.channelName; 208 var requests = this._requests; 209 210 // Check if we should log the request, and if so, do it 211 if (channelName && this._tunedIn) { 212 Radio.log.apply(this, [channelName, name].concat(args)); 213 } 214 215 // If the request isn't handled, log it in DEBUG mode and exit 216 if (requests && (requests[name] || requests['default'])) { 217 var handler = requests[name] || requests['default']; 218 args = requests[name] ? args : arguments; 219 return Radio._callHandler(handler.callback, handler.context, args); 220 } else { 221 Radio.debugLog('An unhandled request was fired', name, channelName); 222 } 223 }, 224 225 // Set up a handler for a request 226 reply: function reply(name, callback, context) { 227 if (Radio._eventsApi(this, 'reply', name, [callback, context])) { 228 return this; 229 } 230 231 this._requests || (this._requests = {}); 232 233 if (this._requests[name]) { 234 Radio.debugLog('A request was overwritten', name, this.channelName); 235 } 236 237 this._requests[name] = { 238 callback: makeCallback(callback), 239 context: context || this 240 }; 241 242 return this; 243 }, 244 245 // Set up a handler that can only be requested once 246 replyOnce: function replyOnce(name, callback, context) { 247 if (Radio._eventsApi(this, 'replyOnce', name, [callback, context])) { 248 return this; 249 } 250 251 var self = this; 252 253 var once = _.once(function () { 254 self.stopReplying(name); 255 return makeCallback(callback).apply(this, arguments); 256 }); 257 258 return this.reply(name, once, context); 259 }, 260 261 // Remove handler(s) 262 stopReplying: function stopReplying(name, callback, context) { 263 if (Radio._eventsApi(this, 'stopReplying', name)) { 264 return this; 265 } 266 267 // Remove everything if there are no arguments passed 268 if (!name && !callback && !context) { 269 delete this._requests; 270 } else if (!removeHandlers(this._requests, name, callback, context)) { 271 Radio.debugLog('Attempted to remove the unregistered request', name, this.channelName); 272 } 273 274 return this; 275 } 276 }; 277 278 /* 279 * Backbone.Radio.channel 280 * ---------------------- 281 * Get a reference to a channel by name. 282 * 283 */ 284 285 Radio._channels = {}; 286 287 Radio.channel = function (channelName) { 288 if (!channelName) { 289 throw new Error('You must provide a name for the channel.'); 290 } 291 292 if (Radio._channels[channelName]) { 293 return Radio._channels[channelName]; 294 } else { 295 return Radio._channels[channelName] = new Radio.Channel(channelName); 296 } 297 }; 298 299 /* 300 * Backbone.Radio.Channel 301 * ---------------------- 302 * A Channel is an object that extends from Backbone.Events, 303 * and Radio.Requests. 304 * 305 */ 306 307 Radio.Channel = function (channelName) { 308 this.channelName = channelName; 309 }; 310 311 _.extend(Radio.Channel.prototype, Backbone.Events, Radio.Requests, { 312 313 // Remove all handlers from the messaging systems of this channel 314 reset: function reset() { 315 this.off(); 316 this.stopListening(); 317 this.stopReplying(); 318 return this; 319 } 320 }); 321 322 /* 323 * Top-level API 324 * ------------- 325 * Supplies the 'top-level API' for working with Channels directly 326 * from Backbone.Radio. 327 * 328 */ 329 330 var channel; 331 var args; 332 var systems = [Backbone.Events, Radio.Requests]; 333 _.each(systems, function (system) { 334 _.each(system, function (method, methodName) { 335 Radio[methodName] = function (channelName) { 336 args = _.rest(arguments); 337 channel = this.channel(channelName); 338 return channel[methodName].apply(channel, args); 339 }; 340 }); 341 }); 342 343 Radio.reset = function (channelName) { 344 var channels = !channelName ? this._channels : [this._channels[channelName]]; 345 _.invoke(channels, 'reset'); 346 }; 347 348 return Radio; 349 350 }));