marionette-rails 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2012 Godfrey Chan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ marionette-rails
2
+ ================
3
+
4
+ [![Dependency Status](https://gemnasium.com/chancancode/marionette-rails.png)](https://gemnasium.com/chancancode/marionette-rails)
5
+
6
+ This gem is a wrapper for Derick Bailey's [Backbone.Marionette](https://github.com/derickbailey/backbone.marionette) library. It vendors the javascript library code for use with Rails' asset pipeline (Rails 3.1+).
7
+
8
+ ## Dependencies
9
+
10
+ [Backbone.Marionette](https://github.com/derickbailey/backbone.marionette) depends on Backbone (and Backbone's dependencies). These dependencies are not currently managed by the `marionette-rails` gem directly, because there exists multiple options to use Backbone with the Rails asset pipeline, such as [`backbone-on-rails`](https://github.com/meleyal/backbone-on-rails), [`backbone-rails`](https://github.com/aflatter/backbone-rails), [`rails-backbone`](https://github.com/codebrew/backbone-rails), just to name a few.
11
+
12
+ ## Usage
13
+
14
+ Add it to your Gemfile:
15
+
16
+ group :assets do
17
+ # Your other asset gems (sass-rails, coffee-rails, etc)
18
+
19
+ gem 'marionette-rails'
20
+ end
21
+
22
+ Then add this to `app/assets/javascripts/application.js.coffee`:
23
+
24
+ #= require backbone.marionette
25
+
26
+ Or, if you are using pure javascript, add this to `app/assets/javascripts/application.js`:
27
+
28
+ //= require backbone.marionette
29
+
30
+
31
+ ## Versioning
32
+
33
+ The gem will mirror the [Backbone.Marionette](https://github.com/derickbailey/backbone.marionette) versioning scheme. That is, version 0.8.2.* of `marionette-rails` would vendor [Backbone.Marionette](https://github.com/derickbailey/backbone.marionette) v0.8.2.
@@ -0,0 +1,8 @@
1
+ require 'rails'
2
+
3
+ module Marionette
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,999 @@
1
+ // Backbone.Marionette v0.8.2
2
+ //
3
+ // Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
4
+ // Distributed Under MIT License
5
+ //
6
+ // Documentation and Full License Available at:
7
+ // http://github.com/derickbailey/backbone.marionette
8
+
9
+ Backbone.Marionette = (function(Backbone, _, $){
10
+ var Marionette = {};
11
+
12
+ Marionette.version = "0.8.2";
13
+
14
+ // Marionette.View
15
+ // ---------------
16
+
17
+ // The core view type that other Marionette views extend from.
18
+ Marionette.View = Backbone.View.extend({
19
+ // Get the template or template id/selector for this view
20
+ // instance. You can set a `template` attribute in the view
21
+ // definition or pass a `template: "whatever"` parameter in
22
+ // to the constructor options. The `template` can also be
23
+ // a function that returns a selector string.
24
+ getTemplateSelector: function(){
25
+ var template;
26
+
27
+ // Get the template from `this.options.template` or
28
+ // `this.template`. The `options` takes precedence.
29
+ if (this.options && this.options.template){
30
+ template = this.options.template;
31
+ } else {
32
+ template = this.template;
33
+ }
34
+
35
+ // check if it's a function and execute it, if it is
36
+ if (_.isFunction(template)){
37
+ template = template.call(this);
38
+ }
39
+
40
+ return template;
41
+ },
42
+
43
+ // Serialize the model or collection for the view. If a model is
44
+ // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
45
+ // is also called, but is used to populate an `items` array in the
46
+ // resulting data. If both are found, defaults to the model.
47
+ // You can override the `serializeData` method in your own view
48
+ // definition, to provide custom serialization for your view's data.
49
+ serializeData: function(){
50
+ var data;
51
+
52
+ if (this.model) {
53
+ data = this.model.toJSON();
54
+ }
55
+ else if (this.collection) {
56
+ data = { items: this.collection.toJSON() };
57
+ }
58
+
59
+ data = this.mixinTemplateHelpers(data);
60
+
61
+ return data;
62
+ },
63
+
64
+ // Mix in template helper methods. Looks for a
65
+ // `templateHelpers` attribute, which can either be an
66
+ // object literal, or a function that returns an object
67
+ // literal. All methods and attributes from this object
68
+ // are copies to the object passed in.
69
+ mixinTemplateHelpers: function(target){
70
+ target = target || {};
71
+ var templateHelpers = this.templateHelpers;
72
+ if (_.isFunction(templateHelpers)){
73
+ templateHelpers = templateHelpers.call(this);
74
+ }
75
+ return _.extend(target, templateHelpers);
76
+ },
77
+
78
+ // Configure `triggers` to forward DOM events to view
79
+ // events. `triggers: {"click .foo": "do:foo"}`
80
+ configureTriggers: function(){
81
+ if (!this.triggers) { return; }
82
+
83
+ var triggers = this.triggers;
84
+ var that = this;
85
+ var triggerEvents = {};
86
+
87
+ // Allow `triggers` to be configured as a function
88
+ if (_.isFunction(triggers)){ triggers = triggers.call(this); }
89
+
90
+ // Configure the triggers, prevent default
91
+ // action and stop propagation of DOM events
92
+ _.each(triggers, function(value, key){
93
+
94
+ triggerEvents[key] = function(e){
95
+ if (e && e.preventDefault){ e.preventDefault(); }
96
+ if (e && e.stopPropagation){ e.stopPropagation(); }
97
+ that.trigger(value);
98
+ }
99
+
100
+ });
101
+
102
+ return triggerEvents;
103
+ },
104
+
105
+ delegateEvents: function(events){
106
+ events = events || this.events;
107
+ if (_.isFunction(events)){ events = events.call(this)}
108
+
109
+ var combinedEvents = {};
110
+ var triggers = this.configureTriggers();
111
+ _.extend(combinedEvents, events, triggers);
112
+
113
+ Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
114
+ },
115
+
116
+ // Default `close` implementation, for removing a view from the
117
+ // DOM and unbinding it. Regions will call this method
118
+ // for you. You can specify an `onClose` method in your view to
119
+ // add custom code that is called after the view is closed.
120
+ close: function(){
121
+ if (this.beforeClose) { this.beforeClose(); }
122
+
123
+ this.unbindAll();
124
+ this.remove();
125
+
126
+ if (this.onClose) { this.onClose(); }
127
+ this.trigger('close');
128
+ this.unbind();
129
+ }
130
+ });
131
+
132
+ // Item View
133
+ // ---------
134
+
135
+ // A single item view implementation that contains code for rendering
136
+ // with underscore.js templates, serializing the view's model or collection,
137
+ // and calling several methods on extended views, such as `onRender`.
138
+ Marionette.ItemView = Marionette.View.extend({
139
+ constructor: function(){
140
+ var args = slice.call(arguments);
141
+ Marionette.View.prototype.constructor.apply(this, args);
142
+
143
+ _.bindAll(this, "render");
144
+
145
+ this.initialEvents();
146
+ },
147
+
148
+ // Configured the initial events that the item view
149
+ // binds to. Override this method to prevent the initial
150
+ // events, or to add your own initial events.
151
+ initialEvents: function(){
152
+ if (this.collection){
153
+ this.bindTo(this.collection, "reset", this.render, this);
154
+ }
155
+ },
156
+
157
+ // Render the view, defaulting to underscore.js templates.
158
+ // You can override this in your view definition.
159
+ render: function(){
160
+ var that = this;
161
+
162
+ var deferredRender = $.Deferred();
163
+
164
+ var beforeRenderDone = function() {
165
+ that.trigger("before:render", that);
166
+ that.trigger("item:before:render", that);
167
+
168
+ var deferredData = that.serializeData();
169
+ $.when(deferredData).then(dataSerialized);
170
+ }
171
+
172
+ var dataSerialized = function(data){
173
+ var asyncRender = that.renderHtml(data);
174
+ $.when(asyncRender).then(templateRendered);
175
+ }
176
+
177
+ var templateRendered = function(html){
178
+ that.$el.html(html);
179
+ callDeferredMethod(that.onRender, onRenderDone, that);
180
+ }
181
+
182
+ var onRenderDone = function(){
183
+ that.trigger("render", that);
184
+ that.trigger("item:rendered", that);
185
+
186
+ deferredRender.resolve();
187
+ }
188
+
189
+ callDeferredMethod(this.beforeRender, beforeRenderDone, this);
190
+
191
+ return deferredRender.promise();
192
+ },
193
+
194
+ // Render the data for this item view in to some HTML.
195
+ // Override this method to replace the specific way in
196
+ // which an item view has it's data rendered in to html.
197
+ renderHtml: function(data) {
198
+ var template = this.getTemplateSelector();
199
+ return Marionette.Renderer.render(template, data);
200
+ },
201
+
202
+ // Override the default close event to add a few
203
+ // more events that are triggered.
204
+ close: function(){
205
+ this.trigger('item:before:close');
206
+ Marionette.View.prototype.close.apply(this, arguments);
207
+ this.trigger('item:closed');
208
+ }
209
+ });
210
+
211
+ // Collection View
212
+ // ---------------
213
+
214
+ // A view that iterates over a Backbone.Collection
215
+ // and renders an individual ItemView for each model.
216
+ Marionette.CollectionView = Marionette.View.extend({
217
+ constructor: function(){
218
+ Marionette.View.prototype.constructor.apply(this, arguments);
219
+
220
+ _.bindAll(this, "addItemView", "render");
221
+ this.initialEvents();
222
+ },
223
+
224
+ // Configured the initial events that the collection view
225
+ // binds to. Override this method to prevent the initial
226
+ // events, or to add your own initial events.
227
+ initialEvents: function(){
228
+ if (this.collection){
229
+ this.bindTo(this.collection, "add", this.addChildView, this);
230
+ this.bindTo(this.collection, "remove", this.removeItemView, this);
231
+ this.bindTo(this.collection, "reset", this.render, this);
232
+ }
233
+ },
234
+
235
+ // Handle a child item added to the collection
236
+ addChildView: function(item){
237
+ var ItemView = this.getItemView();
238
+ return this.addItemView(item, ItemView);
239
+ },
240
+
241
+ // Loop through all of the items and render
242
+ // each of them with the specified `itemView`.
243
+ render: function(){
244
+ var that = this;
245
+ var deferredRender = $.Deferred();
246
+ var promises = [];
247
+ var ItemView = this.getItemView();
248
+
249
+ if (this.beforeRender) { this.beforeRender(); }
250
+ this.trigger("collection:before:render", this);
251
+
252
+ this.closeChildren();
253
+
254
+ if (this.collection) {
255
+ this.collection.each(function(item){
256
+ var promise = that.addItemView(item, ItemView);
257
+ promises.push(promise);
258
+ });
259
+ }
260
+
261
+ deferredRender.done(function(){
262
+ if (this.onRender) { this.onRender(); }
263
+ this.trigger("collection:rendered", this);
264
+ });
265
+
266
+ $.when.apply(this, promises).then(function(){
267
+ deferredRender.resolveWith(that);
268
+ });
269
+
270
+ return deferredRender.promise();
271
+ },
272
+
273
+ // Retrieve the itemView type, either from `this.options.itemView`
274
+ // or from the `itemView` in the object definition. The "options"
275
+ // takes precedence.
276
+ getItemView: function(){
277
+ var itemView = this.options.itemView || this.itemView;
278
+
279
+ if (!itemView){
280
+ var err = new Error("An `itemView` must be specified");
281
+ err.name = "NoItemViewError";
282
+ throw err;
283
+ }
284
+
285
+ return itemView;
286
+ },
287
+
288
+ // Render the child item's view and add it to the
289
+ // HTML for the collection view.
290
+ addItemView: function(item, ItemView){
291
+ var that = this;
292
+
293
+ var view = this.buildItemView(item, ItemView);
294
+ this.bindTo(view, "all", function(){
295
+
296
+ // get the args, prepend the event name
297
+ // with "itemview:" and insert the child view
298
+ // as the first event arg (after the event name)
299
+ var args = slice.call(arguments);
300
+ args[0] = "itemview:" + args[0];
301
+ args.splice(1, 0, view);
302
+
303
+ that.trigger.apply(that, args);
304
+ });
305
+
306
+ this.storeChild(view);
307
+ this.trigger("item:added", view);
308
+
309
+ var viewRendered = view.render();
310
+ $.when(viewRendered).then(function(){
311
+ that.appendHtml(that, view);
312
+ });
313
+
314
+ return viewRendered;
315
+ },
316
+
317
+ // Build an `itemView` for every model in the collection.
318
+ buildItemView: function(item, ItemView){
319
+ var view = new ItemView({
320
+ model: item
321
+ });
322
+ return view;
323
+ },
324
+
325
+ // Remove the child view and close it
326
+ removeItemView: function(item){
327
+ var view = this.children[item.cid];
328
+ if (view){
329
+ view.close();
330
+ delete this.children[item.cid];
331
+ }
332
+ this.trigger("item:removed", view);
333
+ },
334
+
335
+ // Append the HTML to the collection's `el`.
336
+ // Override this method to do something other
337
+ // then `.append`.
338
+ appendHtml: function(collectionView, itemView){
339
+ collectionView.$el.append(itemView.el);
340
+ },
341
+
342
+ // Store references to all of the child `itemView`
343
+ // instances so they can be managed and cleaned up, later.
344
+ storeChild: function(view){
345
+ if (!this.children){
346
+ this.children = {};
347
+ }
348
+ this.children[view.model.cid] = view;
349
+ },
350
+
351
+ // Handle cleanup and other closing needs for
352
+ // the collection of views.
353
+ close: function(){
354
+ this.trigger("collection:before:close");
355
+ this.closeChildren();
356
+ Marionette.View.prototype.close.apply(this, arguments);
357
+ this.trigger("collection:closed");
358
+ },
359
+
360
+ closeChildren: function(){
361
+ if (this.children){
362
+ _.each(this.children, function(childView){
363
+ childView.close();
364
+ });
365
+ }
366
+ }
367
+ });
368
+
369
+ // Composite View
370
+ // --------------
371
+
372
+ // Used for rendering a branch-leaf, hierarchical structure.
373
+ // Extends directly from CollectionView and also renders an
374
+ // an item view as `modelView`, for the top leaf
375
+ Marionette.CompositeView = Marionette.CollectionView.extend({
376
+ constructor: function(options){
377
+ Marionette.CollectionView.apply(this, arguments);
378
+ this.itemView = this.getItemView();
379
+ },
380
+
381
+ // Retrieve the `itemView` to be used when rendering each of
382
+ // the items in the collection. The default is to return
383
+ // `this.itemView` or Marionette.CompositeView if no `itemView`
384
+ // has been defined
385
+ getItemView: function(){
386
+ return this.itemView || this.constructor;
387
+ },
388
+
389
+ // Renders the model once, and the collection once. Calling
390
+ // this again will tell the model's view to re-render itself
391
+ // but the collection will not re-render.
392
+ render: function(){
393
+ var that = this;
394
+ var compositeRendered = $.Deferred();
395
+
396
+ var modelIsRendered = this.renderModel();
397
+ $.when(modelIsRendered).then(function(html){
398
+ that.$el.html(html);
399
+ that.trigger("composite:model:rendered");
400
+ that.trigger("render");
401
+
402
+ var collectionIsRendered = that.renderCollection();
403
+ $.when(collectionIsRendered).then(function(){
404
+ compositeRendered.resolve();
405
+ });
406
+ });
407
+
408
+ compositeRendered.done(function(){
409
+ that.trigger("composite:rendered");
410
+ });
411
+
412
+ return compositeRendered.promise();
413
+ },
414
+
415
+ // Render the collection for the composite view
416
+ renderCollection: function(){
417
+ var collectionDeferred = Marionette.CollectionView.prototype.render.apply(this, arguments);
418
+ collectionDeferred.done(function(){
419
+ this.trigger("composite:collection:rendered");
420
+ });
421
+ return collectionDeferred.promise();
422
+ },
423
+
424
+ // Render an individual model, if we have one, as
425
+ // part of a composite view (branch / leaf). For example:
426
+ // a treeview.
427
+ renderModel: function(){
428
+ var data = {};
429
+ data = this.serializeData();
430
+
431
+ var template = this.getTemplateSelector();
432
+ return Marionette.Renderer.render(template, data);
433
+ }
434
+ });
435
+
436
+ // Region
437
+ // ------
438
+
439
+ // Manage the visual regions of your composite application. See
440
+ // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
441
+ Marionette.Region = function(options){
442
+ this.options = options || {};
443
+
444
+ _.extend(this, options);
445
+
446
+ if (!this.el){
447
+ var err = new Error("An 'el' must be specified");
448
+ err.name = "NoElError";
449
+ throw err;
450
+ }
451
+
452
+ if (this.initialize){
453
+ this.initialize.apply(this, arguments);
454
+ }
455
+ };
456
+
457
+ _.extend(Marionette.Region.prototype, Backbone.Events, {
458
+
459
+ // Displays a backbone view instance inside of the region.
460
+ // Handles calling the `render` method for you. Reads content
461
+ // directly from the `el` attribute. Also calls an optional
462
+ // `onShow` and `close` method on your view, just after showing
463
+ // or just before closing the view, respectively.
464
+ show: function(view, appendMethod){
465
+ this.ensureEl();
466
+
467
+ this.close();
468
+ this.open(view, appendMethod);
469
+
470
+ this.currentView = view;
471
+ },
472
+
473
+ ensureEl: function(){
474
+ if (!this.$el || this.$el.length === 0){
475
+ this.$el = this.getEl(this.el);
476
+ }
477
+ },
478
+
479
+ // Override this method to change how the region finds the
480
+ // DOM element that it manages. Return a jQuery selector object.
481
+ getEl: function(selector){
482
+ return $(selector);
483
+ },
484
+
485
+ // Internal method to render and display a view. Not meant
486
+ // to be called from any external code.
487
+ open: function(view, appendMethod){
488
+ var that = this;
489
+ appendMethod = appendMethod || "html";
490
+
491
+ $.when(view.render()).then(function () {
492
+ that.$el[appendMethod](view.el);
493
+ if (view.onShow) { view.onShow(); }
494
+ if (that.onShow) { that.onShow(view); }
495
+ view.trigger("show");
496
+ that.trigger("view:show", view);
497
+ });
498
+ },
499
+
500
+ // Close the current view, if there is one. If there is no
501
+ // current view, it does nothing and returns immediately.
502
+ close: function(){
503
+ var view = this.currentView;
504
+ if (!view){ return; }
505
+
506
+ if (view.close) { view.close(); }
507
+ this.trigger("view:closed", view);
508
+
509
+ delete this.currentView;
510
+ },
511
+
512
+ // Attach an existing view to the region. This
513
+ // will not call `render` or `onShow` for the new view,
514
+ // and will not replace the current HTML for the `el`
515
+ // of the region.
516
+ attachView: function(view){
517
+ this.currentView = view;
518
+ }
519
+ });
520
+
521
+ // Layout
522
+ // ------
523
+
524
+ // Formerly known as Composite Region.
525
+ //
526
+ // Used for managing application layouts, nested layouts and
527
+ // multiple regions within an application or sub-application.
528
+ //
529
+ // A specialized view type that renders an area of HTML and then
530
+ // attaches `Region` instances to the specified `regions`.
531
+ // Used for composite view management and sub-application areas.
532
+ Marionette.Layout = Marionette.ItemView.extend({
533
+ constructor: function () {
534
+ this.vent = new Backbone.Marionette.EventAggregator();
535
+ Backbone.Marionette.ItemView.apply(this, arguments);
536
+ this.regionManagers = {};
537
+ },
538
+
539
+ render: function () {
540
+ this.initializeRegions();
541
+ return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
542
+ },
543
+
544
+ close: function () {
545
+ this.closeRegions();
546
+ Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
547
+ },
548
+
549
+ // Initialize the regions that have been defined in a
550
+ // `regions` attribute on this layout. The key of the
551
+ // hash becomes an attribute on the layout object directly.
552
+ // For example: `regions: { menu: ".menu-container" }`
553
+ // will product a `layout.menu` object which is a region
554
+ // that controls the `.menu-container` DOM element.
555
+ initializeRegions: function () {
556
+ var that = this;
557
+ _.each(this.regions, function (selector, name) {
558
+ var regionManager = new Backbone.Marionette.Region({
559
+ el: selector,
560
+
561
+ getEl: function(selector){
562
+ return that.$(selector);
563
+ }
564
+ });
565
+ that.regionManagers[name] = regionManager;
566
+ that[name] = regionManager;
567
+ });
568
+ },
569
+
570
+ // Close all of the regions that have been opened by
571
+ // this layout. This method is called when the layout
572
+ // itself is closed.
573
+ closeRegions: function () {
574
+ var that = this;
575
+ _.each(this.regionManagers, function (manager, name) {
576
+ manager.close();
577
+ delete that[name];
578
+ });
579
+ this.regionManagers = {};
580
+ }
581
+ });
582
+
583
+ // AppRouter
584
+ // ---------
585
+
586
+ // Reduce the boilerplate code of handling route events
587
+ // and then calling a single method on another object.
588
+ // Have your routers configured to call the method on
589
+ // your object, directly.
590
+ //
591
+ // Configure an AppRouter with `appRoutes`.
592
+ //
593
+ // App routers can only take one `controller` object.
594
+ // It is recommended that you divide your controller
595
+ // objects in to smaller peices of related functionality
596
+ // and have multiple routers / controllers, instead of
597
+ // just one giant router and controller.
598
+ //
599
+ // You can also add standard routes to an AppRouter.
600
+
601
+ Marionette.AppRouter = Backbone.Router.extend({
602
+
603
+ constructor: function(options){
604
+ Backbone.Router.prototype.constructor.call(this, options);
605
+
606
+ if (this.appRoutes){
607
+ var controller = this.controller;
608
+ if (options && options.controller) {
609
+ controller = options.controller;
610
+ }
611
+ this.processAppRoutes(controller, this.appRoutes);
612
+ }
613
+ },
614
+
615
+ processAppRoutes: function(controller, appRoutes){
616
+ var method, methodName;
617
+ var route, routesLength, i;
618
+ var routes = [];
619
+ var router = this;
620
+
621
+ for(route in appRoutes){
622
+ if (appRoutes.hasOwnProperty(route)){
623
+ routes.unshift([route, appRoutes[route]]);
624
+ }
625
+ }
626
+
627
+ routesLength = routes.length;
628
+ for (i = 0; i < routesLength; i++){
629
+ route = routes[i][0];
630
+ methodName = routes[i][1];
631
+ method = controller[methodName];
632
+
633
+ if (!method){
634
+ var msg = "Method '" + methodName + "' was not found on the controller";
635
+ var err = new Error(msg);
636
+ err.name = "NoMethodError";
637
+ throw err;
638
+ }
639
+
640
+ method = _.bind(method, controller);
641
+ router.route(route, methodName, method);
642
+ }
643
+ }
644
+ });
645
+
646
+ // Composite Application
647
+ // ---------------------
648
+
649
+ // Contain and manage the composite application as a whole.
650
+ // Stores and starts up `Region` objects, includes an
651
+ // event aggregator as `app.vent`
652
+ Marionette.Application = function(options){
653
+ this.initCallbacks = new Marionette.Callbacks();
654
+ this.vent = new Marionette.EventAggregator();
655
+ _.extend(this, options);
656
+ };
657
+
658
+ _.extend(Marionette.Application.prototype, Backbone.Events, {
659
+ // Add an initializer that is either run at when the `start`
660
+ // method is called, or run immediately if added after `start`
661
+ // has already been called.
662
+ addInitializer: function(initializer){
663
+ this.initCallbacks.add(initializer);
664
+ },
665
+
666
+ // kick off all of the application's processes.
667
+ // initializes all of the regions that have been added
668
+ // to the app, and runs all of the initializer functions
669
+ start: function(options){
670
+ this.trigger("initialize:before", options);
671
+ this.initCallbacks.run(this, options);
672
+ this.trigger("initialize:after", options);
673
+
674
+ this.trigger("start", options);
675
+ },
676
+
677
+ // Add regions to your app.
678
+ // Accepts a hash of named strings or Region objects
679
+ // addRegions({something: "#someRegion"})
680
+ // addRegions{{something: Region.extend({el: "#someRegion"}) });
681
+ addRegions: function(regions){
682
+ var regionValue, regionObj, region;
683
+
684
+ for(region in regions){
685
+ if (regions.hasOwnProperty(region)){
686
+ regionValue = regions[region];
687
+
688
+ if (typeof regionValue === "string"){
689
+ regionObj = new Marionette.Region({
690
+ el: regionValue
691
+ });
692
+ } else {
693
+ regionObj = new regionValue();
694
+ }
695
+
696
+ this[region] = regionObj;
697
+ }
698
+ }
699
+ },
700
+
701
+ // Add modules to the application, providing direct
702
+ // access to your applicaiton object, Backbone,
703
+ // Marionette, jQuery and Underscore as parameters
704
+ // to a callback function.
705
+ module: function(moduleNames, moduleDefinition){
706
+ var moduleName, module, moduleOverride;
707
+ var parentModule = this;
708
+ var moduleNames = moduleNames.split(".");
709
+
710
+ // Loop through all the parts of the module definition
711
+ var length = moduleNames.length;
712
+ for(var i = 0; i < length; i++){
713
+
714
+ // only run the module definition if this is the
715
+ // last module in the chaing: "Foo.Bar.Baz" only
716
+ // runs the module definition for the "Baz" module
717
+ var defineModule = (i === length-1);
718
+
719
+ // Get the module name, and check if it exists on
720
+ // the current parent already
721
+ moduleName = moduleNames[i];
722
+ module = parentModule[moduleName];
723
+
724
+ if (!module){
725
+ // Create the module
726
+ module = new Marionette.Application();
727
+
728
+ // Build the module definition if this is the last
729
+ // module specified in the . chain
730
+ if (defineModule && moduleDefinition){
731
+ moduleOverride = moduleDefinition(module, this, Backbone, Marionette, jQuery, _);
732
+ // Override it if necessary
733
+ if (moduleOverride){
734
+ module = moduleOverride;
735
+ }
736
+ }
737
+
738
+ // And attach it to the parent module
739
+ parentModule[moduleName] = module;
740
+ }
741
+
742
+ // Reset the parent module so that the next child
743
+ // in the list will be added to the correct parent
744
+ parentModule = module;
745
+ }
746
+
747
+ return module;
748
+ }
749
+ });
750
+
751
+ // BindTo: Event Binding
752
+ // ---------------------
753
+
754
+ // BindTo facilitates the binding and unbinding of events
755
+ // from objects that extend `Backbone.Events`. It makes
756
+ // unbinding events, even with anonymous callback functions,
757
+ // easy.
758
+ //
759
+ // Thanks to Johnny Oshika for this code.
760
+ // http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
761
+ Marionette.BindTo = {
762
+ // Store the event binding in array so it can be unbound
763
+ // easily, at a later point in time.
764
+ bindTo: function (obj, eventName, callback, context) {
765
+ context = context || this;
766
+ obj.on(eventName, callback, context);
767
+
768
+ if (!this.bindings) { this.bindings = []; }
769
+
770
+ var binding = {
771
+ obj: obj,
772
+ eventName: eventName,
773
+ callback: callback,
774
+ context: context
775
+ }
776
+
777
+ this.bindings.push(binding);
778
+
779
+ return binding;
780
+ },
781
+
782
+ // Unbind from a single binding object. Binding objects are
783
+ // returned from the `bindTo` method call.
784
+ unbindFrom: function(binding){
785
+ binding.obj.off(binding.eventName, binding.callback);
786
+ this.bindings = _.reject(this.bindings, function(bind){return bind === binding});
787
+ },
788
+
789
+ // Unbind all of the events that we have stored.
790
+ unbindAll: function () {
791
+ var that = this;
792
+
793
+ // The `unbindFrom` call removes elements from the array
794
+ // while it is being iterated, so clone it first.
795
+ var bindings = _.map(this.bindings, _.identity);
796
+ _.each(bindings, function (binding, index) {
797
+ that.unbindFrom(binding);
798
+ });
799
+ }
800
+ };
801
+
802
+ // Callbacks
803
+ // ---------
804
+
805
+ // A simple way of managing a collection of callbacks
806
+ // and executing them at a later point in time, using jQuery's
807
+ // `Deferred` object.
808
+ Marionette.Callbacks = function(){
809
+ this.deferred = $.Deferred();
810
+ this.promise = this.deferred.promise();
811
+ };
812
+
813
+ _.extend(Marionette.Callbacks.prototype, {
814
+
815
+ // Add a callback to be executed. Callbacks added here are
816
+ // guaranteed to execute, even if they are added after the
817
+ // `run` method is called.
818
+ add: function(callback){
819
+ this.promise.done(function(context, options){
820
+ callback.call(context, options);
821
+ });
822
+ },
823
+
824
+ // Run all registered callbacks with the context specified.
825
+ // Additional callbacks can be added after this has been run
826
+ // and they will still be executed.
827
+ run: function(context, options){
828
+ this.deferred.resolve(context, options);
829
+ }
830
+ });
831
+
832
+ // Event Aggregator
833
+ // ----------------
834
+
835
+ // A pub-sub object that can be used to decouple various parts
836
+ // of an application through event-driven architecture.
837
+ Marionette.EventAggregator = function(options){
838
+ _.extend(this, options);
839
+ };
840
+
841
+ _.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
842
+ // Assumes the event aggregator itself is the
843
+ // object being bound to.
844
+ bindTo: function(eventName, callback, context){
845
+ return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
846
+ }
847
+ });
848
+
849
+ // Template Cache
850
+ // --------------
851
+
852
+ // Manage templates stored in `<script>` blocks,
853
+ // caching them for faster access.
854
+ Marionette.TemplateCache = {
855
+ templates: {},
856
+ loaders: {},
857
+
858
+ // Get the specified template by id. Either
859
+ // retrieves the cached version, or loads it
860
+ // from the DOM.
861
+ get: function(templateId){
862
+ var that = this;
863
+ var templateRetrieval = $.Deferred();
864
+ var cachedTemplate = this.templates[templateId];
865
+
866
+ if (cachedTemplate){
867
+ templateRetrieval.resolve(cachedTemplate);
868
+ } else {
869
+ var loader = this.loaders[templateId];
870
+ if(loader) {
871
+ templateRetrieval = loader;
872
+ } else {
873
+ this.loaders[templateId] = templateRetrieval;
874
+
875
+ this.loadTemplate(templateId, function(template){
876
+ delete that.loaders[templateId];
877
+ that.templates[templateId] = template;
878
+ templateRetrieval.resolve(template);
879
+ });
880
+ }
881
+
882
+ }
883
+
884
+ return templateRetrieval.promise();
885
+ },
886
+
887
+ // Load a template from the DOM, by default. Override
888
+ // this method to provide your own template retrieval,
889
+ // such as asynchronous loading from a server.
890
+ loadTemplate: function(templateId, callback){
891
+ var template = $(templateId).html();
892
+
893
+ // Make sure we have a template before trying to compile it
894
+ if (!template || template.length === 0){
895
+ var msg = "Could not find template: '" + templateId + "'";
896
+ var err = new Error(msg);
897
+ err.name = "NoTemplateError";
898
+ throw err;
899
+ }
900
+
901
+ template = this.compileTemplate(template);
902
+
903
+ callback.call(this, template);
904
+ },
905
+
906
+ // Pre-compile the template before caching it. Override
907
+ // this method if you do not need to pre-compile a template
908
+ // (JST / RequireJS for example) or if you want to change
909
+ // the template engine used (Handebars, etc).
910
+ compileTemplate: function(rawTemplate){
911
+ return _.template(rawTemplate);
912
+ },
913
+
914
+ // Clear templates from the cache. If no arguments
915
+ // are specified, clears all templates:
916
+ // `clear()`
917
+ //
918
+ // If arguments are specified, clears each of the
919
+ // specified templates from the cache:
920
+ // `clear("#t1", "#t2", "...")`
921
+ clear: function(){
922
+ var i;
923
+ var length = arguments.length;
924
+
925
+ if (length > 0){
926
+ for(i=0; i<length; i++){
927
+ delete this.templates[arguments[i]];
928
+ }
929
+ } else {
930
+ this.templates = {};
931
+ }
932
+ }
933
+ };
934
+
935
+ // Renderer
936
+ // --------
937
+
938
+ // Render a template with data by passing in the template
939
+ // selector and the data to render.
940
+ Marionette.Renderer = {
941
+
942
+ // Render a template with data. The `template` parameter is
943
+ // passed to the `TemplateCache` object to retrieve the
944
+ // actual template. Override this method to provide your own
945
+ // custom rendering and template handling for all of Marionette.
946
+ render: function(template, data){
947
+ var that = this;
948
+ var asyncRender = $.Deferred();
949
+
950
+ var templateRetrieval = Marionette.TemplateCache.get(template);
951
+
952
+ $.when(templateRetrieval).then(function(template){
953
+ var html = that.renderTemplate(template, data);
954
+ asyncRender.resolve(html);
955
+ });
956
+
957
+ return asyncRender.promise();
958
+ },
959
+
960
+ // Default implementation uses underscore.js templates. Override
961
+ // this method to use your own templating engine.
962
+ renderTemplate: function(template, data){
963
+ var html = template(data);
964
+ return html;
965
+ }
966
+
967
+ };
968
+
969
+ // Helpers
970
+ // -------
971
+
972
+ // For slicing `arguments` in functions
973
+ var slice = Array.prototype.slice;
974
+
975
+ // Copy the `extend` function used by Backbone's classes
976
+ var extend = Marionette.View.extend;
977
+ Marionette.Region.extend = extend;
978
+ Marionette.Application.extend = extend;
979
+
980
+ // Copy the features of `BindTo` on to these objects
981
+ _.extend(Marionette.View.prototype, Marionette.BindTo);
982
+ _.extend(Marionette.Application.prototype, Marionette.BindTo);
983
+ _.extend(Marionette.Region.prototype, Marionette.BindTo);
984
+
985
+ // A simple wrapper method for deferring a callback until
986
+ // after another method has been called, passing the
987
+ // results of the first method to the second. Uses jQuery's
988
+ // deferred / promise objects, and $.when/then to make it
989
+ // work.
990
+ var callDeferredMethod = function(fn, callback, context){
991
+ var promise;
992
+ if (fn) { promise = fn.call(context); }
993
+ $.when(promise).then(callback);
994
+ }
995
+
996
+
997
+ return Marionette;
998
+ })(Backbone, _, window.jQuery || window.Zepto || window.ender);
999
+
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: marionette-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Godfrey Chan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.1.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.1.0
30
+ description: Vendors Backbone.Marionette for use with asset pipeline.
31
+ email:
32
+ - godfreykfc@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/marionette-rails.rb
38
+ - vendor/assets/javascripts/backbone.marionette.js
39
+ - LICENSE
40
+ - README.md
41
+ homepage: https://github.com/chancancode/marionette-rails
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: 1.3.6
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.24
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Backbone.Marionette for Rails
65
+ test_files: []