laces 0.1.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/CONTRIBUTING.md +38 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +122 -0
- data/LICENSE +21 -0
- data/README.md +5 -0
- data/Rakefile +8 -0
- data/bin/laces +16 -0
- data/features/creating_a_heroku_app.feature +9 -0
- data/features/rake_clean.feature +21 -0
- data/features/skipping_clearance.feature +13 -0
- data/features/step_definitions/gem_steps.rb +5 -0
- data/features/step_definitions/heroku_steps.rb +3 -0
- data/features/step_definitions/shell_steps.rb +55 -0
- data/features/support/bin/heroku +5 -0
- data/features/support/env.rb +15 -0
- data/features/support/fake_heroku.rb +21 -0
- data/laces-0.0.1.gem +0 -0
- data/laces.gemspec +35 -0
- data/lib/laces/actions.rb +35 -0
- data/lib/laces/app_builder.rb +237 -0
- data/lib/laces/generators/app_generator.rb +111 -0
- data/lib/laces/version.rb +3 -0
- data/templates/.DS_Store +0 -0
- data/templates/Gemfile_template +76 -0
- data/templates/HEROKU_README.md +66 -0
- data/templates/Procfile +1 -0
- data/templates/README.md +81 -0
- data/templates/app/assets/imgs/glyphicons-halflings-white.png +0 -0
- data/templates/app/assets/imgs/glyphicons-halflings.png +0 -0
- data/templates/app/assets/javascripts/admin.coffee +20 -0
- data/templates/app/assets/javascripts/application.coffee +21 -0
- data/templates/app/assets/javascripts/lib/actinology.coffee +47 -0
- data/templates/app/assets/javascripts/lib/analytics.js +11 -0
- data/templates/app/assets/javascripts/lib/auth_token_sync.js +17 -0
- data/templates/app/assets/javascripts/lib/backbone-ui.js +2455 -0
- data/templates/app/assets/javascripts/lib/backbone.coffee +27 -0
- data/templates/app/assets/javascripts/lib/backbone/collection.js +249 -0
- data/templates/app/assets/javascripts/lib/backbone/events.js +64 -0
- data/templates/app/assets/javascripts/lib/backbone/helpers.js +68 -0
- data/templates/app/assets/javascripts/lib/backbone/history.js +144 -0
- data/templates/app/assets/javascripts/lib/backbone/model.js +291 -0
- data/templates/app/assets/javascripts/lib/backbone/router.coffee +45 -0
- data/templates/app/assets/javascripts/lib/backbone/sync.coffee +38 -0
- data/templates/app/assets/javascripts/lib/backbone/view.js +150 -0
- data/templates/app/assets/javascripts/lib/backbone_extended.coffee +276 -0
- data/templates/app/assets/javascripts/lib/bootstrap.js +1836 -0
- data/templates/app/assets/javascripts/lib/inflection.js +658 -0
- data/templates/app/assets/javascripts/lib/jquery-ui.js +343 -0
- data/templates/app/assets/javascripts/lib/jquery.hotkeys.js +102 -0
- data/templates/app/assets/javascripts/lib/jquery.js +4 -0
- data/templates/app/assets/javascripts/lib/milk.js.coffee +265 -0
- data/templates/app/assets/javascripts/lib/raw.js +143 -0
- data/templates/app/assets/javascripts/lib/strftime.js +732 -0
- data/templates/app/assets/javascripts/lib/throttle-debounce.js +251 -0
- data/templates/app/assets/javascripts/lib/timeago.js +148 -0
- data/templates/app/assets/javascripts/lib/underscore.js +28 -0
- data/templates/app/assets/styles/application.sass +21 -0
- data/templates/app/assets/styles/layouts/default.sass +15 -0
- data/templates/app/assets/styles/layouts/footer.sass +0 -0
- data/templates/app/assets/styles/layouts/forms.sass +34 -0
- data/templates/app/assets/styles/layouts/header.sass +0 -0
- data/templates/app/assets/styles/layouts/navigation.sass +0 -0
- data/templates/app/assets/styles/lib/backbone-ui.css +580 -0
- data/templates/app/assets/styles/lib/bootstrap.sass +4248 -0
- data/templates/app/assets/styles/pages/home.sass +0 -0
- data/templates/app/assets/styles/sessions/new.sass +0 -0
- data/templates/app/assets/styles/users/activate.sass +0 -0
- data/templates/app/assets/styles/users/new.sass +0 -0
- data/templates/app/assets/styles/users/suspended.sass +0 -0
- data/templates/app/controllers/app_controller.rb +14 -0
- data/templates/app/controllers/pages_controller.rb +2 -0
- data/templates/app/controllers/sessions_controller.rb +2 -0
- data/templates/app/controllers/templating_controller.rb +31 -0
- data/templates/app/controllers/users_controller.rb +2 -0
- data/templates/app/helpers/app_helper.rb +94 -0
- data/templates/app/helpers/users_helper.rb +53 -0
- data/templates/app/models/user.rb +9 -0
- data/templates/app/views/devise/confirmations/new.haml +9 -0
- data/templates/app/views/devise/passwords/edit.haml +11 -0
- data/templates/app/views/devise/passwords/new.haml +9 -0
- data/templates/app/views/devise/registrations/edit.haml +13 -0
- data/templates/app/views/devise/registrations/new.haml +8 -0
- data/templates/app/views/devise/sessions/_form.haml +7 -0
- data/templates/app/views/devise/sessions/new.haml +5 -0
- data/templates/app/views/devise/unlocks/new.haml +7 -0
- data/templates/app/views/layouts/_ascii.haml +0 -0
- data/templates/app/views/layouts/_column.haml +0 -0
- data/templates/app/views/layouts/_content.haml +1 -0
- data/templates/app/views/layouts/_extra.haml +0 -0
- data/templates/app/views/layouts/_footer.haml +9 -0
- data/templates/app/views/layouts/_head.haml +13 -0
- data/templates/app/views/layouts/_header.haml +6 -0
- data/templates/app/views/layouts/application.html.haml +16 -0
- data/templates/app/views/pages/home.haml +1 -0
- data/templates/config/app.yml +29 -0
- data/templates/config/application.erb +28 -0
- data/templates/config/database.yml +32 -0
- data/templates/config/initializers/devise.rb +232 -0
- data/templates/config/initializers/rabl_init.rb +4 -0
- data/templates/config/initializers/setup_mail.rb +13 -0
- data/templates/config/initializers/wrap_parameters.rb +12 -0
- data/templates/db/migrate/user_migration.rb +36 -0
- data/templates/laces_gitignore +9 -0
- data/templates/lib/development_mail_interceptor.rb +7 -0
- data/templates/lib/templating.rb +40 -0
- metadata +225 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#= require_self
|
|
2
|
+
#= require lib/backbone/events
|
|
3
|
+
#= require lib/backbone/model
|
|
4
|
+
#= require lib/backbone/collection
|
|
5
|
+
#= require lib/backbone/router
|
|
6
|
+
#= require lib/backbone/history
|
|
7
|
+
#= require lib/backbone/view
|
|
8
|
+
#= require lib/backbone/sync
|
|
9
|
+
#= require lib/backbone/helpers
|
|
10
|
+
|
|
11
|
+
root = this
|
|
12
|
+
previousBackbone = root.Backbone
|
|
13
|
+
root.Backbone = {}
|
|
14
|
+
slice = Array::slice
|
|
15
|
+
Backbone = if typeof exports isnt "undefined" then exports else root.Backbone
|
|
16
|
+
Backbone.VERSION = "0.5.3"
|
|
17
|
+
|
|
18
|
+
_ = root._
|
|
19
|
+
_ = require("underscore")._ if not _ and (typeof require isnt "undefined")
|
|
20
|
+
$ = root.jQuery or root.Zepto or root.ender
|
|
21
|
+
|
|
22
|
+
Backbone.noConflict = ->
|
|
23
|
+
root.Backbone = previousBackbone
|
|
24
|
+
this
|
|
25
|
+
|
|
26
|
+
Backbone.emulateHTTP = false
|
|
27
|
+
Backbone.emulateJSON = false
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// Backbone.Collection
|
|
2
|
+
// -------------------
|
|
3
|
+
|
|
4
|
+
// Provides a standard collection class for our sets of models, ordered
|
|
5
|
+
// or unordered. If a `comparator` is specified, the Collection will maintain
|
|
6
|
+
// its models in sort order, as they're added and removed.
|
|
7
|
+
Backbone.Collection = function(models, options) {
|
|
8
|
+
options || (options = {});
|
|
9
|
+
if (options.comparator) this.comparator = options.comparator;
|
|
10
|
+
_.bindAll(this, '_onModelEvent', '_removeReference');
|
|
11
|
+
this._reset();
|
|
12
|
+
if (models) this.reset(models, {silent: true});
|
|
13
|
+
this.initialize.apply(this, arguments);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Define the Collection's inheritable methods.
|
|
17
|
+
_.extend(Backbone.Collection.prototype, Backbone.Events, {
|
|
18
|
+
|
|
19
|
+
// The default model for a collection is just a **Backbone.Model**.
|
|
20
|
+
// This should be overridden in most cases.
|
|
21
|
+
model : Backbone.Model,
|
|
22
|
+
|
|
23
|
+
// Initialize is an empty function by default. Override it with your own
|
|
24
|
+
// initialization logic.
|
|
25
|
+
initialize : function(){},
|
|
26
|
+
|
|
27
|
+
// The JSON representation of a Collection is an array of the
|
|
28
|
+
// models' attributes.
|
|
29
|
+
toJSON : function() {
|
|
30
|
+
return this.map(function(model){ return model.toJSON(); });
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
// Add a model, or list of models to the set. Pass **silent** to avoid
|
|
34
|
+
// firing the `added` event for every new model.
|
|
35
|
+
add : function(models, options) {
|
|
36
|
+
if (_.isArray(models)) {
|
|
37
|
+
for (var i = 0, l = models.length; i < l; i++) {
|
|
38
|
+
this._add(models[i], options);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
this._add(models, options);
|
|
42
|
+
}
|
|
43
|
+
return this;
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Remove a model, or a list of models from the set. Pass silent to avoid
|
|
47
|
+
// firing the `removed` event for every model removed.
|
|
48
|
+
remove : function(models, options) {
|
|
49
|
+
if (_.isArray(models)) {
|
|
50
|
+
for (var i = 0, l = models.length; i < l; i++) {
|
|
51
|
+
this._remove(models[i], options);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
this._remove(models, options);
|
|
55
|
+
}
|
|
56
|
+
return this;
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Get a model from the set by id.
|
|
60
|
+
get : function(id) {
|
|
61
|
+
if (id == null) return null;
|
|
62
|
+
return this._byId[id.id != null ? id.id : id];
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Get a model from the set by client id.
|
|
66
|
+
getByCid : function(cid) {
|
|
67
|
+
return cid && this._byCid[cid.cid || cid];
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Get the model at the given index.
|
|
71
|
+
at: function(index) {
|
|
72
|
+
return this.models[index];
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// Force the collection to re-sort itself. You don't need to call this under normal
|
|
76
|
+
// circumstances, as the set will maintain sort order as each item is added.
|
|
77
|
+
sort : function(options) {
|
|
78
|
+
options || (options = {});
|
|
79
|
+
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
|
|
80
|
+
this.models = this.sortBy(this.comparator);
|
|
81
|
+
if (!options.silent) this.trigger('reset', this, options);
|
|
82
|
+
return this;
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Pluck an attribute from each model in the collection.
|
|
86
|
+
pluck : function(attr) {
|
|
87
|
+
return _.map(this.models, function(model){ return model.get(attr); });
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// When you have more items than you want to add or remove individually,
|
|
91
|
+
// you can reset the entire set with a new list of models, without firing
|
|
92
|
+
// any `added` or `removed` events. Fires `reset` when finished.
|
|
93
|
+
reset : function(models, options) {
|
|
94
|
+
models || (models = []);
|
|
95
|
+
options || (options = {});
|
|
96
|
+
this.each(this._removeReference);
|
|
97
|
+
this._reset();
|
|
98
|
+
this.add(models, {silent: true});
|
|
99
|
+
if (!options.silent) this.trigger('reset', this, options);
|
|
100
|
+
return this;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
// Fetch the default set of models for this collection, resetting the
|
|
104
|
+
// collection when they arrive. If `add: true` is passed, appends the
|
|
105
|
+
// models to the collection instead of resetting.
|
|
106
|
+
fetch : function(options) {
|
|
107
|
+
options || (options = {});
|
|
108
|
+
var collection = this;
|
|
109
|
+
var success = options.success;
|
|
110
|
+
var parse = options.parse;
|
|
111
|
+
options.success = function(resp, status, xhr) {
|
|
112
|
+
if (parse)
|
|
113
|
+
parse_results = parse.call(collection, resp, xhr);
|
|
114
|
+
else
|
|
115
|
+
parse_results = collection.parse(resp, xhr);
|
|
116
|
+
collection[options.add ? 'add' : 'reset'](parse_results, options);
|
|
117
|
+
if (success) success(collection, resp);
|
|
118
|
+
};
|
|
119
|
+
options.error = wrapError(options.error, collection, options);
|
|
120
|
+
return (this.sync || Backbone.sync).call(this, 'read', this, options);
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// Create a new instance of a model in this collection. After the model
|
|
124
|
+
// has been created on the server, it will be added to the collection.
|
|
125
|
+
// Returns the model, or 'false' if validation on a new model fails.
|
|
126
|
+
create : function(model, options) {
|
|
127
|
+
var coll = this;
|
|
128
|
+
options || (options = {});
|
|
129
|
+
model = this._prepareModel(model, options);
|
|
130
|
+
if (!model) return false;
|
|
131
|
+
var success = options.success;
|
|
132
|
+
options.success = function(nextModel, resp, xhr) {
|
|
133
|
+
coll.add(nextModel, options);
|
|
134
|
+
if (success) success(nextModel, resp, xhr);
|
|
135
|
+
};
|
|
136
|
+
model.save(null, options);
|
|
137
|
+
return model;
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// **parse** converts a response into a list of models to be added to the
|
|
141
|
+
// collection. The default implementation is just to pass it through.
|
|
142
|
+
parse : function(resp, xhr) {
|
|
143
|
+
return resp;
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
// Proxy to _'s chain. Can't be proxied the same way the rest of the
|
|
147
|
+
// underscore methods are proxied because it relies on the underscore
|
|
148
|
+
// constructor.
|
|
149
|
+
chain: function () {
|
|
150
|
+
return _(this.models).chain();
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
// Reset all internal state. Called when the collection is reset.
|
|
154
|
+
_reset : function(options) {
|
|
155
|
+
this.length = 0;
|
|
156
|
+
this.models = [];
|
|
157
|
+
this._byId = {};
|
|
158
|
+
this._byCid = {};
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// Prepare a model to be added to this collection
|
|
162
|
+
_prepareModel: function(model, options) {
|
|
163
|
+
if (!(model instanceof Backbone.Model)) {
|
|
164
|
+
var attrs = model;
|
|
165
|
+
model = new this.model(attrs, {collection: this});
|
|
166
|
+
if (model.validate && !model._performValidation(model.attributes, options)) model = false;
|
|
167
|
+
} else if (!model.collection) {
|
|
168
|
+
model.collection = this;
|
|
169
|
+
}
|
|
170
|
+
return model;
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
// Internal implementation of adding a single model to the set, updating
|
|
174
|
+
// hash indexes for `id` and `cid` lookups.
|
|
175
|
+
// Returns the model, or 'false' if validation on a new model fails.
|
|
176
|
+
_add : function(model, options) {
|
|
177
|
+
options || (options = {});
|
|
178
|
+
model = this._prepareModel(model, options);
|
|
179
|
+
if (!model) return false;
|
|
180
|
+
var already = this.getByCid(model);
|
|
181
|
+
if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
|
|
182
|
+
this._byId[model.id] = model;
|
|
183
|
+
this._byCid[model.cid] = model;
|
|
184
|
+
var index = options.at != null ? options.at :
|
|
185
|
+
this.comparator ? this.sortedIndex(model, this.comparator) :
|
|
186
|
+
this.length;
|
|
187
|
+
this.models.splice(index, 0, model);
|
|
188
|
+
model.bind('all', this._onModelEvent);
|
|
189
|
+
this.length++;
|
|
190
|
+
options.index = index;
|
|
191
|
+
if (!options.silent) model.trigger('add', model, this, options);
|
|
192
|
+
return model;
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// Internal implementation of removing a single model from the set, updating
|
|
196
|
+
// hash indexes for `id` and `cid` lookups.
|
|
197
|
+
_remove : function(model, options) {
|
|
198
|
+
options || (options = {});
|
|
199
|
+
model = this.getByCid(model) || this.get(model);
|
|
200
|
+
if (!model) return null;
|
|
201
|
+
delete this._byId[model.id];
|
|
202
|
+
delete this._byCid[model.cid];
|
|
203
|
+
var index = this.indexOf(model);
|
|
204
|
+
this.models.splice(index, 1);
|
|
205
|
+
this.length--;
|
|
206
|
+
options.index = index;
|
|
207
|
+
if (!options.silent) model.trigger('remove', model, this, options);
|
|
208
|
+
this._removeReference(model);
|
|
209
|
+
return model;
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
// Internal method to remove a model's ties to a collection.
|
|
213
|
+
_removeReference : function(model) {
|
|
214
|
+
if (this == model.collection) {
|
|
215
|
+
delete model.collection;
|
|
216
|
+
}
|
|
217
|
+
model.unbind('all', this._onModelEvent);
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
// Internal method called every time a model in the set fires an event.
|
|
221
|
+
// Sets need to update their indexes when models change ids. All other
|
|
222
|
+
// events simply proxy through. "add" and "remove" events that originate
|
|
223
|
+
// in other collections are ignored.
|
|
224
|
+
_onModelEvent : function(ev, model, collection, options) {
|
|
225
|
+
if ((ev == 'add' || ev == 'remove') && collection != this) return;
|
|
226
|
+
if (ev == 'destroy') {
|
|
227
|
+
this._remove(model, options);
|
|
228
|
+
}
|
|
229
|
+
if (model && ev === 'change:' + model.idAttribute) {
|
|
230
|
+
delete this._byId[model.previous(model.idAttribute)];
|
|
231
|
+
this._byId[model.id] = model;
|
|
232
|
+
}
|
|
233
|
+
this.trigger.apply(this, arguments);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Underscore methods that we want to implement on the Collection.
|
|
239
|
+
var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
|
|
240
|
+
'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
|
|
241
|
+
'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
|
|
242
|
+
'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy'];
|
|
243
|
+
|
|
244
|
+
// Mix in each Underscore method as a proxy to `Collection#models`.
|
|
245
|
+
_.each(methods, function(method) {
|
|
246
|
+
Backbone.Collection.prototype[method] = function() {
|
|
247
|
+
return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
|
|
248
|
+
};
|
|
249
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Backbone.Events
|
|
2
|
+
// -----------------
|
|
3
|
+
|
|
4
|
+
// A module that can be mixed in to *any object* in order to provide it with
|
|
5
|
+
// custom events. You may `bind` or `unbind` a callback function to an event;
|
|
6
|
+
// `trigger`-ing an event fires all callbacks in succession.
|
|
7
|
+
//
|
|
8
|
+
// var object = {};
|
|
9
|
+
// _.extend(object, Backbone.Events);
|
|
10
|
+
// object.bind('expand', function(){ alert('expanded'); });
|
|
11
|
+
// object.trigger('expand');
|
|
12
|
+
//
|
|
13
|
+
Backbone.Events = {
|
|
14
|
+
|
|
15
|
+
// Bind an event, specified by a string name, `ev`, to a `callback` function.
|
|
16
|
+
// Passing `"all"` will bind the callback to all events fired.
|
|
17
|
+
bind : function(ev, callback, context) {
|
|
18
|
+
var calls = this._callbacks || (this._callbacks = {});
|
|
19
|
+
var list = calls[ev] || (calls[ev] = {});
|
|
20
|
+
var tail = list.tail || (list.tail = list.next = {});
|
|
21
|
+
tail.callback = callback;
|
|
22
|
+
tail.context = context;
|
|
23
|
+
list.tail = tail.next = {};
|
|
24
|
+
return this;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Remove one or many callbacks. If `callback` is null, removes all
|
|
28
|
+
// callbacks for the event. If `ev` is null, removes all bound callbacks
|
|
29
|
+
// for all events.
|
|
30
|
+
unbind : function(ev, callback) {
|
|
31
|
+
var calls, node, prev;
|
|
32
|
+
if (!ev) {
|
|
33
|
+
this._callbacks = null;
|
|
34
|
+
} else if (calls = this._callbacks) {
|
|
35
|
+
if (!callback) {
|
|
36
|
+
calls[ev] = {};
|
|
37
|
+
} else if (node = calls[ev]) {
|
|
38
|
+
while ((prev = node) && (node = node.next)) {
|
|
39
|
+
if (node.callback !== callback) continue;
|
|
40
|
+
prev.next = node.next;
|
|
41
|
+
node.context = node.callback = null;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return this;
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// Trigger an event, firing all bound callbacks. Callbacks are passed the
|
|
50
|
+
// same arguments as `trigger` is, apart from the event name.
|
|
51
|
+
// Listening for `"all"` passes the true event name as the first argument.
|
|
52
|
+
trigger : function(eventName) {
|
|
53
|
+
var node, calls, callback, args, ev, events = ['all', eventName];
|
|
54
|
+
if (!(calls = this._callbacks)) return this;
|
|
55
|
+
while (ev = events.pop()) {
|
|
56
|
+
if (!(node = calls[ev])) continue;
|
|
57
|
+
args = ev == 'all' ? arguments : Array.prototype.slice.call(arguments, 1);
|
|
58
|
+
while (node = node.next) if (callback = node.callback) callback.apply(node.context || this, args);
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
};
|
|
64
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Helpers
|
|
2
|
+
// -------
|
|
3
|
+
|
|
4
|
+
// Shared empty constructor function to aid in prototype-chain creation.
|
|
5
|
+
var ctor = function(){};
|
|
6
|
+
|
|
7
|
+
// Helper function to correctly set up the prototype chain, for subclasses.
|
|
8
|
+
// Similar to `goog.inherits`, but uses a hash of prototype properties and
|
|
9
|
+
// class properties to be extended.
|
|
10
|
+
var inherits = function(parent, protoProps, staticProps) {
|
|
11
|
+
var child;
|
|
12
|
+
|
|
13
|
+
// The constructor function for the new subclass is either defined by you
|
|
14
|
+
// (the "constructor" property in your `extend` definition), or defaulted
|
|
15
|
+
// by us to simply call `super()`.
|
|
16
|
+
if (protoProps && protoProps.hasOwnProperty('constructor')) {
|
|
17
|
+
child = protoProps.constructor;
|
|
18
|
+
} else {
|
|
19
|
+
child = function(){ return parent.apply(this, arguments); };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Inherit class (static) properties from parent.
|
|
23
|
+
_.extend(child, parent);
|
|
24
|
+
|
|
25
|
+
// Set the prototype chain to inherit from `parent`, without calling
|
|
26
|
+
// `parent`'s constructor function.
|
|
27
|
+
ctor.prototype = parent.prototype;
|
|
28
|
+
child.prototype = new ctor();
|
|
29
|
+
|
|
30
|
+
// Add prototype properties (instance properties) to the subclass,
|
|
31
|
+
// if supplied.
|
|
32
|
+
if (protoProps) _.extend(child.prototype, protoProps);
|
|
33
|
+
|
|
34
|
+
// Add static properties to the constructor function, if supplied.
|
|
35
|
+
if (staticProps) _.extend(child, staticProps);
|
|
36
|
+
|
|
37
|
+
// Correctly set child's `prototype.constructor`.
|
|
38
|
+
child.prototype.constructor = child;
|
|
39
|
+
|
|
40
|
+
// Set a convenience property in case the parent's prototype is needed later.
|
|
41
|
+
child.__super__ = parent.prototype;
|
|
42
|
+
|
|
43
|
+
return child;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Helper function to get a URL from a Model or Collection as a property
|
|
47
|
+
// or as a function.
|
|
48
|
+
var getUrl = function(object) {
|
|
49
|
+
if (!(object && object.url)) return null;
|
|
50
|
+
return _.isFunction(object.url) ? object.url() : object.url;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Throw an error when a URL is needed, and none is supplied.
|
|
54
|
+
var urlError = function() {
|
|
55
|
+
throw new Error('A "url" property or function must be specified');
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Wrap an optional error callback with a fallback error event.
|
|
59
|
+
var wrapError = function(onError, originalModel, options) {
|
|
60
|
+
return function(model, resp) {
|
|
61
|
+
var resp = model === originalModel ? resp : model;
|
|
62
|
+
if (onError) {
|
|
63
|
+
onError(model, resp, options);
|
|
64
|
+
} else {
|
|
65
|
+
model.trigger('error', model, resp, options);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Backbone.History
|
|
2
|
+
// ----------------
|
|
3
|
+
|
|
4
|
+
// Handles cross-browser history management, based on URL fragments. If the
|
|
5
|
+
// browser does not support `onhashchange`, falls back to polling.
|
|
6
|
+
Backbone.History = function() {
|
|
7
|
+
this.handlers = [];
|
|
8
|
+
_.bindAll(this, 'checkUrl');
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// Cached regex for cleaning hashes.
|
|
12
|
+
var hashStrip = /^#*/;
|
|
13
|
+
|
|
14
|
+
// Cached regex for detecting MSIE.
|
|
15
|
+
var isExplorer = /msie [\w.]+/;
|
|
16
|
+
|
|
17
|
+
// Has the history handling already been started?
|
|
18
|
+
var historyStarted = false;
|
|
19
|
+
|
|
20
|
+
// Set up all inheritable **Backbone.History** properties and methods.
|
|
21
|
+
_.extend(Backbone.History.prototype, {
|
|
22
|
+
|
|
23
|
+
// The default interval to poll for hash changes, if necessary, is
|
|
24
|
+
// twenty times a second.
|
|
25
|
+
interval: 50,
|
|
26
|
+
|
|
27
|
+
// Get the cross-browser normalized URL fragment, either from the URL,
|
|
28
|
+
// the hash, or the override.
|
|
29
|
+
getFragment : function(fragment, forcePushState) {
|
|
30
|
+
if (fragment == null) {
|
|
31
|
+
if (this._hasPushState || forcePushState) {
|
|
32
|
+
fragment = window.location.pathname;
|
|
33
|
+
var search = window.location.search;
|
|
34
|
+
if (search) fragment += search;
|
|
35
|
+
} else {
|
|
36
|
+
fragment = window.location.hash;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
fragment = decodeURIComponent(fragment.replace(hashStrip, ''));
|
|
40
|
+
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
|
|
41
|
+
return fragment;
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Start the hash change handling, returning `true` if the current URL matches
|
|
45
|
+
// an existing route, and `false` otherwise.
|
|
46
|
+
start : function(options) {
|
|
47
|
+
|
|
48
|
+
// Figure out the initial configuration. Do we need an iframe?
|
|
49
|
+
// Is pushState desired ... is it available?
|
|
50
|
+
if (historyStarted) throw new Error("Backbone.history has already been started");
|
|
51
|
+
this.options = _.extend({}, {root: '/'}, this.options, options);
|
|
52
|
+
this._wantsPushState = !!this.options.pushState;
|
|
53
|
+
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
|
|
54
|
+
var fragment = this.getFragment();
|
|
55
|
+
var docMode = document.documentMode;
|
|
56
|
+
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
|
|
57
|
+
if (oldIE) {
|
|
58
|
+
this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
|
|
59
|
+
this.navigate(fragment);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Depending on whether we're using pushState or hashes, and whether
|
|
63
|
+
// 'onhashchange' is supported, determine how we check the URL state.
|
|
64
|
+
if (this._hasPushState) {
|
|
65
|
+
$(window).bind('popstate', this.checkUrl);
|
|
66
|
+
} else if ('onhashchange' in window && !oldIE) {
|
|
67
|
+
$(window).bind('hashchange', this.checkUrl);
|
|
68
|
+
} else {
|
|
69
|
+
setInterval(this.checkUrl, this.interval);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Determine if we need to change the base url, for a pushState link
|
|
73
|
+
// opened by a non-pushState browser.
|
|
74
|
+
this.fragment = fragment;
|
|
75
|
+
historyStarted = true;
|
|
76
|
+
var loc = window.location;
|
|
77
|
+
var atRoot = loc.pathname == this.options.root;
|
|
78
|
+
if (this._wantsPushState && !this._hasPushState && !atRoot) {
|
|
79
|
+
this.fragment = this.getFragment(null, true);
|
|
80
|
+
window.location.replace(this.options.root + '#' + this.fragment);
|
|
81
|
+
// Return immediately as browser will do redirect to new url
|
|
82
|
+
return true;
|
|
83
|
+
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
|
|
84
|
+
this.fragment = loc.hash.replace(hashStrip, '');
|
|
85
|
+
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!this.options.silent) {
|
|
89
|
+
return this.loadUrl();
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Add a route to be tested when the fragment changes. Routes added later may
|
|
94
|
+
// override previous routes.
|
|
95
|
+
route : function(route, callback) {
|
|
96
|
+
this.handlers.unshift({route : route, callback : callback});
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// Checks the current URL to see if it has changed, and if it has,
|
|
100
|
+
// calls `loadUrl`, normalizing across the hidden iframe.
|
|
101
|
+
checkUrl : function(e) {
|
|
102
|
+
var current = this.getFragment();
|
|
103
|
+
if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
|
|
104
|
+
if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
|
|
105
|
+
if (this.iframe) this.navigate(current);
|
|
106
|
+
this.loadUrl() || this.loadUrl(window.location.hash);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// Attempt to load the current URL fragment. If a route succeeds with a
|
|
110
|
+
// match, returns `true`. If no defined routes matches the fragment,
|
|
111
|
+
// returns `false`.
|
|
112
|
+
loadUrl : function(fragmentOverride) {
|
|
113
|
+
var fragment = this.fragment = this.getFragment(fragmentOverride);
|
|
114
|
+
var matched = _.any(this.handlers, function(handler) {
|
|
115
|
+
if (handler.route.test(fragment)) {
|
|
116
|
+
handler.callback(fragment);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
return matched;
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// Save a fragment into the hash history. You are responsible for properly
|
|
124
|
+
// URL-encoding the fragment in advance. This does not trigger
|
|
125
|
+
// a `hashchange` event.
|
|
126
|
+
navigate : function(fragment, triggerRoute) {
|
|
127
|
+
var frag = (fragment || '').replace(hashStrip, '');
|
|
128
|
+
if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
|
|
129
|
+
if (this._hasPushState) {
|
|
130
|
+
var loc = window.location;
|
|
131
|
+
if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
|
|
132
|
+
this.fragment = frag;
|
|
133
|
+
window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag);
|
|
134
|
+
} else {
|
|
135
|
+
window.location.hash = this.fragment = frag;
|
|
136
|
+
if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
|
|
137
|
+
this.iframe.document.open().close();
|
|
138
|
+
this.iframe.location.hash = frag;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (triggerRoute) this.loadUrl(fragment);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
});
|