marionette-rails 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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: []