master
  1// 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}));