rails-backbone 0.2.0 → 0.5.0
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/README.md +38 -7
- data/lib/generators/backbone/install/install_generator.rb +2 -2
- data/lib/generators/backbone/install/templates/app.coffee +2 -2
- data/lib/generators/backbone/model/model_generator.rb +1 -1
- data/lib/generators/backbone/resource_helpers.rb +2 -2
- data/lib/generators/backbone/{controller/controller_generator.rb → router/router_generator.rb} +5 -5
- data/lib/generators/backbone/{controller/templates/controller.coffee → router/templates/router.coffee} +1 -1
- data/lib/generators/backbone/{controller → router}/templates/template.jst +0 -0
- data/lib/generators/backbone/{controller → router}/templates/view.coffee +0 -0
- data/lib/generators/backbone/scaffold/scaffold_generator.rb +4 -4
- data/lib/generators/backbone/scaffold/templates/{controller.coffee → router.coffee} +2 -2
- data/lib/generators/backbone/scaffold/templates/views/index_view.coffee +1 -1
- data/vendor/assets/javascripts/backbone.js +145 -94
- data/vendor/assets/javascripts/backbone_rails_sync.js +5 -1
- data/vendor/assets/javascripts/underscore.js +3 -14
- metadata +11 -11
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Backbone-Rails
|
2
2
|
|
3
|
-
Easily setup and use backbone.js with rails 3.1
|
3
|
+
Easily setup and use backbone.js (0.5.0) with rails 3.1
|
4
4
|
|
5
5
|
## Rails 3.1 setup
|
6
6
|
This gem requires the use of rails 3.1, coffeescript and the new rails asset pipeline provided by sprockets.
|
@@ -22,7 +22,7 @@ Then run the following commands:
|
|
22
22
|
|
23
23
|
Running `rails g backbone:install` will create the following directory structure under `app/assets/javascripts/backbone`:
|
24
24
|
|
25
|
-
|
25
|
+
routers/
|
26
26
|
models/
|
27
27
|
templates/
|
28
28
|
views/
|
@@ -30,7 +30,8 @@ Running `rails g backbone:install` will create the following directory structure
|
|
30
30
|
It will also create a toplevel app_name.coffee file to setup namespacing and setup initial requires.
|
31
31
|
|
32
32
|
## Generators
|
33
|
-
backbone-rails provides 3 simple generators to help get you started using bacbone.js with rails 3.1
|
33
|
+
backbone-rails provides 3 simple generators to help get you started using bacbone.js with rails 3.1.
|
34
|
+
The generators will only create client side code (javascript).
|
34
35
|
|
35
36
|
### Model Generator
|
36
37
|
|
@@ -38,14 +39,44 @@ backbone-rails provides 3 simple generators to help get you started using bacbon
|
|
38
39
|
|
39
40
|
This generator creates a backbone model and collection inside `app/assets/javascript/backbone/models` to be used to talk to the rails backend.
|
40
41
|
|
41
|
-
###
|
42
|
+
### Routers
|
42
43
|
|
43
|
-
rails g backbone:
|
44
|
+
rails g backbone:router
|
44
45
|
|
45
|
-
This generator creates a backbone
|
46
|
+
This generator creates a backbone router with corresponding views and templates for the given actions provided.
|
46
47
|
|
47
48
|
### Scaffolding
|
48
49
|
|
49
50
|
rails g backbone:scaffold
|
50
51
|
|
51
|
-
This generator creates a
|
52
|
+
This generator creates a router, views, templates, model and collection to create a simple crud single page app
|
53
|
+
|
54
|
+
## Example Usage
|
55
|
+
|
56
|
+
Say we have just created a new rails 3.1 application called `blog`. Edit your gemfile and add `gem rails-backbone`.
|
57
|
+
|
58
|
+
Install the gem and generate scaffolding.
|
59
|
+
|
60
|
+
bundle install
|
61
|
+
rails g backbone:install
|
62
|
+
rails g scaffold Post title:string content:string
|
63
|
+
rake db:migrate
|
64
|
+
rails g backbone:scaffold Post title:string content:string
|
65
|
+
|
66
|
+
You now have installed the backbone-rails gem, setup a default directory structure for your frontend backbone code.
|
67
|
+
Then you generated the usual rails server side crud scaffolding and finally generated backbone.js code to provide a simple single page crud app.
|
68
|
+
You have one last step:
|
69
|
+
|
70
|
+
Edit your posts index view `app/views/posts/index.html.erb` with the following contents:
|
71
|
+
|
72
|
+
<div id="posts"></div>
|
73
|
+
|
74
|
+
<script type="text/javascript">
|
75
|
+
$(function() {
|
76
|
+
window.router = new Blog.Routers.PostsRouter({posts: <%= @posts.to_json.html_safe -%>});
|
77
|
+
Backbone.history.start();
|
78
|
+
});
|
79
|
+
</script>
|
80
|
+
|
81
|
+
Now start your server `rails s` and browse to [localhost:3000/posts](http://localhost:3000/posts)
|
82
|
+
You should now have a fully functioning single page crud app for Post models.
|
@@ -15,14 +15,14 @@ module Backbone
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def create_dir_layout
|
18
|
-
%W{
|
18
|
+
%W{routers models views templates}.each do |dir|
|
19
19
|
empty_directory "app/assets/javascripts/backbone/#{dir}"
|
20
20
|
create_file "app/assets/javascripts/backbone/#{dir}/.gitkeep" unless options[:skip_git]
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
24
|
def create_app_file
|
25
|
-
template "app.coffee", "app/assets/javascripts/backbone/#{application_name}.coffee"
|
25
|
+
template "app.coffee", "app/assets/javascripts/backbone/#{application_name}.js.coffee"
|
26
26
|
end
|
27
27
|
|
28
28
|
protected
|
@@ -2,10 +2,10 @@
|
|
2
2
|
#= require_tree ./templates
|
3
3
|
#= require_tree ./models
|
4
4
|
#= require_tree ./views
|
5
|
-
#= require_tree ./
|
5
|
+
#= require_tree ./routers
|
6
6
|
|
7
7
|
window.<%= application_name.capitalize %> =
|
8
8
|
Models: {}
|
9
9
|
Collections: {}
|
10
|
-
|
10
|
+
Routers: {}
|
11
11
|
Views: {}
|
@@ -11,7 +11,7 @@ module Backbone
|
|
11
11
|
argument :attributes, :type => :array, :default => [], :banner => "field:type field:type"
|
12
12
|
|
13
13
|
def create_backbone_model
|
14
|
-
template "model.coffee", "#{backbone_path}/models/#{file_name}.coffee"
|
14
|
+
template "model.coffee", "#{backbone_path}/models/#{file_name}.js.coffee"
|
15
15
|
end
|
16
16
|
|
17
17
|
end
|
@@ -18,8 +18,8 @@ module Backbone
|
|
18
18
|
[application_name.capitalize, "Views", plural_name.capitalize].join(".")
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
[application_name.capitalize, "
|
21
|
+
def router_namespace
|
22
|
+
[application_name.capitalize, "Routers", plural_name.capitalize].join(".")
|
23
23
|
end
|
24
24
|
|
25
25
|
def jst(action)
|
data/lib/generators/backbone/{controller/controller_generator.rb → router/router_generator.rb}
RENAMED
@@ -2,11 +2,11 @@ require 'generators/backbone/resource_helpers'
|
|
2
2
|
|
3
3
|
module Backbone
|
4
4
|
module Generators
|
5
|
-
class
|
5
|
+
class RouterGenerator < Rails::Generators::NamedBase
|
6
6
|
include Backbone::Generators::ResourceHelpers
|
7
7
|
|
8
8
|
source_root File.expand_path("../templates", __FILE__)
|
9
|
-
desc "This generator creates a backbone
|
9
|
+
desc "This generator creates a backbone router with views and templates for the provided actions"
|
10
10
|
|
11
11
|
argument :actions, :type => :array, :default => [], :banner => "action action"
|
12
12
|
|
@@ -24,14 +24,14 @@ module Backbone
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
template '
|
27
|
+
def create_router_files
|
28
|
+
template 'router.coffee', File.join(backbone_path, "routers", class_path, "#{file_name}_router.js.coffee")
|
29
29
|
end
|
30
30
|
|
31
31
|
def create_view_files
|
32
32
|
actions.each do |action|
|
33
33
|
@action = action
|
34
|
-
@view_path = File.join(backbone_path, "views", plural_name, "#{action}_view.coffee")
|
34
|
+
@view_path = File.join(backbone_path, "views", plural_name, "#{action}_view.js.coffee")
|
35
35
|
@jst_path = File.join(backbone_path,"templates", plural_name, "#{action}.jst.ejs")
|
36
36
|
|
37
37
|
template "view.coffee", @view_path
|
File without changes
|
File without changes
|
@@ -7,17 +7,17 @@ module Backbone
|
|
7
7
|
source_root File.expand_path("../templates", __FILE__)
|
8
8
|
desc "This generator creates the client side crud scaffolding"
|
9
9
|
|
10
|
-
def
|
11
|
-
template '
|
10
|
+
def create_router_files
|
11
|
+
template 'router.coffee', File.join(backbone_path, "routers", class_path, "#{plural_name}_router.js.coffee")
|
12
12
|
end
|
13
13
|
|
14
14
|
def create_view_files
|
15
15
|
available_views.each do |view|
|
16
|
-
template "views/#{view}_view.coffee", File.join(backbone_path, "views", plural_name, "#{view}_view.coffee")
|
16
|
+
template "views/#{view}_view.coffee", File.join(backbone_path, "views", plural_name, "#{view}_view.js.coffee")
|
17
17
|
template "templates/#{view}.jst", File.join(backbone_path, "templates", plural_name, "#{view}.jst.ejs")
|
18
18
|
end
|
19
19
|
|
20
|
-
template "views/model_view.coffee", File.join(backbone_path, "views", plural_name, "#{singular_name}_view.coffee")
|
20
|
+
template "views/model_view.coffee", File.join(backbone_path, "views", plural_name, "#{singular_name}_view.js.coffee")
|
21
21
|
template "templates/model.jst", File.join(backbone_path, "templates", plural_name, "#{singular_name}.jst.ejs")
|
22
22
|
end
|
23
23
|
|
@@ -1,7 +1,7 @@
|
|
1
|
-
class <%=
|
1
|
+
class <%= router_namespace %>Router extends Backbone.Router
|
2
2
|
initialize: (options) ->
|
3
3
|
@<%= plural_name %> = new <%= collection_namespace %>Collection()
|
4
|
-
@<%= plural_name %>.
|
4
|
+
@<%= plural_name %>.reset options.<%= plural_name %>
|
5
5
|
|
6
6
|
routes:
|
7
7
|
"/new": "new<%= class_name %>"
|
@@ -6,7 +6,7 @@ class <%= view_namespace %>.IndexView extends Backbone.View
|
|
6
6
|
initialize: () ->
|
7
7
|
_.bindAll(this, 'addOne', 'addAll', 'render');
|
8
8
|
|
9
|
-
@options.<%= plural_name %>.bind('
|
9
|
+
@options.<%= plural_name %>.bind('reset', this.addAll);
|
10
10
|
|
11
11
|
addAll: () ->
|
12
12
|
@options.<%= plural_name %>.each(this.addOne)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
// Backbone.js 0.
|
1
|
+
// Backbone.js 0.5.0
|
2
2
|
// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
|
3
3
|
// Backbone may be freely distributed under the MIT license.
|
4
4
|
// For all details and documentation:
|
@@ -25,7 +25,7 @@
|
|
25
25
|
}
|
26
26
|
|
27
27
|
// Current version of the library. Keep in sync with `package.json`.
|
28
|
-
Backbone.VERSION = '0.
|
28
|
+
Backbone.VERSION = '0.5.0';
|
29
29
|
|
30
30
|
// Require Underscore, if we're on the server, and it's not already present.
|
31
31
|
var _ = root._;
|
@@ -70,7 +70,7 @@
|
|
70
70
|
// Passing `"all"` will bind the callback to all events fired.
|
71
71
|
bind : function(ev, callback) {
|
72
72
|
var calls = this._callbacks || (this._callbacks = {});
|
73
|
-
var list =
|
73
|
+
var list = calls[ev] || (calls[ev] = []);
|
74
74
|
list.push(callback);
|
75
75
|
return this;
|
76
76
|
},
|
@@ -103,13 +103,13 @@
|
|
103
103
|
// same arguments as `trigger` is, apart from the event name.
|
104
104
|
// Listening for `"all"` passes the true event name as the first argument.
|
105
105
|
trigger : function(eventName) {
|
106
|
-
var list, calls, ev, callback, args
|
106
|
+
var list, calls, ev, callback, args;
|
107
107
|
var both = 2;
|
108
108
|
if (!(calls = this._callbacks)) return this;
|
109
109
|
while (both--) {
|
110
110
|
ev = both ? eventName : 'all';
|
111
111
|
if (list = calls[ev]) {
|
112
|
-
for (i = 0, l = list.length; i < l; i++) {
|
112
|
+
for (var i = 0, l = list.length; i < l; i++) {
|
113
113
|
if (!(callback = list[i])) {
|
114
114
|
list.splice(i, 1); i--; l--;
|
115
115
|
} else {
|
@@ -143,7 +143,7 @@
|
|
143
143
|
this._changed = false;
|
144
144
|
this._previousAttributes = _.clone(this.attributes);
|
145
145
|
if (options && options.collection) this.collection = options.collection;
|
146
|
-
this.initialize(
|
146
|
+
this.initialize.apply(this, arguments);
|
147
147
|
};
|
148
148
|
|
149
149
|
// Attach all inheritable methods to the Model prototype.
|
@@ -204,6 +204,10 @@
|
|
204
204
|
// Check for changes of `id`.
|
205
205
|
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
|
206
206
|
|
207
|
+
// We're about to start triggering change events.
|
208
|
+
var alreadyChanging = this._changing;
|
209
|
+
this._changing = true;
|
210
|
+
|
207
211
|
// Update attributes.
|
208
212
|
for (var attr in attrs) {
|
209
213
|
var val = attrs[attr];
|
@@ -216,7 +220,8 @@
|
|
216
220
|
}
|
217
221
|
|
218
222
|
// Fire the `"change"` event, if the model has been changed.
|
219
|
-
if (!options.silent && this._changed) this.change(options);
|
223
|
+
if (!alreadyChanging && !options.silent && this._changed) this.change(options);
|
224
|
+
this._changing = false;
|
220
225
|
return this;
|
221
226
|
},
|
222
227
|
|
@@ -248,6 +253,7 @@
|
|
248
253
|
// to silence it.
|
249
254
|
clear : function(options) {
|
250
255
|
options || (options = {});
|
256
|
+
var attr;
|
251
257
|
var old = this.attributes;
|
252
258
|
|
253
259
|
// Run validation.
|
@@ -299,10 +305,11 @@
|
|
299
305
|
return (this.sync || Backbone.sync).call(this, method, this, options);
|
300
306
|
},
|
301
307
|
|
302
|
-
// Destroy this model on the server. Upon success, the model is removed
|
308
|
+
// Destroy this model on the server if it was already persisted. Upon success, the model is removed
|
303
309
|
// from its collection, if it has one.
|
304
310
|
destroy : function(options) {
|
305
311
|
options || (options = {});
|
312
|
+
if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
|
306
313
|
var model = this;
|
307
314
|
var success = options.success;
|
308
315
|
options.success = function(resp) {
|
@@ -333,10 +340,9 @@
|
|
333
340
|
return new this.constructor(this);
|
334
341
|
},
|
335
342
|
|
336
|
-
// A model is new if it has never been saved to the server, and
|
337
|
-
// ID.
|
343
|
+
// A model is new if it has never been saved to the server, and lacks an id.
|
338
344
|
isNew : function() {
|
339
|
-
return
|
345
|
+
return this.id == null;
|
340
346
|
},
|
341
347
|
|
342
348
|
// Call this method to manually fire a `change` event for this model.
|
@@ -391,7 +397,7 @@
|
|
391
397
|
var error = this.validate(attrs);
|
392
398
|
if (error) {
|
393
399
|
if (options.error) {
|
394
|
-
options.error(this, error);
|
400
|
+
options.error(this, error, options);
|
395
401
|
} else {
|
396
402
|
this.trigger('error', this, error, options);
|
397
403
|
}
|
@@ -410,14 +416,11 @@
|
|
410
416
|
// its models in sort order, as they're added and removed.
|
411
417
|
Backbone.Collection = function(models, options) {
|
412
418
|
options || (options = {});
|
413
|
-
if (options.comparator)
|
414
|
-
this.comparator = options.comparator;
|
415
|
-
delete options.comparator;
|
416
|
-
}
|
419
|
+
if (options.comparator) this.comparator = options.comparator;
|
417
420
|
_.bindAll(this, '_onModelEvent', '_removeReference');
|
418
421
|
this._reset();
|
419
|
-
if (models) this.
|
420
|
-
this.initialize(
|
422
|
+
if (models) this.reset(models, {silent: true});
|
423
|
+
this.initialize.apply(this, arguments);
|
421
424
|
};
|
422
425
|
|
423
426
|
// Define the Collection's inheritable methods.
|
@@ -485,7 +488,7 @@
|
|
485
488
|
options || (options = {});
|
486
489
|
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
|
487
490
|
this.models = this.sortBy(this.comparator);
|
488
|
-
if (!options.silent) this.trigger('
|
491
|
+
if (!options.silent) this.trigger('reset', this, options);
|
489
492
|
return this;
|
490
493
|
},
|
491
494
|
|
@@ -495,27 +498,27 @@
|
|
495
498
|
},
|
496
499
|
|
497
500
|
// When you have more items than you want to add or remove individually,
|
498
|
-
// you can
|
499
|
-
// any `added` or `removed` events. Fires `
|
500
|
-
|
501
|
+
// you can reset the entire set with a new list of models, without firing
|
502
|
+
// any `added` or `removed` events. Fires `reset` when finished.
|
503
|
+
reset : function(models, options) {
|
501
504
|
models || (models = []);
|
502
505
|
options || (options = {});
|
503
506
|
this.each(this._removeReference);
|
504
507
|
this._reset();
|
505
508
|
this.add(models, {silent: true});
|
506
|
-
if (!options.silent) this.trigger('
|
509
|
+
if (!options.silent) this.trigger('reset', this, options);
|
507
510
|
return this;
|
508
511
|
},
|
509
512
|
|
510
|
-
// Fetch the default set of models for this collection,
|
513
|
+
// Fetch the default set of models for this collection, resetting the
|
511
514
|
// collection when they arrive. If `add: true` is passed, appends the
|
512
|
-
// models to the collection instead of
|
515
|
+
// models to the collection instead of resetting.
|
513
516
|
fetch : function(options) {
|
514
517
|
options || (options = {});
|
515
518
|
var collection = this;
|
516
519
|
var success = options.success;
|
517
520
|
options.success = function(resp, status, xhr) {
|
518
|
-
collection[options.add ? 'add' : '
|
521
|
+
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
|
519
522
|
if (success) success(collection, resp);
|
520
523
|
};
|
521
524
|
options.error = wrapError(options.error, collection, options);
|
@@ -524,19 +527,15 @@
|
|
524
527
|
|
525
528
|
// Create a new instance of a model in this collection. After the model
|
526
529
|
// has been created on the server, it will be added to the collection.
|
530
|
+
// Returns the model, or 'false' if validation on a new model fails.
|
527
531
|
create : function(model, options) {
|
528
532
|
var coll = this;
|
529
533
|
options || (options = {});
|
530
|
-
|
531
|
-
|
532
|
-
model = new this.model(null, {collection: coll});
|
533
|
-
if (!model.set(attrs, options)) return false;
|
534
|
-
} else {
|
535
|
-
model.collection = coll;
|
536
|
-
}
|
534
|
+
model = this._prepareModel(model, options);
|
535
|
+
if (!model) return false;
|
537
536
|
var success = options.success;
|
538
537
|
options.success = function(nextModel, resp, xhr) {
|
539
|
-
coll.add(nextModel);
|
538
|
+
coll.add(nextModel, options);
|
540
539
|
if (success) success(nextModel, resp, xhr);
|
541
540
|
};
|
542
541
|
model.save(null, options);
|
@@ -556,7 +555,7 @@
|
|
556
555
|
return _(this.models).chain();
|
557
556
|
},
|
558
557
|
|
559
|
-
// Reset all internal state. Called when the collection is
|
558
|
+
// Reset all internal state. Called when the collection is reset.
|
560
559
|
_reset : function(options) {
|
561
560
|
this.length = 0;
|
562
561
|
this.models = [];
|
@@ -564,21 +563,32 @@
|
|
564
563
|
this._byCid = {};
|
565
564
|
},
|
566
565
|
|
566
|
+
// Prepare a model to be added to this collection
|
567
|
+
_prepareModel: function(model, options) {
|
568
|
+
if (!(model instanceof Backbone.Model)) {
|
569
|
+
var attrs = model;
|
570
|
+
model = new this.model(attrs, {collection: this});
|
571
|
+
if (model.validate && !model._performValidation(attrs, options)) model = false;
|
572
|
+
} else if (!model.collection) {
|
573
|
+
model.collection = this;
|
574
|
+
}
|
575
|
+
return model;
|
576
|
+
},
|
577
|
+
|
567
578
|
// Internal implementation of adding a single model to the set, updating
|
568
579
|
// hash indexes for `id` and `cid` lookups.
|
580
|
+
// Returns the model, or 'false' if validation on a new model fails.
|
569
581
|
_add : function(model, options) {
|
570
582
|
options || (options = {});
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
var already = this.getByCid(model);
|
583
|
+
model = this._prepareModel(model, options);
|
584
|
+
if (!model) return false;
|
585
|
+
var already = this.getByCid(model) || this.get(model);
|
575
586
|
if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
|
576
587
|
this._byId[model.id] = model;
|
577
588
|
this._byCid[model.cid] = model;
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
var index = this.comparator ? this.sortedIndex(model, this.comparator) : this.length;
|
589
|
+
var index = options.at != null ? options.at :
|
590
|
+
this.comparator ? this.sortedIndex(model, this.comparator) :
|
591
|
+
this.length;
|
582
592
|
this.models.splice(index, 0, model);
|
583
593
|
model.bind('all', this._onModelEvent);
|
584
594
|
this.length++;
|
@@ -640,16 +650,16 @@
|
|
640
650
|
};
|
641
651
|
});
|
642
652
|
|
643
|
-
// Backbone.
|
653
|
+
// Backbone.Router
|
644
654
|
// -------------------
|
645
655
|
|
646
|
-
//
|
656
|
+
// Routers map faux-URLs to actions, and fire events when routes are
|
647
657
|
// matched. Creating a new one sets its `routes` hash, if not set statically.
|
648
|
-
Backbone.
|
658
|
+
Backbone.Router = function(options) {
|
649
659
|
options || (options = {});
|
650
660
|
if (options.routes) this.routes = options.routes;
|
651
661
|
this._bindRoutes();
|
652
|
-
this.initialize(
|
662
|
+
this.initialize.apply(this, arguments);
|
653
663
|
};
|
654
664
|
|
655
665
|
// Cached regular expressions for matching named param parts and splatted
|
@@ -658,8 +668,8 @@
|
|
658
668
|
var splatParam = /\*([\w\d]+)/g;
|
659
669
|
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
|
660
670
|
|
661
|
-
// Set up all inheritable **Backbone.
|
662
|
-
_.extend(Backbone.
|
671
|
+
// Set up all inheritable **Backbone.Router** properties and methods.
|
672
|
+
_.extend(Backbone.Router.prototype, Backbone.Events, {
|
663
673
|
|
664
674
|
// Initialize is an empty function by default. Override it with your own
|
665
675
|
// initialization logic.
|
@@ -674,17 +684,16 @@
|
|
674
684
|
route : function(route, name, callback) {
|
675
685
|
Backbone.history || (Backbone.history = new Backbone.History);
|
676
686
|
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
|
677
|
-
Backbone.history.route(route, _.bind(function(
|
678
|
-
var args = this._extractParameters(route,
|
687
|
+
Backbone.history.route(route, _.bind(function(fragment) {
|
688
|
+
var args = this._extractParameters(route, fragment);
|
679
689
|
callback.apply(this, args);
|
680
690
|
this.trigger.apply(this, ['route:' + name].concat(args));
|
681
691
|
}, this));
|
682
692
|
},
|
683
693
|
|
684
|
-
// Simple proxy to `Backbone.history` to save a fragment into the history
|
685
|
-
|
686
|
-
|
687
|
-
Backbone.history.saveLocation(hash);
|
694
|
+
// Simple proxy to `Backbone.history` to save a fragment into the history.
|
695
|
+
navigate : function(fragment, triggerRoute) {
|
696
|
+
Backbone.history.navigate(fragment, triggerRoute);
|
688
697
|
},
|
689
698
|
|
690
699
|
// Bind all defined routes to `Backbone.history`. We have to reverse the
|
@@ -712,8 +721,8 @@
|
|
712
721
|
|
713
722
|
// Given a route, and a URL fragment that it matches, return the array of
|
714
723
|
// extracted parameters.
|
715
|
-
_extractParameters : function(route,
|
716
|
-
return route.exec(
|
724
|
+
_extractParameters : function(route, fragment) {
|
725
|
+
return route.exec(fragment).slice(1);
|
717
726
|
}
|
718
727
|
|
719
728
|
});
|
@@ -721,7 +730,7 @@
|
|
721
730
|
// Backbone.History
|
722
731
|
// ----------------
|
723
732
|
|
724
|
-
// Handles cross-browser history management, based on URL
|
733
|
+
// Handles cross-browser history management, based on URL fragments. If the
|
725
734
|
// browser does not support `onhashchange`, falls back to polling.
|
726
735
|
Backbone.History = function() {
|
727
736
|
this.handlers = [];
|
@@ -744,33 +753,67 @@
|
|
744
753
|
// twenty times a second.
|
745
754
|
interval: 50,
|
746
755
|
|
747
|
-
// Get the cross-browser normalized URL fragment
|
748
|
-
|
749
|
-
|
756
|
+
// Get the cross-browser normalized URL fragment, either from the URL,
|
757
|
+
// the hash, or the override.
|
758
|
+
getFragment : function(fragment, forcePushState) {
|
759
|
+
if (fragment == null) {
|
760
|
+
if (this._hasPushState || forcePushState) {
|
761
|
+
fragment = window.location.pathname;
|
762
|
+
var search = window.location.search;
|
763
|
+
if (search) fragment += search;
|
764
|
+
if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length);
|
765
|
+
} else {
|
766
|
+
fragment = window.location.hash;
|
767
|
+
}
|
768
|
+
}
|
769
|
+
return fragment.replace(hashStrip, '');
|
750
770
|
},
|
751
771
|
|
752
772
|
// Start the hash change handling, returning `true` if the current URL matches
|
753
773
|
// an existing route, and `false` otherwise.
|
754
|
-
start : function() {
|
774
|
+
start : function(options) {
|
775
|
+
|
776
|
+
// Figure out the initial configuration. Do we need an iframe?
|
777
|
+
// Is pushState desired ... is it available?
|
755
778
|
if (historyStarted) throw new Error("Backbone.history has already been started");
|
756
|
-
|
757
|
-
|
758
|
-
|
779
|
+
this.options = _.extend({}, {root: '/'}, this.options, options);
|
780
|
+
this._wantsPushState = !!this.options.pushState;
|
781
|
+
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
|
782
|
+
var fragment = this.getFragment();
|
783
|
+
var docMode = document.documentMode;
|
784
|
+
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
|
759
785
|
if (oldIE) {
|
760
786
|
this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
|
761
|
-
this.
|
787
|
+
this.navigate(fragment);
|
762
788
|
}
|
763
|
-
|
789
|
+
|
790
|
+
// Depending on whether we're using pushState or hashes, and whether
|
791
|
+
// 'onhashchange' is supported, determine how we check the URL state.
|
792
|
+
if (this._hasPushState) {
|
793
|
+
$(window).bind('popstate', this.checkUrl);
|
794
|
+
} else if ('onhashchange' in window && !oldIE) {
|
764
795
|
$(window).bind('hashchange', this.checkUrl);
|
765
796
|
} else {
|
766
797
|
setInterval(this.checkUrl, this.interval);
|
767
798
|
}
|
768
|
-
|
799
|
+
|
800
|
+
// Determine if we need to change the base url, for a pushState link
|
801
|
+
// opened by a non-pushState browser.
|
802
|
+
this.fragment = fragment;
|
769
803
|
historyStarted = true;
|
770
|
-
|
804
|
+
var started = this.loadUrl() || this.loadUrl(window.location.hash);
|
805
|
+
var atRoot = window.location.pathname == this.options.root;
|
806
|
+
if (this._wantsPushState && !this._hasPushState && !atRoot) {
|
807
|
+
this.fragment = this.getFragment(null, true);
|
808
|
+
window.location = this.options.root + '#' + this.fragment;
|
809
|
+
} else if (this._wantsPushState && this._hasPushState && atRoot && window.location.hash) {
|
810
|
+
this.navigate(window.location.hash);
|
811
|
+
} else {
|
812
|
+
return started;
|
813
|
+
}
|
771
814
|
},
|
772
815
|
|
773
|
-
// Add a route to be tested when the
|
816
|
+
// Add a route to be tested when the fragment changes. Routes added later may
|
774
817
|
// override previous routes.
|
775
818
|
route : function(route, callback) {
|
776
819
|
this.handlers.unshift({route : route, callback : callback});
|
@@ -778,23 +821,22 @@
|
|
778
821
|
|
779
822
|
// Checks the current URL to see if it has changed, and if it has,
|
780
823
|
// calls `loadUrl`, normalizing across the hidden iframe.
|
781
|
-
checkUrl : function() {
|
782
|
-
var
|
783
|
-
if (
|
784
|
-
if (
|
785
|
-
if (this.iframe) this.
|
786
|
-
this.
|
787
|
-
this.loadUrl();
|
824
|
+
checkUrl : function(e) {
|
825
|
+
var current = this.getFragment();
|
826
|
+
if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
|
827
|
+
if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
|
828
|
+
if (this.iframe) this.navigate(current);
|
829
|
+
this.loadUrl() || this.loadUrl(window.location.hash);
|
788
830
|
},
|
789
831
|
|
790
832
|
// Attempt to load the current URL fragment. If a route succeeds with a
|
791
833
|
// match, returns `true`. If no defined routes matches the fragment,
|
792
834
|
// returns `false`.
|
793
|
-
loadUrl : function() {
|
794
|
-
var
|
835
|
+
loadUrl : function(fragmentOverride) {
|
836
|
+
var fragment = this.fragment = this.getFragment(fragmentOverride);
|
795
837
|
var matched = _.any(this.handlers, function(handler) {
|
796
|
-
if (handler.route.test(
|
797
|
-
handler.callback(
|
838
|
+
if (handler.route.test(fragment)) {
|
839
|
+
handler.callback(fragment);
|
798
840
|
return true;
|
799
841
|
}
|
800
842
|
});
|
@@ -804,14 +846,22 @@
|
|
804
846
|
// Save a fragment into the hash history. You are responsible for properly
|
805
847
|
// URL-encoding the fragment in advance. This does not trigger
|
806
848
|
// a `hashchange` event.
|
807
|
-
|
808
|
-
|
809
|
-
if (this.
|
810
|
-
|
811
|
-
|
812
|
-
this.
|
813
|
-
this.
|
849
|
+
navigate : function(fragment, triggerRoute) {
|
850
|
+
fragment = (fragment || '').replace(hashStrip, '');
|
851
|
+
if (this.fragment == fragment || this.fragment == decodeURIComponent(fragment)) return;
|
852
|
+
if (this._hasPushState) {
|
853
|
+
var loc = window.location;
|
854
|
+
if (fragment.indexOf(this.options.root) != 0) fragment = this.options.root + fragment;
|
855
|
+
this.fragment = fragment;
|
856
|
+
window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + fragment);
|
857
|
+
} else {
|
858
|
+
window.location.hash = this.fragment = fragment;
|
859
|
+
if (this.iframe && (fragment != this.getFragment(this.iframe.location.hash))) {
|
860
|
+
this.iframe.document.open().close();
|
861
|
+
this.iframe.location.hash = fragment;
|
862
|
+
}
|
814
863
|
}
|
864
|
+
if (triggerRoute) this.loadUrl(fragment);
|
815
865
|
}
|
816
866
|
|
817
867
|
});
|
@@ -826,7 +876,7 @@
|
|
826
876
|
this._configure(options || {});
|
827
877
|
this._ensureElement();
|
828
878
|
this.delegateEvents();
|
829
|
-
this.initialize(
|
879
|
+
this.initialize.apply(this, arguments);
|
830
880
|
};
|
831
881
|
|
832
882
|
// Element lookup, scoped to DOM elements within the current view.
|
@@ -899,10 +949,11 @@
|
|
899
949
|
if (!(events || (events = this.events))) return;
|
900
950
|
$(this.el).unbind('.delegateEvents' + this.cid);
|
901
951
|
for (var key in events) {
|
902
|
-
var
|
952
|
+
var method = this[events[key]];
|
953
|
+
if (!method) throw new Error('Event "' + events[key] + '" does not exist');
|
903
954
|
var match = key.match(eventSplitter);
|
904
955
|
var eventName = match[1], selector = match[2];
|
905
|
-
|
956
|
+
method = _.bind(method, this);
|
906
957
|
eventName += '.delegateEvents' + this.cid;
|
907
958
|
if (selector === '') {
|
908
959
|
$(this.el).bind(eventName, method);
|
@@ -950,7 +1001,7 @@
|
|
950
1001
|
|
951
1002
|
// Set up inheritance for the model, collection, and view.
|
952
1003
|
Backbone.Model.extend = Backbone.Collection.extend =
|
953
|
-
Backbone.
|
1004
|
+
Backbone.Router.extend = Backbone.View.extend = extend;
|
954
1005
|
|
955
1006
|
// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
|
956
1007
|
var methodMap = {
|
@@ -1076,7 +1127,7 @@
|
|
1076
1127
|
|
1077
1128
|
// Throw an error when a URL is needed, and none is supplied.
|
1078
1129
|
var urlError = function() {
|
1079
|
-
throw new Error(
|
1130
|
+
throw new Error('A "url" property or function must be specified');
|
1080
1131
|
};
|
1081
1132
|
|
1082
1133
|
// Wrap an optional error callback with a fallback error event.
|
@@ -1092,7 +1143,7 @@
|
|
1092
1143
|
|
1093
1144
|
// Helper function to escape a string for HTML rendering.
|
1094
1145
|
var escapeHTML = function(string) {
|
1095
|
-
return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
1146
|
+
return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
|
1096
1147
|
};
|
1097
1148
|
|
1098
1149
|
}).call(this);
|
@@ -22,7 +22,11 @@
|
|
22
22
|
var params = _.extend({
|
23
23
|
type: type,
|
24
24
|
dataType: 'json',
|
25
|
-
processData: false
|
25
|
+
processData: false,
|
26
|
+
beforeSend: function( xhr ) {
|
27
|
+
var token = $('meta[name="csrf-token"]').attr('content');
|
28
|
+
if (token) xhr.setRequestHeader('X-CSRF-Token', token);
|
29
|
+
}
|
26
30
|
}, options);
|
27
31
|
|
28
32
|
if (!params.url) {
|
@@ -55,8 +55,7 @@
|
|
55
55
|
module.exports = _;
|
56
56
|
_._ = _;
|
57
57
|
} else {
|
58
|
-
|
59
|
-
root['_'] = _;
|
58
|
+
root._ = _;
|
60
59
|
}
|
61
60
|
|
62
61
|
// Current version.
|
@@ -74,7 +73,7 @@
|
|
74
73
|
obj.forEach(iterator, context);
|
75
74
|
} else if (_.isNumber(obj.length)) {
|
76
75
|
for (var i = 0, l = obj.length; i < l; i++) {
|
77
|
-
if (
|
76
|
+
if (iterator.call(context, obj[i], i, obj) === breaker) return;
|
78
77
|
}
|
79
78
|
} else {
|
80
79
|
for (var key in obj) {
|
@@ -252,16 +251,6 @@
|
|
252
251
|
}), 'value');
|
253
252
|
};
|
254
253
|
|
255
|
-
// Groups the object's values by a criterion produced by an iterator
|
256
|
-
_.groupBy = function(obj, iterator) {
|
257
|
-
var result = {};
|
258
|
-
each(obj, function(value, index) {
|
259
|
-
var key = iterator(value, index);
|
260
|
-
(result[key] || (result[key] = [])).push(value);
|
261
|
-
});
|
262
|
-
return result;
|
263
|
-
};
|
264
|
-
|
265
254
|
// Use a comparator function to figure out at what index an object should
|
266
255
|
// be inserted so as to maintain order. Uses binary search.
|
267
256
|
_.sortedIndex = function(array, obj, iterator) {
|
@@ -513,7 +502,7 @@
|
|
513
502
|
var funcs = slice.call(arguments);
|
514
503
|
return function() {
|
515
504
|
var args = slice.call(arguments);
|
516
|
-
for (var i
|
505
|
+
for (var i=funcs.length-1; i >= 0; i--) {
|
517
506
|
args = [funcs[i].apply(this, args)];
|
518
507
|
}
|
519
508
|
return args[0];
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: rails-backbone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.5.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Ryan Fitzgerald
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2011-
|
14
|
+
date: 2011-07-04 00:00:00 -04:00
|
15
15
|
default_executable:
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
requirements:
|
22
22
|
- - ~>
|
23
23
|
- !ruby/object:Gem::Version
|
24
|
-
version: 3.1
|
24
|
+
version: "3.1"
|
25
25
|
type: :runtime
|
26
26
|
prerelease: false
|
27
27
|
version_requirements: *id001
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
requirements:
|
33
33
|
- - ~>
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: 2.2
|
35
|
+
version: "2.2"
|
36
36
|
type: :runtime
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: *id002
|
@@ -58,18 +58,18 @@ extra_rdoc_files: []
|
|
58
58
|
|
59
59
|
files:
|
60
60
|
- lib/backbone-rails.rb
|
61
|
-
- lib/generators/backbone/controller/controller_generator.rb
|
62
|
-
- lib/generators/backbone/controller/templates/controller.coffee
|
63
|
-
- lib/generators/backbone/controller/templates/template.jst
|
64
|
-
- lib/generators/backbone/controller/templates/view.coffee
|
65
61
|
- lib/generators/backbone/install/install_generator.rb
|
66
62
|
- lib/generators/backbone/install/templates/app.coffee
|
67
63
|
- lib/generators/backbone/model/model_generator.rb
|
68
64
|
- lib/generators/backbone/model/templates/model.coffee
|
69
65
|
- lib/generators/backbone/resource_helpers.rb
|
66
|
+
- lib/generators/backbone/router/router_generator.rb
|
67
|
+
- lib/generators/backbone/router/templates/router.coffee
|
68
|
+
- lib/generators/backbone/router/templates/template.jst
|
69
|
+
- lib/generators/backbone/router/templates/view.coffee
|
70
70
|
- lib/generators/backbone/scaffold/scaffold_generator.rb
|
71
|
-
- lib/generators/backbone/scaffold/templates/controller.coffee
|
72
71
|
- lib/generators/backbone/scaffold/templates/model.coffee
|
72
|
+
- lib/generators/backbone/scaffold/templates/router.coffee
|
73
73
|
- lib/generators/backbone/scaffold/templates/templates/edit.jst
|
74
74
|
- lib/generators/backbone/scaffold/templates/templates/index.jst
|
75
75
|
- lib/generators/backbone/scaffold/templates/templates/model.jst
|
@@ -103,7 +103,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
103
|
requirements:
|
104
104
|
- - ">="
|
105
105
|
- !ruby/object:Gem::Version
|
106
|
-
hash:
|
106
|
+
hash: -730083928370135301
|
107
107
|
segments:
|
108
108
|
- 0
|
109
109
|
version: "0"
|
@@ -112,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
112
|
requirements:
|
113
113
|
- - ">="
|
114
114
|
- !ruby/object:Gem::Version
|
115
|
-
hash:
|
115
|
+
hash: -730083928370135301
|
116
116
|
segments:
|
117
117
|
- 0
|
118
118
|
version: "0"
|