gumdrop 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (201) hide show
  1. data/.gitignore +3 -0
  2. data/ChangeLog.md +13 -0
  3. data/Gemfile +6 -2
  4. data/Gemfile.lock +43 -3
  5. data/Rakefile +37 -21
  6. data/Readme.md +60 -158
  7. data/bin/gumdrop +2 -1
  8. data/gumdrop.gemspec +11 -4
  9. data/lib/gumdrop/builder.rb +164 -0
  10. data/lib/gumdrop/cli/external.rb +14 -9
  11. data/lib/gumdrop/cli/internal.rb +54 -22
  12. data/lib/gumdrop/cli.rb +21 -0
  13. data/lib/gumdrop/content.rb +195 -132
  14. data/lib/gumdrop/data.rb +185 -0
  15. data/lib/gumdrop/data_providers/csv.rb +18 -0
  16. data/lib/gumdrop/data_providers/pstore.rb +25 -0
  17. data/lib/gumdrop/data_providers/sqlite.rb +43 -0
  18. data/lib/gumdrop/data_providers/xml.rb +15 -0
  19. data/lib/gumdrop/data_providers/yaml_and_json.rb +18 -0
  20. data/lib/gumdrop/data_providers/yamldb.rb +18 -0
  21. data/lib/gumdrop/data_providers/yamldoc.rb +12 -0
  22. data/lib/gumdrop/generator.rb +118 -94
  23. data/lib/gumdrop/renderer.rb +272 -0
  24. data/lib/gumdrop/server.rb +36 -27
  25. data/lib/gumdrop/site.rb +283 -338
  26. data/lib/gumdrop/support/compressor.rb +52 -0
  27. data/lib/gumdrop/support/sprockets.rb +14 -23
  28. data/lib/gumdrop/support/stitch.rb +25 -14
  29. data/lib/gumdrop/support/yaml_doc_template.rb +29 -0
  30. data/lib/gumdrop/util/configurable.rb +44 -0
  31. data/lib/gumdrop/util/core_ex.rb +84 -0
  32. data/lib/gumdrop/util/eventable.rb +35 -0
  33. data/lib/gumdrop/util/hash_object.rb +70 -0
  34. data/lib/gumdrop/util/loggable.rb +44 -0
  35. data/lib/gumdrop/util/pager.rb +51 -0
  36. data/lib/gumdrop/{support → util}/proxy_handler.rb +7 -6
  37. data/lib/gumdrop/util/scanner.rb +47 -0
  38. data/lib/gumdrop/util/site_access.rb +16 -0
  39. data/lib/gumdrop/util/view_helpers.rb +50 -0
  40. data/lib/gumdrop/util/yaml_doc.rb +51 -0
  41. data/lib/gumdrop/version.rb +2 -2
  42. data/lib/gumdrop.rb +40 -77
  43. data/specs/content_spec.rb +83 -73
  44. data/specs/fixtures/expected/data-access/from-csv.html +4 -0
  45. data/specs/fixtures/expected/data-access/from-json.html +6 -0
  46. data/specs/fixtures/expected/data-access/from-sqlite.html +5 -0
  47. data/specs/fixtures/expected/data-access/from-xml.html +4 -0
  48. data/specs/fixtures/expected/data-access/from-yaml.html +5 -0
  49. data/specs/fixtures/expected/data-access/from-yamldb.html +5 -0
  50. data/specs/fixtures/expected/data-access/from-yamldoc.html +2 -0
  51. data/specs/fixtures/expected/gen-with-block/as-file.html +1 -0
  52. data/specs/fixtures/expected/gen-with-block/as-plain.html +1 -0
  53. data/specs/fixtures/expected/gen-with-block/layout-nested.html +1 -0
  54. data/specs/fixtures/expected/gen-with-block/layout-nil.html +1 -0
  55. data/specs/fixtures/expected/gen-with-block/layout-wrap.html +1 -0
  56. data/specs/fixtures/expected/gen-with-inline-render/0-as-file.html +4 -0
  57. data/specs/fixtures/expected/gen-with-inline-render/0-as-plain.html +4 -0
  58. data/specs/fixtures/expected/gen-with-inline-render/0-layout-nil.html +4 -0
  59. data/specs/fixtures/expected/gen-with-inline-render/0-layout-sub.html +4 -0
  60. data/specs/fixtures/expected/gen-with-inline-render/0-layout-wrap.html +4 -0
  61. data/specs/fixtures/expected/gen-with-inline-render/1-as-file.html +4 -0
  62. data/specs/fixtures/expected/gen-with-inline-render/1-as-plain.html +4 -0
  63. data/specs/fixtures/expected/gen-with-inline-render/1-layout-nil.html +4 -0
  64. data/specs/fixtures/expected/gen-with-inline-render/1-layout-sub.html +4 -0
  65. data/specs/fixtures/expected/gen-with-inline-render/1-layout-wrap.html +4 -0
  66. data/specs/fixtures/expected/goodbye.html +1 -0
  67. data/specs/fixtures/expected/hello.html +1 -0
  68. data/specs/fixtures/expected/image.png +0 -0
  69. data/specs/fixtures/expected/index.html +47 -0
  70. data/specs/fixtures/expected/js/sprockets-app.js +7 -0
  71. data/specs/fixtures/expected/js/stitch-app.js +65 -0
  72. data/specs/fixtures/expected/js/stitch-app.min.js +5 -0
  73. data/specs/fixtures/expected/js/stitch-app.min.src.js +65 -0
  74. data/specs/fixtures/expected/js/straight.js +8 -0
  75. data/specs/fixtures/expected/js/test-coffee.js +5 -0
  76. data/specs/fixtures/expected/pages/docs/force-abs.html +2 -0
  77. data/specs/fixtures/expected/pages/docs/index.html +2 -0
  78. data/specs/fixtures/expected/partials/hoisted-data.html +3 -0
  79. data/specs/fixtures/expected/partials/nested.html +7 -0
  80. data/specs/fixtures/expected/partials/params.html +1 -0
  81. data/specs/fixtures/expected/partials/with-layouts.html +1 -0
  82. data/specs/fixtures/expected/robots.txt +1 -0
  83. data/specs/fixtures/expected/test-multi-procs.html +4 -0
  84. data/specs/fixtures/expected/test-nested-layout.html +1 -0
  85. data/specs/fixtures/expected/test-no-layout.html +2 -0
  86. data/specs/fixtures/expected/test-yamldoc.html +1 -0
  87. data/specs/fixtures/expected/test.html +2 -0
  88. data/{templates/backbone → specs/fixtures/source}/Gemfile +8 -4
  89. data/specs/fixtures/source/Gemfile.lock +84 -0
  90. data/specs/fixtures/source/Gumdrop +60 -9
  91. data/specs/fixtures/source/data/csv_data.csv +2 -0
  92. data/specs/fixtures/source/data/json_data.json +13 -0
  93. data/specs/fixtures/source/data/sql_data.sqlite +0 -0
  94. data/specs/fixtures/source/data/xml_data.xml +4 -0
  95. data/specs/fixtures/source/data/yaml_db.yamldb +9 -0
  96. data/specs/fixtures/source/data/yaml_items.yaml +2 -0
  97. data/specs/fixtures/source/data/yamldoc.yamldoc +4 -0
  98. data/specs/fixtures/source/data-access/from-csv.html.erb +4 -0
  99. data/specs/fixtures/source/data-access/from-json.html.erb +6 -0
  100. data/specs/fixtures/source/data-access/from-sqlite.html.erb +5 -0
  101. data/specs/fixtures/source/data-access/from-xml.html.erb +5 -0
  102. data/specs/fixtures/source/data-access/from-yaml.html.erb +5 -0
  103. data/specs/fixtures/source/data-access/from-yamldb.html.erb +5 -0
  104. data/specs/fixtures/source/data-access/from-yamldoc.html.erb +2 -0
  105. data/specs/fixtures/source/gen-with-inline-render.generator +7 -0
  106. data/specs/fixtures/source/goodbye.html.erb +1 -0
  107. data/specs/fixtures/source/hello.html +1 -0
  108. data/specs/fixtures/source/image.png +0 -0
  109. data/specs/fixtures/source/index.html.erb +6 -0
  110. data/specs/fixtures/source/js/sprockets/app.js +2 -0
  111. data/specs/fixtures/source/js/sprockets/view.js.coffee +1 -0
  112. data/specs/fixtures/source/js/stitch/app.js +1 -0
  113. data/specs/fixtures/source/js/stitch/view.js.coffee +1 -0
  114. data/specs/fixtures/source/js/straight.js +8 -0
  115. data/specs/fixtures/source/js/test-coffee.js.coffee +1 -0
  116. data/specs/fixtures/source/layouts/site.layout.erb +1 -0
  117. data/specs/fixtures/source/layouts/sub.layout.erb +1 -0
  118. data/specs/fixtures/source/layouts/wrap.layout.erb +1 -0
  119. data/specs/fixtures/source/pages/_users-nested.html.erb +7 -0
  120. data/specs/fixtures/source/pages/docs/force-abs.html.erb +2 -0
  121. data/specs/fixtures/source/pages/docs/index.html.erb +2 -0
  122. data/specs/fixtures/source/partials/_post_entry.html.erb +4 -0
  123. data/specs/fixtures/source/partials/_provides_name.html.erb +1 -0
  124. data/specs/fixtures/source/partials/_user-params.html.erb +1 -0
  125. data/specs/fixtures/source/partials/_user.html.erb +1 -0
  126. data/specs/fixtures/source/partials/_user_sets_layout.html.erb +1 -0
  127. data/specs/fixtures/source/partials/hoisted-data.html.erb +6 -0
  128. data/specs/fixtures/source/partials/nested.html.erb +1 -0
  129. data/specs/fixtures/source/partials/params.html.erb +1 -0
  130. data/specs/fixtures/source/partials/with-layouts.html.erb +1 -0
  131. data/specs/fixtures/source/test-multi-procs.html.markdown.erb +3 -0
  132. data/specs/fixtures/source/test-nested-layout.html.erb +1 -0
  133. data/specs/fixtures/source/test-no-layout.html.erb +2 -0
  134. data/specs/fixtures/source/test-yamldoc.html.erb.yamldoc +4 -0
  135. data/specs/fixtures/source/test.html.erb +2 -0
  136. data/specs/hash_object_spec.rb +53 -8
  137. data/specs/render_spec.rb +10 -0
  138. data/specs/site_spec.rb +79 -0
  139. data/specs/spec_helper.rb +59 -0
  140. data/templates/blank/Gemfile +0 -4
  141. data/templates/blank/Gumdrop +180 -113
  142. data/templates/default/Gemfile +3 -8
  143. data/templates/default/Gumdrop +166 -95
  144. data/templates/default/ReadMe.md +14 -0
  145. data/templates/default/config.ru +1 -0
  146. data/templates/default/data/news/20120703.yamldoc +14 -0
  147. data/templates/default/data/news/20120704.yamldoc +8 -0
  148. data/templates/{backbone/source/feed.xml.builder.txt → default/source/feed.xml.builder} +4 -4
  149. data/templates/default/source/index.html.erb +10 -1
  150. data/templates/default/source/theme/layout/_sidebar.html +1 -0
  151. data/templates/{backbone/source/theme/templates/site.template.slim → default/source/theme/layout/site.layout.slim} +3 -3
  152. metadata +212 -63
  153. data/lib/gumdrop/context.rb +0 -135
  154. data/lib/gumdrop/data_manager.rb +0 -214
  155. data/lib/gumdrop/support/base_packager.rb +0 -60
  156. data/lib/gumdrop/support/callbacks.rb +0 -30
  157. data/lib/gumdrop/support/hash_object.rb +0 -22
  158. data/lib/gumdrop/view_helpers.rb +0 -25
  159. data/notes.md +0 -347
  160. data/specs/deferred_loader_spec.rb +0 -31
  161. data/specs/fixtures/expected/posts/post1.html +0 -1
  162. data/specs/fixtures/expected/posts/post1.js +0 -14
  163. data/specs/fixtures/expected/posts/post2.html +0 -5
  164. data/specs/fixtures/expected/sub/sub/sub/test.html +0 -5
  165. data/specs/fixtures/expected/sub/sub/sub/test2.html +0 -5
  166. data/specs/fixtures/expected/test.js +0 -14
  167. data/specs/fixtures/source/posts/post1.html.slim +0 -6
  168. data/specs/fixtures/source/posts/post1.js.coffee +0 -3
  169. data/specs/fixtures/source/posts/post2.html.erb +0 -5
  170. data/specs/fixtures/source/sub/sub/sub/test.html +0 -5
  171. data/specs/fixtures/source/sub/sub/sub/test2.html.erb +0 -5
  172. data/specs/fixtures/source/test.js.coffee +0 -4
  173. data/specs/fixtures/source/test.js.erb.coffee +0 -3
  174. data/templates/backbone/Gumdrop +0 -123
  175. data/templates/backbone/Rakefile +0 -38
  176. data/templates/backbone/app/app.js.coffee +0 -49
  177. data/templates/backbone/app/init.js.coffee +0 -10
  178. data/templates/backbone/app/models/.gitkeep +0 -0
  179. data/templates/backbone/app/utils/index.js.coffee +0 -27
  180. data/templates/backbone/app/views/home.js.coffee +0 -17
  181. data/templates/backbone/app/views/styles/home.scss +0 -8
  182. data/templates/backbone/app/views/templates/home.mustache +0 -2
  183. data/templates/backbone/config.ru +0 -20
  184. data/templates/backbone/lib/all.js.coffee +0 -18
  185. data/templates/backbone/lib/backbone.js +0 -1158
  186. data/templates/backbone/lib/hogan.js +0 -509
  187. data/templates/backbone/lib/jquery.js +0 -9266
  188. data/templates/backbone/lib/underscore.js +0 -981
  189. data/templates/backbone/powrc +0 -2
  190. data/templates/backbone/source/default.htaccess.erb +0 -27
  191. data/templates/backbone/source/favicon.ico +0 -0
  192. data/templates/backbone/source/index.html.erb +0 -2
  193. data/templates/backbone/source/theme/screen.css.sass +0 -9
  194. data/templates/backbone/source/theme/scripts/app.js.coffee +0 -4
  195. data/templates/backbone/source/theme/styles/_tools.scss +0 -434
  196. data/templates/backbone/source/theme/templates/app.template.slim +0 -14
  197. data/templates/default/Rakefile +0 -38
  198. data/templates/default/powrc +0 -2
  199. data/templates/default/source/default.htaccess.erb +0 -27
  200. data/templates/default/source/feed.xml.builder.txt +0 -23
  201. data/templates/default/source/theme/templates/site.template.slim +0 -33
