blob: 66de72f92eb31150e3e8dbcd3c70173c1c3419fa [file] [log] [blame]
Scott Baker7ee32a02014-07-13 09:52:15 -07001// Backbone.Wreqr (Backbone.Marionette)
2// ----------------------------------
3// v1.3.1
4//
5// Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
6// Distributed under MIT license
7//
8// http://github.com/marionettejs/backbone.wreqr
9
10
11(function(root, factory) {
12
13 if (typeof define === 'function' && define.amd) {
14 define(['backbone', 'underscore'], function(Backbone, _) {
15 return factory(Backbone, _);
16 });
17 } else if (typeof exports !== 'undefined') {
18 var Backbone = require('backbone');
19 var _ = require('underscore');
20 module.exports = factory(Backbone, _);
21 } else {
22 factory(root.Backbone, root._);
23 }
24
25}(this, function(Backbone, _) {
26 "use strict";
27
28 var previousWreqr = Backbone.Wreqr;
29
30 var Wreqr = Backbone.Wreqr = {};
31
32 Backbone.Wreqr.VERSION = '1.3.1';
33
34 Backbone.Wreqr.noConflict = function () {
35 Backbone.Wreqr = previousWreqr;
36 return this;
37 };
38
39 // Handlers
40 // --------
41 // A registry of functions to call, given a name
42
43 Wreqr.Handlers = (function(Backbone, _){
44 "use strict";
45
46 // Constructor
47 // -----------
48
49 var Handlers = function(options){
50 this.options = options;
51 this._wreqrHandlers = {};
52
53 if (_.isFunction(this.initialize)){
54 this.initialize(options);
55 }
56 };
57
58 Handlers.extend = Backbone.Model.extend;
59
60 // Instance Members
61 // ----------------
62
63 _.extend(Handlers.prototype, Backbone.Events, {
64
65 // Add multiple handlers using an object literal configuration
66 setHandlers: function(handlers){
67 _.each(handlers, function(handler, name){
68 var context = null;
69
70 if (_.isObject(handler) && !_.isFunction(handler)){
71 context = handler.context;
72 handler = handler.callback;
73 }
74
75 this.setHandler(name, handler, context);
76 }, this);
77 },
78
79 // Add a handler for the given name, with an
80 // optional context to run the handler within
81 setHandler: function(name, handler, context){
82 var config = {
83 callback: handler,
84 context: context
85 };
86
87 this._wreqrHandlers[name] = config;
88
89 this.trigger("handler:add", name, handler, context);
90 },
91
92 // Determine whether or not a handler is registered
93 hasHandler: function(name){
94 return !! this._wreqrHandlers[name];
95 },
96
97 // Get the currently registered handler for
98 // the specified name. Throws an exception if
99 // no handler is found.
100 getHandler: function(name){
101 var config = this._wreqrHandlers[name];
102
103 if (!config){
104 return;
105 }
106
107 return function(){
108 var args = Array.prototype.slice.apply(arguments);
109 return config.callback.apply(config.context, args);
110 };
111 },
112
113 // Remove a handler for the specified name
114 removeHandler: function(name){
115 delete this._wreqrHandlers[name];
116 },
117
118 // Remove all handlers from this registry
119 removeAllHandlers: function(){
120 this._wreqrHandlers = {};
121 }
122 });
123
124 return Handlers;
125 })(Backbone, _);
126
127 // Wreqr.CommandStorage
128 // --------------------
129 //
130 // Store and retrieve commands for execution.
131 Wreqr.CommandStorage = (function(){
132 "use strict";
133
134 // Constructor function
135 var CommandStorage = function(options){
136 this.options = options;
137 this._commands = {};
138
139 if (_.isFunction(this.initialize)){
140 this.initialize(options);
141 }
142 };
143
144 // Instance methods
145 _.extend(CommandStorage.prototype, Backbone.Events, {
146
147 // Get an object literal by command name, that contains
148 // the `commandName` and the `instances` of all commands
149 // represented as an array of arguments to process
150 getCommands: function(commandName){
151 var commands = this._commands[commandName];
152
153 // we don't have it, so add it
154 if (!commands){
155
156 // build the configuration
157 commands = {
158 command: commandName,
159 instances: []
160 };
161
162 // store it
163 this._commands[commandName] = commands;
164 }
165
166 return commands;
167 },
168
169 // Add a command by name, to the storage and store the
170 // args for the command
171 addCommand: function(commandName, args){
172 var command = this.getCommands(commandName);
173 command.instances.push(args);
174 },
175
176 // Clear all commands for the given `commandName`
177 clearCommands: function(commandName){
178 var command = this.getCommands(commandName);
179 command.instances = [];
180 }
181 });
182
183 return CommandStorage;
184 })();
185
186 // Wreqr.Commands
187 // --------------
188 //
189 // A simple command pattern implementation. Register a command
190 // handler and execute it.
191 Wreqr.Commands = (function(Wreqr){
192 "use strict";
193
194 return Wreqr.Handlers.extend({
195 // default storage type
196 storageType: Wreqr.CommandStorage,
197
198 constructor: function(options){
199 this.options = options || {};
200
201 this._initializeStorage(this.options);
202 this.on("handler:add", this._executeCommands, this);
203
204 var args = Array.prototype.slice.call(arguments);
205 Wreqr.Handlers.prototype.constructor.apply(this, args);
206 },
207
208 // Execute a named command with the supplied args
209 execute: function(name, args){
210 name = arguments[0];
211 args = Array.prototype.slice.call(arguments, 1);
212
213 if (this.hasHandler(name)){
214 this.getHandler(name).apply(this, args);
215 } else {
216 this.storage.addCommand(name, args);
217 }
218
219 },
220
221 // Internal method to handle bulk execution of stored commands
222 _executeCommands: function(name, handler, context){
223 var command = this.storage.getCommands(name);
224
225 // loop through and execute all the stored command instances
226 _.each(command.instances, function(args){
227 handler.apply(context, args);
228 });
229
230 this.storage.clearCommands(name);
231 },
232
233 // Internal method to initialize storage either from the type's
234 // `storageType` or the instance `options.storageType`.
235 _initializeStorage: function(options){
236 var storage;
237
238 var StorageType = options.storageType || this.storageType;
239 if (_.isFunction(StorageType)){
240 storage = new StorageType();
241 } else {
242 storage = StorageType;
243 }
244
245 this.storage = storage;
246 }
247 });
248
249 })(Wreqr);
250
251 // Wreqr.RequestResponse
252 // ---------------------
253 //
254 // A simple request/response implementation. Register a
255 // request handler, and return a response from it
256 Wreqr.RequestResponse = (function(Wreqr){
257 "use strict";
258
259 return Wreqr.Handlers.extend({
260 request: function(){
261 var name = arguments[0];
262 var args = Array.prototype.slice.call(arguments, 1);
263 if (this.hasHandler(name)) {
264 return this.getHandler(name).apply(this, args);
265 }
266 }
267 });
268
269 })(Wreqr);
270
271 // Event Aggregator
272 // ----------------
273 // A pub-sub object that can be used to decouple various parts
274 // of an application through event-driven architecture.
275
276 Wreqr.EventAggregator = (function(Backbone, _){
277 "use strict";
278 var EA = function(){};
279
280 // Copy the `extend` function used by Backbone's classes
281 EA.extend = Backbone.Model.extend;
282
283 // Copy the basic Backbone.Events on to the event aggregator
284 _.extend(EA.prototype, Backbone.Events);
285
286 return EA;
287 })(Backbone, _);
288
289 // Wreqr.Channel
290 // --------------
291 //
292 // An object that wraps the three messaging systems:
293 // EventAggregator, RequestResponse, Commands
294 Wreqr.Channel = (function(Wreqr){
295 "use strict";
296
297 var Channel = function(channelName) {
298 this.vent = new Backbone.Wreqr.EventAggregator();
299 this.reqres = new Backbone.Wreqr.RequestResponse();
300 this.commands = new Backbone.Wreqr.Commands();
301 this.channelName = channelName;
302 };
303
304 _.extend(Channel.prototype, {
305
306 // Remove all handlers from the messaging systems of this channel
307 reset: function() {
308 this.vent.off();
309 this.vent.stopListening();
310 this.reqres.removeAllHandlers();
311 this.commands.removeAllHandlers();
312 return this;
313 },
314
315 // Connect a hash of events; one for each messaging system
316 connectEvents: function(hash, context) {
317 this._connect('vent', hash, context);
318 return this;
319 },
320
321 connectCommands: function(hash, context) {
322 this._connect('commands', hash, context);
323 return this;
324 },
325
326 connectRequests: function(hash, context) {
327 this._connect('reqres', hash, context);
328 return this;
329 },
330
331 // Attach the handlers to a given message system `type`
332 _connect: function(type, hash, context) {
333 if (!hash) {
334 return;
335 }
336
337 context = context || this;
338 var method = (type === 'vent') ? 'on' : 'setHandler';
339
340 _.each(hash, function(fn, eventName) {
341 this[type][method](eventName, _.bind(fn, context));
342 }, this);
343 }
344 });
345
346
347 return Channel;
348 })(Wreqr);
349
350 // Wreqr.Radio
351 // --------------
352 //
353 // An object that lets you communicate with many channels.
354 Wreqr.radio = (function(Wreqr){
355 "use strict";
356
357 var Radio = function() {
358 this._channels = {};
359 this.vent = {};
360 this.commands = {};
361 this.reqres = {};
362 this._proxyMethods();
363 };
364
365 _.extend(Radio.prototype, {
366
367 channel: function(channelName) {
368 if (!channelName) {
369 throw new Error('Channel must receive a name');
370 }
371
372 return this._getChannel( channelName );
373 },
374
375 _getChannel: function(channelName) {
376 var channel = this._channels[channelName];
377
378 if(!channel) {
379 channel = new Wreqr.Channel(channelName);
380 this._channels[channelName] = channel;
381 }
382
383 return channel;
384 },
385
386 _proxyMethods: function() {
387 _.each(['vent', 'commands', 'reqres'], function(system) {
388 _.each( messageSystems[system], function(method) {
389 this[system][method] = proxyMethod(this, system, method);
390 }, this);
391 }, this);
392 }
393 });
394
395
396 var messageSystems = {
397 vent: [
398 'on',
399 'off',
400 'trigger',
401 'once',
402 'stopListening',
403 'listenTo',
404 'listenToOnce'
405 ],
406
407 commands: [
408 'execute',
409 'setHandler',
410 'setHandlers',
411 'removeHandler',
412 'removeAllHandlers'
413 ],
414
415 reqres: [
416 'request',
417 'setHandler',
418 'setHandlers',
419 'removeHandler',
420 'removeAllHandlers'
421 ]
422 };
423
424 var proxyMethod = function(radio, system, method) {
425 return function(channelName) {
426 var messageSystem = radio._getChannel(channelName)[system];
427 var args = Array.prototype.slice.call(arguments, 1);
428
429 return messageSystem[method].apply(messageSystem, args);
430 };
431 };
432
433 return new Radio();
434
435 })(Wreqr);
436
437
438 return Backbone.Wreqr;
439
440}));