marionette-amd-rails 0.8.4

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