@@ -1,1158 +0,0 @@
1
- // Backbone.js 0.5.3
2
- // (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
3
- // Backbone may be freely distributed under the MIT license.
4
- // For all details and documentation:
5
- // http://documentcloud.github.com/backbone
6
-
7
- (function(){
8
-
9
- // Initial Setup
10
- // -------------
11
-
12
- // Save a reference to the global object.
13
- var root = this;
14
-
15
- // Save the previous value of the `Backbone` variable.
16
- var previousBackbone = root.Backbone;
17
-
18
- // The top-level namespace. All public Backbone classes and modules will
19
- // be attached to this. Exported for both CommonJS and the browser.
20
- var Backbone;
21
- if (typeof exports !== 'undefined') {
22
- Backbone = exports;
23
- } else {
24
- Backbone = root.Backbone = {};
25
- }
26
-
27
- // Current version of the library. Keep in sync with `package.json`.
28
- Backbone.VERSION = '0.5.3';
29
-
30
- // Require Underscore, if we're on the server, and it's not already present.
31
- var _ = root._;
32
- if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
33
-
34
- // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
35
- var $ = root.jQuery || root.Zepto;
36
-
37
- // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
38
- // to its previous owner. Returns a reference to this Backbone object.
39
- Backbone.noConflict = function() {
40
- root.Backbone = previousBackbone;
41
- return this;
42
- };
43
-
44
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will
45
- // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
46
- // `X-Http-Method-Override` header.
47
- Backbone.emulateHTTP = false;
48
-
49
- // Turn on `emulateJSON` to support legacy servers that can't deal with direct
50
- // `application/json` requests ... will encode the body as
51
- // `application/x-www-form-urlencoded` instead and will send the model in a
52
- // form param named `model`.
53
- Backbone.emulateJSON = false;
54
-
55
- // Backbone.Events
56
- // -----------------
57
-
58
- // A module that can be mixed in to *any object* in order to provide it with
59
- // custom events. You may `bind` or `unbind` a callback function to an event;
60
- // `trigger`-ing an event fires all callbacks in succession.
61
- //
62
- // var object = {};
63
- // _.extend(object, Backbone.Events);
64
- // object.bind('expand', function(){ alert('expanded'); });
65
- // object.trigger('expand');
66
- //
67
- Backbone.Events = {
68
-
69
- // Bind an event, specified by a string name, `ev`, to a `callback` function.
70
- // Passing `"all"` will bind the callback to all events fired.
71
- bind : function(ev, callback, context) {
72
- var calls = this._callbacks || (this._callbacks = {});
73
- var list = calls[ev] || (calls[ev] = []);
74
- list.push([callback, context]);
75
- return this;
76
- },
77
-
78
- // Remove one or many callbacks. If `callback` is null, removes all
79
- // callbacks for the event. If `ev` is null, removes all bound callbacks
80
- // for all events.
81
- unbind : function(ev, callback) {
82
- var calls;
83
- if (!ev) {
84
- this._callbacks = {};
85
- } else if (calls = this._callbacks) {
86
- if (!callback) {
87
- calls[ev] = [];
88
- } else {
89
- var list = calls[ev];
90
- if (!list) return this;
91
- for (var i = 0, l = list.length; i < l; i++) {
92
- if (list[i] && callback === list[i][0]) {
93
- list[i] = null;
94
- break;
95
- }
96
- }
97
- }
98
- }
99
- return this;
100
- },
101
-
102
- // Trigger an event, firing all bound callbacks. Callbacks are passed the
103
- // same arguments as `trigger` is, apart from the event name.
104
- // Listening for `"all"` passes the true event name as the first argument.
105
- trigger : function(eventName) {
106
- var list, calls, ev, callback, args;
107
- var both = 2;
108
- if (!(calls = this._callbacks)) return this;
109
- while (both--) {
110
- ev = both ? eventName : 'all';
111
- if (list = calls[ev]) {
112
- for (var i = 0, l = list.length; i < l; i++) {
113
- if (!(callback = list[i])) {
114
- list.splice(i, 1); i--; l--;
115
- } else {
116
- args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
117
- callback[0].apply(callback[1] || this, args);
118
- }
119
- }
120
- }
121
- }
122
- return this;
123
- }
124
-
125
- };
126
-
127
- // Backbone.Model
128
- // --------------
129
-
130
- // Create a new model, with defined attributes. A client id (`cid`)
131
- // is automatically generated and assigned for you.
132
- Backbone.Model = function(attributes, options) {
133
- var defaults;
134
- attributes || (attributes = {});
135
- if (defaults = this.defaults) {
136
- if (_.isFunction(defaults)) defaults = defaults.call(this);
137
- attributes = _.extend({}, defaults, attributes);
138
- }
139
- this.attributes = {};
140
- this._escapedAttributes = {};
141
- this.cid = _.uniqueId('c');
142
- this.set(attributes, {silent : true});
143
- this._changed = false;
144
- this._previousAttributes = _.clone(this.attributes);
145
- if (options && options.collection) this.collection = options.collection;
146
- this.initialize(attributes, options);
147
- };
148
-
149
- // Attach all inheritable methods to the Model prototype.
150
- _.extend(Backbone.Model.prototype, Backbone.Events, {
151
-
152
- // A snapshot of the model's previous attributes, taken immediately
153
- // after the last `"change"` event was fired.
154
- _previousAttributes : null,
155
-
156
- // Has the item been changed since the last `"change"` event?
157
- _changed : false,
158
-
159
- // The default name for the JSON `id` attribute is `"id"`. MongoDB and
160
- // CouchDB users may want to set this to `"_id"`.
161
- idAttribute : 'id',
162
-
163
- // Initialize is an empty function by default. Override it with your own
164
- // initialization logic.
165
- initialize : function(){},
166
-
167
- // Return a copy of the model's `attributes` object.
168
- toJSON : function() {
169
- return _.clone(this.attributes);
170
- },
171
-
172
- // Get the value of an attribute.
173
- get : function(attr) {
174
- return this.attributes[attr];
175
- },
176
-
177
- // Get the HTML-escaped value of an attribute.
178
- escape : function(attr) {
179
- var html;
180
- if (html = this._escapedAttributes[attr]) return html;
181
- var val = this.attributes[attr];
182
- return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
183
- },
184
-
185
- // Returns `true` if the attribute contains a value that is not null
186
- // or undefined.
187
- has : function(attr) {
188
- return this.attributes[attr] != null;
189
- },
190
-
191
- // Set a hash of model attributes on the object, firing `"change"` unless you
192
- // choose to silence it.
193
- set : function(attrs, options) {
194
-
195
- // Extract attributes and options.
196
- options || (options = {});
197
- if (!attrs) return this;
198
- if (attrs.attributes) attrs = attrs.attributes;
199
- var now = this.attributes, escaped = this._escapedAttributes;
200
-
201
- // Run validation.
202
- if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
203
-
204
- // Check for changes of `id`.
205
- if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
206
-
207
- // We're about to start triggering change events.
208
- var alreadyChanging = this._changing;
209
- this._changing = true;
210
-
211
- // Update attributes.
212
- for (var attr in attrs) {
213
- var val = attrs[attr];
214
- if (!_.isEqual(now[attr], val)) {
215
- now[attr] = val;
216
- delete escaped[attr];
217
- this._changed = true;
218
- if (!options.silent) this.trigger('change:' + attr, this, val, options);
219
- }
220
- }
221
-
222
- // Fire the `"change"` event, if the model has been changed.
223
- if (!alreadyChanging && !options.silent && this._changed) this.change(options);
224
- this._changing = false;
225
- return this;
226
- },
227
-
228
- // Remove an attribute from the model, firing `"change"` unless you choose
229
- // to silence it. `unset` is a noop if the attribute doesn't exist.
230
- unset : function(attr, options) {
231
- if (!(attr in this.attributes)) return this;
232
- options || (options = {});
233
- var value = this.attributes[attr];
234
-
235
- // Run validation.
236
- var validObj = {};
237
- validObj[attr] = void 0;
238
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
239
-
240
- // Remove the attribute.
241
- delete this.attributes[attr];
242
- delete this._escapedAttributes[attr];
243
- if (attr == this.idAttribute) delete this.id;
244
- this._changed = true;
245
- if (!options.silent) {
246
- this.trigger('change:' + attr, this, void 0, options);
247
- this.change(options);
248
- }
249
- return this;
250
- },
251
-
252
- // Clear all attributes on the model, firing `"change"` unless you choose
253
- // to silence it.
254
- clear : function(options) {
255
- options || (options = {});
256
- var attr;
257
- var old = this.attributes;
258
-
259
- // Run validation.
260
- var validObj = {};
261
- for (attr in old) validObj[attr] = void 0;
262
- if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
263
-
264
- this.attributes = {};
265
- this._escapedAttributes = {};
266
- this._changed = true;
267
- if (!options.silent) {
268
- for (attr in old) {
269
- this.trigger('change:' + attr, this, void 0, options);
270
- }
271
- this.change(options);
272
- }
273
- return this;
274
- },
275
-
276
- // Fetch the model from the server. If the server's representation of the
277
- // model differs from its current attributes, they will be overriden,
278
- // triggering a `"change"` event.
279
- fetch : function(options) {
280
- options || (options = {});
281
- var model = this;
282
- var success = options.success;
283
- options.success = function(resp, status, xhr) {
284
- if (!model.set(model.parse(resp, xhr), options)) return false;
285
- if (success) success(model, resp);
286
- };
287
- options.error = wrapError(options.error, model, options);
288
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
289
- },
290
-
291
- // Set a hash of model attributes, and sync the model to the server.
292
- // If the server returns an attributes hash that differs, the model's
293
- // state will be `set` again.
294
- save : function(attrs, options) {
295
- options || (options = {});
296
- if (attrs && !this.set(attrs, options)) return false;
297
- var model = this;
298
- var success = options.success;
299
- options.success = function(resp, status, xhr) {
300
- if (!model.set(model.parse(resp, xhr), options)) return false;
301
- if (success) success(model, resp, xhr);
302
- };
303
- options.error = wrapError(options.error, model, options);
304
- var method = this.isNew() ? 'create' : 'update';
305
- return (this.sync || Backbone.sync).call(this, method, this, options);
306
- },
307
-
308
- // Destroy this model on the server if it was already persisted. Upon success, the model is removed
309
- // from its collection, if it has one.
310
- destroy : function(options) {
311
- options || (options = {});
312
- if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
313
- var model = this;
314
- var success = options.success;
315
- options.success = function(resp) {
316
- model.trigger('destroy', model, model.collection, options);
317
- if (success) success(model, resp);
318
- };
319
- options.error = wrapError(options.error, model, options);
320
- return (this.sync || Backbone.sync).call(this, 'delete', this, options);
321
- },
322
-
323
- // Default URL for the model's representation on the server -- if you're
324
- // using Backbone's restful methods, override this to change the endpoint
325
- // that will be called.
326
- url : function() {
327
- var base = getUrl(this.collection) || this.urlRoot || urlError();
328
- if (this.isNew()) return base;
329
- return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
330
- },
331
-
332
- // **parse** converts a response into the hash of attributes to be `set` on
333
- // the model. The default implementation is just to pass the response along.
334
- parse : function(resp, xhr) {
335
- return resp;
336
- },
337
-
338
- // Create a new model with identical attributes to this one.
339
- clone : function() {
340
- return new this.constructor(this);
341
- },
342
-
343
- // A model is new if it has never been saved to the server, and lacks an id.
344
- isNew : function() {
345
- return this.id == null;
346
- },
347
-
348
- // Call this method to manually fire a `change` event for this model.
349
- // Calling this will cause all objects observing the model to update.
350
- change : function(options) {
351
- this.trigger('change', this, options);
352
- this._previousAttributes = _.clone(this.attributes);
353
- this._changed = false;
354
- },
355
-
356
- // Determine if the model has changed since the last `"change"` event.
357
- // If you specify an attribute name, determine if that attribute has changed.
358
- hasChanged : function(attr) {
359
- if (attr) return this._previousAttributes[attr] != this.attributes[attr];
360
- return this._changed;
361
- },
362
-
363
- // Return an object containing all the attributes that have changed, or false
364
- // if there are no changed attributes. Useful for determining what parts of a
365
- // view need to be updated and/or what attributes need to be persisted to
366
- // the server.
367
- changedAttributes : function(now) {
368
- now || (now = this.attributes);
369
- var old = this._previousAttributes;
370
- var changed = false;
371
- for (var attr in now) {
372
- if (!_.isEqual(old[attr], now[attr])) {
373
- changed = changed || {};
374
- changed[attr] = now[attr];
375
- }
376
- }
377
- return changed;
378
- },
379
-
380
- // Get the previous value of an attribute, recorded at the time the last
381
- // `"change"` event was fired.
382
- previous : function(attr) {
383
- if (!attr || !this._previousAttributes) return null;
384
- return this._previousAttributes[attr];
385
- },
386
-
387
- // Get all of the attributes of the model at the time of the previous
388
- // `"change"` event.
389
- previousAttributes : function() {
390
- return _.clone(this._previousAttributes);
391
- },
392
-
393
- // Run validation against a set of incoming attributes, returning `true`
394
- // if all is well. If a specific `error` callback has been passed,
395
- // call that instead of firing the general `"error"` event.
396
- _performValidation : function(attrs, options) {
397
- var error = this.validate(attrs);
398
- if (error) {
399
- if (options.error) {
400
- options.error(this, error, options);
401
- } else {
402
- this.trigger('error', this, error, options);
403
- }
404
- return false;
405
- }
406
- return true;
407
- }
408
-
409
- });
410
-
411
- // Backbone.Collection
412
- // -------------------
413
-
414
- // Provides a standard collection class for our sets of models, ordered
415
- // or unordered. If a `comparator` is specified, the Collection will maintain
416
- // its models in sort order, as they're added and removed.
417
- Backbone.Collection = function(models, options) {
418
- options || (options = {});
419
- if (options.comparator) this.comparator = options.comparator;
420
- _.bindAll(this, '_onModelEvent', '_removeReference');
421
- this._reset();
422
- if (models) this.reset(models, {silent: true});
423
- this.initialize.apply(this, arguments);
424
- };
425
-
426
- // Define the Collection's inheritable methods.
427
- _.extend(Backbone.Collection.prototype, Backbone.Events, {
428
-
429
- // The default model for a collection is just a **Backbone.Model**.
430
- // This should be overridden in most cases.
431
- model : Backbone.Model,
432
-
433
- // Initialize is an empty function by default. Override it with your own
434
- // initialization logic.
435
- initialize : function(){},
436
-
437
- // The JSON representation of a Collection is an array of the
438
- // models' attributes.
439
- toJSON : function() {
440
- return this.map(function(model){ return model.toJSON(); });
441
- },
442
-
443
- // Add a model, or list of models to the set. Pass **silent** to avoid
444
- // firing the `added` event for every new model.
445
- add : function(models, options) {
446
- if (_.isArray(models)) {
447
- for (var i = 0, l = models.length; i < l; i++) {
448
- this._add(models[i], options);
449
- }
450
- } else {
451
- this._add(models, options);
452
- }
453
- return this;
454
- },
455
-
456
- // Remove a model, or a list of models from the set. Pass silent to avoid
457
- // firing the `removed` event for every model removed.
458
- remove : function(models, options) {
459
- if (_.isArray(models)) {
460
- for (var i = 0, l = models.length; i < l; i++) {
461
- this._remove(models[i], options);
462
- }
463
- } else {
464
- this._remove(models, options);
465
- }
466
- return this;
467
- },
468
-
469
- // Get a model from the set by id.
470
- get : function(id) {
471
- if (id == null) return null;
472
- return this._byId[id.id != null ? id.id : id];
473
- },
474
-
475
- // Get a model from the set by client id.
476
- getByCid : function(cid) {
477
- return cid && this._byCid[cid.cid || cid];
478
- },
479
-
480
- // Get the model at the given index.
481
- at: function(index) {
482
- return this.models[index];
483
- },
484
-
485
- // Force the collection to re-sort itself. You don't need to call this under normal
486
- // circumstances, as the set will maintain sort order as each item is added.
487
- sort : function(options) {
488
- options || (options = {});
489
- if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
490
- this.models = this.sortBy(this.comparator);
491
- if (!options.silent) this.trigger('reset', this, options);
492
- return this;
493
- },
494
-
495
- // Pluck an attribute from each model in the collection.
496
- pluck : function(attr) {
497
- return _.map(this.models, function(model){ return model.get(attr); });
498
- },
499
-
500
- // When you have more items than you want to add or remove individually,
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) {
504
- models || (models = []);
505
- options || (options = {});
506
- this.each(this._removeReference);
507
- this._reset();
508
- this.add(models, {silent: true});
509
- if (!options.silent) this.trigger('reset', this, options);
510
- return this;
511
- },
512
-
513
- // Fetch the default set of models for this collection, resetting the
514
- // collection when they arrive. If `add: true` is passed, appends the
515
- // models to the collection instead of resetting.
516
- fetch : function(options) {
517
- options || (options = {});
518
- var collection = this;
519
- var success = options.success;
520
- options.success = function(resp, status, xhr) {
521
- collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
522
- if (success) success(collection, resp);
523
- };
524
- options.error = wrapError(options.error, collection, options);
525
- return (this.sync || Backbone.sync).call(this, 'read', this, options);
526
- },
527
-
528
- // Create a new instance of a model in this collection. After the model
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.
531
- create : function(model, options) {
532
- var coll = this;
533
- options || (options = {});
534
- model = this._prepareModel(model, options);
535
- if (!model) return false;
536
- var success = options.success;
537
- options.success = function(nextModel, resp, xhr) {
538
- coll.add(nextModel, options);
539
- if (success) success(nextModel, resp, xhr);
540
- };
541
- model.save(null, options);
542
- return model;
543
- },
544
-
545
- // **parse** converts a response into a list of models to be added to the
546
- // collection. The default implementation is just to pass it through.
547
- parse : function(resp, xhr) {
548
- return resp;
549
- },
550
-
551
- // Proxy to _'s chain. Can't be proxied the same way the rest of the
552
- // underscore methods are proxied because it relies on the underscore
553
- // constructor.
554
- chain: function () {
555
- return _(this.models).chain();
556
- },
557
-
558
- // Reset all internal state. Called when the collection is reset.
559
- _reset : function(options) {
560
- this.length = 0;
561
- this.models = [];
562
- this._byId = {};
563
- this._byCid = {};
564
- },
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
-
578
- // Internal implementation of adding a single model to the set, updating
579
- // hash indexes for `id` and `cid` lookups.
580
- // Returns the model, or 'false' if validation on a new model fails.
581
- _add : function(model, options) {
582
- options || (options = {});
583
- model = this._prepareModel(model, options);
584
- if (!model) return false;
585
- var already = this.getByCid(model);
586
- if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
587
- this._byId[model.id] = model;
588
- this._byCid[model.cid] = model;
589
- var index = options.at != null ? options.at :
590
- this.comparator ? this.sortedIndex(model, this.comparator) :
591
- this.length;
592
- this.models.splice(index, 0, model);
593
- model.bind('all', this._onModelEvent);
594
- this.length++;
595
- if (!options.silent) model.trigger('add', model, this, options);
596
- return model;
597
- },
598
-
599
- // Internal implementation of removing a single model from the set, updating
600
- // hash indexes for `id` and `cid` lookups.
601
- _remove : function(model, options) {
602
- options || (options = {});
603
- model = this.getByCid(model) || this.get(model);
604
- if (!model) return null;
605
- delete this._byId[model.id];
606
- delete this._byCid[model.cid];
607
- this.models.splice(this.indexOf(model), 1);
608
- this.length--;
609
- if (!options.silent) model.trigger('remove', model, this, options);
610
- this._removeReference(model);
611
- return model;
612
- },
613
-
614
- // Internal method to remove a model's ties to a collection.
615
- _removeReference : function(model) {
616
- if (this == model.collection) {
617
- delete model.collection;
618
- }
619
- model.unbind('all', this._onModelEvent);
620
- },
621
-
622
- // Internal method called every time a model in the set fires an event.
623
- // Sets need to update their indexes when models change ids. All other
624
- // events simply proxy through. "add" and "remove" events that originate
625
- // in other collections are ignored.
626
- _onModelEvent : function(ev, model, collection, options) {
627
- if ((ev == 'add' || ev == 'remove') && collection != this) return;
628
- if (ev == 'destroy') {
629
- this._remove(model, options);
630
- }
631
- if (model && ev === 'change:' + model.idAttribute) {
632
- delete this._byId[model.previous(model.idAttribute)];
633
- this._byId[model.id] = model;
634
- }
635
- this.trigger.apply(this, arguments);
636
- }
637
-
638
- });
639
-
640
- // Underscore methods that we want to implement on the Collection.
641
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
642
- 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
643
- 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
644
- 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy'];
645
-
646
- // Mix in each Underscore method as a proxy to `Collection#models`.
647
- _.each(methods, function(method) {
648
- Backbone.Collection.prototype[method] = function() {
649
- return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
650
- };
651
- });
652
-
653
- // Backbone.Router
654
- // -------------------
655
-
656
- // Routers map faux-URLs to actions, and fire events when routes are
657
- // matched. Creating a new one sets its `routes` hash, if not set statically.
658
- Backbone.Router = function(options) {
659
- options || (options = {});
660
- if (options.routes) this.routes = options.routes;
661
- this._bindRoutes();
662
- this.initialize.apply(this, arguments);
663
- };
664
-
665
- // Cached regular expressions for matching named param parts and splatted
666
- // parts of route strings.
667
- var namedParam = /:([\w\d]+)/g;
668
- var splatParam = /\*([\w\d]+)/g;
669
- var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
670
-
671
- // Set up all inheritable **Backbone.Router** properties and methods.
672
- _.extend(Backbone.Router.prototype, Backbone.Events, {
673
-
674
- // Initialize is an empty function by default. Override it with your own
675
- // initialization logic.
676
- initialize : function(){},
677
-
678
- // Manually bind a single named route to a callback. For example:
679
- //
680
- // this.route('search/:query/p:num', 'search', function(query, num) {
681
- // ...
682
- // });
683
- //
684
- route : function(route, name, callback) {
685
- Backbone.history || (Backbone.history = new Backbone.History);
686
- if (!_.isRegExp(route)) route = this._routeToRegExp(route);
687
- Backbone.history.route(route, _.bind(function(fragment) {
688
- var args = this._extractParameters(route, fragment);
689
- callback.apply(this, args);
690
- this.trigger.apply(this, ['route:' + name].concat(args));
691
- }, this));
692
- },
693
-
694
- // Simple proxy to `Backbone.history` to save a fragment into the history.
695
- navigate : function(fragment, triggerRoute) {
696
- Backbone.history.navigate(fragment, triggerRoute);
697
- },
698
-
699
- // Bind all defined routes to `Backbone.history`. We have to reverse the
700
- // order of the routes here to support behavior where the most general
701
- // routes can be defined at the bottom of the route map.
702
- _bindRoutes : function() {
703
- if (!this.routes) return;
704
- var routes = [];
705
- for (var route in this.routes) {
706
- routes.unshift([route, this.routes[route]]);
707
- }
708
- for (var i = 0, l = routes.length; i < l; i++) {
709
- this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
710
- }
711
- },
712
-
713
- // Convert a route string into a regular expression, suitable for matching
714
- // against the current location hash.
715
- _routeToRegExp : function(route) {
716
- route = route.replace(escapeRegExp, "\\$&")
717
- .replace(namedParam, "([^\/]*)")
718
- .replace(splatParam, "(.*?)");
719
- return new RegExp('^' + route + '$');
720
- },
721
-
722
- // Given a route, and a URL fragment that it matches, return the array of
723
- // extracted parameters.
724
- _extractParameters : function(route, fragment) {
725
- return route.exec(fragment).slice(1);
726
- }
727
-
728
- });
729
-
730
- // Backbone.History
731
- // ----------------
732
-
733
- // Handles cross-browser history management, based on URL fragments. If the
734
- // browser does not support `onhashchange`, falls back to polling.
735
- Backbone.History = function() {
736
- this.handlers = [];
737
- _.bindAll(this, 'checkUrl');
738
- };
739
-
740
- // Cached regex for cleaning hashes.
741
- var hashStrip = /^#*/;
742
-
743
- // Cached regex for detecting MSIE.
744
- var isExplorer = /msie [\w.]+/;
745
-
746
- // Has the history handling already been started?
747
- var historyStarted = false;
748
-
749
- // Set up all inheritable **Backbone.History** properties and methods.
750
- _.extend(Backbone.History.prototype, {
751
-
752
- // The default interval to poll for hash changes, if necessary, is
753
- // twenty times a second.
754
- interval: 50,
755
-
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 decodeURIComponent(fragment.replace(hashStrip, ''));
770
- },
771
-
772
- // Start the hash change handling, returning `true` if the current URL matches
773
- // an existing route, and `false` otherwise.
774
- start : function(options) {
775
-
776
- // Figure out the initial configuration. Do we need an iframe?
777
- // Is pushState desired ... is it available?
778
- if (historyStarted) throw new Error("Backbone.history has already been started");
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));
785
- if (oldIE) {
786
- this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
787
- this.navigate(fragment);
788
- }
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) {
795
- $(window).bind('hashchange', this.checkUrl);
796
- } else {
797
- setInterval(this.checkUrl, this.interval);
798
- }
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;
803
- historyStarted = true;
804
- var loc = window.location;
805
- var atRoot = loc.pathname == this.options.root;
806
- if (this._wantsPushState && !this._hasPushState && !atRoot) {
807
- this.fragment = this.getFragment(null, true);
808
- window.location.replace(this.options.root + '#' + this.fragment);
809
- // Return immediately as browser will do redirect to new url
810
- return true;
811
- } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
812
- this.fragment = loc.hash.replace(hashStrip, '');
813
- window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
814
- }
815
-
816
- if (!this.options.silent) {
817
- return this.loadUrl();
818
- }
819
- },
820
-
821
- // Add a route to be tested when the fragment changes. Routes added later may
822
- // override previous routes.
823
- route : function(route, callback) {
824
- this.handlers.unshift({route : route, callback : callback});
825
- },
826
-
827
- // Checks the current URL to see if it has changed, and if it has,
828
- // calls `loadUrl`, normalizing across the hidden iframe.
829
- checkUrl : function(e) {
830
- var current = this.getFragment();
831
- if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
832
- if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
833
- if (this.iframe) this.navigate(current);
834
- this.loadUrl() || this.loadUrl(window.location.hash);
835
- },
836
-
837
- // Attempt to load the current URL fragment. If a route succeeds with a
838
- // match, returns `true`. If no defined routes matches the fragment,
839
- // returns `false`.
840
- loadUrl : function(fragmentOverride) {
841
- var fragment = this.fragment = this.getFragment(fragmentOverride);
842
- var matched = _.any(this.handlers, function(handler) {
843
- if (handler.route.test(fragment)) {
844
- handler.callback(fragment);
845
- return true;
846
- }
847
- });
848
- return matched;
849
- },
850
-
851
- // Save a fragment into the hash history. You are responsible for properly
852
- // URL-encoding the fragment in advance. This does not trigger
853
- // a `hashchange` event.
854
- navigate : function(fragment, triggerRoute) {
855
- var frag = (fragment || '').replace(hashStrip, '');
856
- if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
857
- if (this._hasPushState) {
858
- var loc = window.location;
859
- if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
860
- this.fragment = frag;
861
- window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag);
862
- } else {
863
- window.location.hash = this.fragment = frag;
864
- if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
865
- this.iframe.document.open().close();
866
- this.iframe.location.hash = frag;
867
- }
868
- }
869
- if (triggerRoute) this.loadUrl(fragment);
870
- }
871
-
872
- });
873
-
874
- // Backbone.View
875
- // -------------
876
-
877
- // Creating a Backbone.View creates its initial element outside of the DOM,
878
- // if an existing element is not provided...
879
- Backbone.View = function(options) {
880
- this.cid = _.uniqueId('view');
881
- this._configure(options || {});
882
- this._ensureElement();
883
- this.delegateEvents();
884
- this.initialize.apply(this, arguments);
885
- };
886
-
887
- // Element lookup, scoped to DOM elements within the current view.
888
- // This should be prefered to global lookups, if you're dealing with
889
- // a specific view.
890
- var selectorDelegate = function(selector) {
891
- return $(selector, this.el);
892
- };
893
-
894
- // Cached regex to split keys for `delegate`.
895
- var eventSplitter = /^(\S+)\s*(.*)$/;
896
-
897
- // List of view options to be merged as properties.
898
- var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
899
-
900
- // Set up all inheritable **Backbone.View** properties and methods.
901
- _.extend(Backbone.View.prototype, Backbone.Events, {
902
-
903
- // The default `tagName` of a View's element is `"div"`.
904
- tagName : 'div',
905
-
906
- // Attach the `selectorDelegate` function as the `$` property.
907
- $ : selectorDelegate,
908
-
909
- // Initialize is an empty function by default. Override it with your own
910
- // initialization logic.
911
- initialize : function(){},
912
-
913
- // **render** is the core function that your view should override, in order
914
- // to populate its element (`this.el`), with the appropriate HTML. The
915
- // convention is for **render** to always return `this`.
916
- render : function() {
917
- return this;
918
- },
919
-
920
- // Remove this view from the DOM. Note that the view isn't present in the
921
- // DOM by default, so calling this method may be a no-op.
922
- remove : function() {
923
- $(this.el).remove();
924
- return this;
925
- },
926
-
927
- // For small amounts of DOM Elements, where a full-blown template isn't
928
- // needed, use **make** to manufacture elements, one at a time.
929
- //
930
- // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
931
- //
932
- make : function(tagName, attributes, content) {
933
- var el = document.createElement(tagName);
934
- if (attributes) $(el).attr(attributes);
935
- if (content) $(el).html(content);
936
- return el;
937
- },
938
-
939
- // Set callbacks, where `this.callbacks` is a hash of
940
- //
941
- // *{"event selector": "callback"}*
942
- //
943
- // {
944
- // 'mousedown .title': 'edit',
945
- // 'click .button': 'save'
946
- // }
947
- //
948
- // pairs. Callbacks will be bound to the view, with `this` set properly.
949
- // Uses event delegation for efficiency.
950
- // Omitting the selector binds the event to `this.el`.
951
- // This only works for delegate-able events: not `focus`, `blur`, and
952
- // not `change`, `submit`, and `reset` in Internet Explorer.
953
- delegateEvents : function(events) {
954
- if (!(events || (events = this.events))) return;
955
- if (_.isFunction(events)) events = events.call(this);
956
- $(this.el).unbind('.delegateEvents' + this.cid);
957
- for (var key in events) {
958
- var method = this[events[key]];
959
- if (!method) throw new Error('Event "' + events[key] + '" does not exist');
960
- var match = key.match(eventSplitter);
961
- var eventName = match[1], selector = match[2];
962
- method = _.bind(method, this);
963
- eventName += '.delegateEvents' + this.cid;
964
- if (selector === '') {
965
- $(this.el).bind(eventName, method);
966
- } else {
967
- $(this.el).delegate(selector, eventName, method);
968
- }
969
- }
970
- },
971
-
972
- // Performs the initial configuration of a View with a set of options.
973
- // Keys with special meaning *(model, collection, id, className)*, are
974
- // attached directly to the view.
975
- _configure : function(options) {
976
- if (this.options) options = _.extend({}, this.options, options);
977
- for (var i = 0, l = viewOptions.length; i < l; i++) {
978
- var attr = viewOptions[i];
979
- if (options[attr]) this[attr] = options[attr];
980
- }
981
- this.options = options;
982
- },
983
-
984
- // Ensure that the View has a DOM element to render into.
985
- // If `this.el` is a string, pass it through `$()`, take the first
986
- // matching element, and re-assign it to `el`. Otherwise, create
987
- // an element from the `id`, `className` and `tagName` proeprties.
988
- _ensureElement : function() {
989
- if (!this.el) {
990
- var attrs = this.attributes || {};
991
- if (this.id) attrs.id = this.id;
992
- if (this.className) attrs['class'] = this.className;
993
- this.el = this.make(this.tagName, attrs);
994
- } else if (_.isString(this.el)) {
995
- this.el = $(this.el).get(0);
996
- }
997
- }
998
-
999
- });
1000
-
1001
- // The self-propagating extend function that Backbone classes use.
1002
- var extend = function (protoProps, classProps) {
1003
- var child = inherits(this, protoProps, classProps);
1004
- child.extend = this.extend;
1005
- return child;
1006
- };
1007
-
1008
- // Set up inheritance for the model, collection, and view.
1009
- Backbone.Model.extend = Backbone.Collection.extend =
1010
- Backbone.Router.extend = Backbone.View.extend = extend;
1011
-
1012
- // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
1013
- var methodMap = {
1014
- 'create': 'POST',
1015
- 'update': 'PUT',
1016
- 'delete': 'DELETE',
1017
- 'read' : 'GET'
1018
- };
1019
-
1020
- // Backbone.sync
1021
- // -------------
1022
-
1023
- // Override this function to change the manner in which Backbone persists
1024
- // models to the server. You will be passed the type of request, and the
1025
- // model in question. By default, uses makes a RESTful Ajax request
1026
- // to the model's `url()`. Some possible customizations could be:
1027
- //
1028
- // * Use `setTimeout` to batch rapid-fire updates into a single request.
1029
- // * Send up the models as XML instead of JSON.
1030
- // * Persist models via WebSockets instead of Ajax.
1031
- //
1032
- // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
1033
- // as `POST`, with a `_method` parameter containing the true HTTP method,
1034
- // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
1035
- // `application/json` with the model in a param named `model`.
1036
- // Useful when interfacing with server-side languages like **PHP** that make
1037
- // it difficult to read the body of `PUT` requests.
1038
- Backbone.sync = function(method, model, options) {
1039
- var type = methodMap[method];
1040
-
1041
- // Default JSON-request options.
1042
- var params = _.extend({
1043
- type: type,
1044
- dataType: 'json'
1045
- }, options);
1046
-
1047
- // Ensure that we have a URL.
1048
- if (!params.url) {
1049
- params.url = getUrl(model) || urlError();
1050
- }
1051
-
1052
- // Ensure that we have the appropriate request data.
1053
- if (!params.data && model && (method == 'create' || method == 'update')) {
1054
- params.contentType = 'application/json';
1055
- params.data = JSON.stringify(model.toJSON());
1056
- }
1057
-
1058
- // For older servers, emulate JSON by encoding the request into an HTML-form.
1059
- if (Backbone.emulateJSON) {
1060
- params.contentType = 'application/x-www-form-urlencoded';
1061
- params.data = params.data ? {model : params.data} : {};
1062
- }
1063
-
1064
- // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
1065
- // And an `X-HTTP-Method-Override` header.
1066
- if (Backbone.emulateHTTP) {
1067
- if (type === 'PUT' || type === 'DELETE') {
1068
- if (Backbone.emulateJSON) params.data._method = type;
1069
- params.type = 'POST';
1070
- params.beforeSend = function(xhr) {
1071
- xhr.setRequestHeader('X-HTTP-Method-Override', type);
1072
- };
1073
- }
1074
- }
1075
-
1076
- // Don't process data on a non-GET request.
1077
- if (params.type !== 'GET' && !Backbone.emulateJSON) {
1078
- params.processData = false;
1079
- }
1080
-
1081
- // Make the request.
1082
- return $.ajax(params);
1083
- };
1084
-
1085
- // Helpers
1086
- // -------
1087
-
1088
- // Shared empty constructor function to aid in prototype-chain creation.
1089
- var ctor = function(){};
1090
-
1091
- // Helper function to correctly set up the prototype chain, for subclasses.
1092
- // Similar to `goog.inherits`, but uses a hash of prototype properties and
1093
- // class properties to be extended.
1094
- var inherits = function(parent, protoProps, staticProps) {
1095
- var child;
1096
-
1097
- // The constructor function for the new subclass is either defined by you
1098
- // (the "constructor" property in your `extend` definition), or defaulted
1099
- // by us to simply call `super()`.
1100
- if (protoProps && protoProps.hasOwnProperty('constructor')) {
1101
- child = protoProps.constructor;
1102
- } else {
1103
- child = function(){ return parent.apply(this, arguments); };
1104
- }
1105
-
1106
- // Inherit class (static) properties from parent.
1107
- _.extend(child, parent);
1108
-
1109
- // Set the prototype chain to inherit from `parent`, without calling
1110
- // `parent`'s constructor function.
1111
- ctor.prototype = parent.prototype;
1112
- child.prototype = new ctor();
1113
-
1114
- // Add prototype properties (instance properties) to the subclass,
1115
- // if supplied.
1116
- if (protoProps) _.extend(child.prototype, protoProps);
1117
-
1118
- // Add static properties to the constructor function, if supplied.
1119
- if (staticProps) _.extend(child, staticProps);
1120
-
1121
- // Correctly set child's `prototype.constructor`.
1122
- child.prototype.constructor = child;
1123
-
1124
- // Set a convenience property in case the parent's prototype is needed later.
1125
- child.__super__ = parent.prototype;
1126
-
1127
- return child;
1128
- };
1129
-
1130
- // Helper function to get a URL from a Model or Collection as a property
1131
- // or as a function.
1132
- var getUrl = function(object) {
1133
- if (!(object && object.url)) return null;
1134
- return _.isFunction(object.url) ? object.url() : object.url;
1135
- };
1136
-
1137
- // Throw an error when a URL is needed, and none is supplied.
1138
- var urlError = function() {
1139
- throw new Error('A "url" property or function must be specified');
1140
- };
1141
-
1142
- // Wrap an optional error callback with a fallback error event.
1143
- var wrapError = function(onError, model, options) {
1144
- return function(resp) {
1145
- if (onError) {
1146
- onError(model, resp, options);
1147
- } else {
1148
- model.trigger('error', model, resp, options);
1149
- }
1150
- };
1151
- };
1152
-
1153
- // Helper function to escape a string for HTML rendering.
1154
- var escapeHTML = function(string) {
1155
- return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
1156
- };
1157
-
1158
- }).call(this);