rails-backbone 0.2.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|