changeset 16 | a86126ab1dd4 |
parent 9 | 177826044cd9 |
child 19 | 3d72ae0968f4 |
15:3d4e9c994f10 | 16:a86126ab1dd4 |
---|---|
1 // Backbone.js 1.3.3 |
1 // Backbone.js 1.4.0 |
2 |
2 |
3 // (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors |
3 // (c) 2010-2019 Jeremy Ashkenas and DocumentCloud |
4 // Backbone may be freely distributed under the MIT license. |
4 // Backbone may be freely distributed under the MIT license. |
5 // For all details and documentation: |
5 // For all details and documentation: |
6 // http://backbonejs.org |
6 // http://backbonejs.org |
7 |
7 |
8 (function(factory) { |
8 (function(factory) { |
9 |
9 |
10 // Establish the root object, `window` (`self`) in the browser, or `global` on the server. |
10 // Establish the root object, `window` (`self`) in the browser, or `global` on the server. |
11 // We use `self` instead of `window` for `WebWorker` support. |
11 // We use `self` instead of `window` for `WebWorker` support. |
12 var root = (typeof self == 'object' && self.self === self && self) || |
12 var root = typeof self == 'object' && self.self === self && self || |
13 (typeof global == 'object' && global.global === global && global); |
13 typeof global == 'object' && global.global === global && global; |
14 |
14 |
15 // Set up Backbone appropriately for the environment. Start with AMD. |
15 // Set up Backbone appropriately for the environment. Start with AMD. |
16 if (typeof define === 'function' && define.amd) { |
16 if (typeof define === 'function' && define.amd) { |
17 define(['underscore', 'jquery', 'exports'], function(_, $, exports) { |
17 define(['underscore', 'jquery', 'exports'], function(_, $, exports) { |
18 // Export global even in AMD case in case this script is loaded with |
18 // Export global even in AMD case in case this script is loaded with |
26 try { $ = require('jquery'); } catch (e) {} |
26 try { $ = require('jquery'); } catch (e) {} |
27 factory(root, exports, _, $); |
27 factory(root, exports, _, $); |
28 |
28 |
29 // Finally, as a browser global. |
29 // Finally, as a browser global. |
30 } else { |
30 } else { |
31 root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$)); |
31 root.Backbone = factory(root, {}, root._, root.jQuery || root.Zepto || root.ender || root.$); |
32 } |
32 } |
33 |
33 |
34 })(function(root, Backbone, _, $) { |
34 })(function(root, Backbone, _, $) { |
35 |
35 |
36 // Initial Setup |
36 // Initial Setup |
42 |
42 |
43 // Create a local reference to a common array method we'll want to use later. |
43 // Create a local reference to a common array method we'll want to use later. |
44 var slice = Array.prototype.slice; |
44 var slice = Array.prototype.slice; |
45 |
45 |
46 // Current version of the library. Keep in sync with `package.json`. |
46 // Current version of the library. Keep in sync with `package.json`. |
47 Backbone.VERSION = '1.3.3'; |
47 Backbone.VERSION = '1.4.0'; |
48 |
48 |
49 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns |
49 // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns |
50 // the `$` variable. |
50 // the `$` variable. |
51 Backbone.$ = $; |
51 Backbone.$ = $; |
52 |
52 |
65 // Turn on `emulateJSON` to support legacy servers that can't deal with direct |
65 // Turn on `emulateJSON` to support legacy servers that can't deal with direct |
66 // `application/json` requests ... this will encode the body as |
66 // `application/json` requests ... this will encode the body as |
67 // `application/x-www-form-urlencoded` instead and will send the model in a |
67 // `application/x-www-form-urlencoded` instead and will send the model in a |
68 // form param named `model`. |
68 // form param named `model`. |
69 Backbone.emulateJSON = false; |
69 Backbone.emulateJSON = false; |
70 |
|
71 // Proxy Backbone class methods to Underscore functions, wrapping the model's |
|
72 // `attributes` object or collection's `models` array behind the scenes. |
|
73 // |
|
74 // collection.filter(function(model) { return model.get('age') > 10 }); |
|
75 // collection.each(this.addView); |
|
76 // |
|
77 // `Function#apply` can be slow so we use the method's arg count, if we know it. |
|
78 var addMethod = function(length, method, attribute) { |
|
79 switch (length) { |
|
80 case 1: return function() { |
|
81 return _[method](this[attribute]); |
|
82 }; |
|
83 case 2: return function(value) { |
|
84 return _[method](this[attribute], value); |
|
85 }; |
|
86 case 3: return function(iteratee, context) { |
|
87 return _[method](this[attribute], cb(iteratee, this), context); |
|
88 }; |
|
89 case 4: return function(iteratee, defaultVal, context) { |
|
90 return _[method](this[attribute], cb(iteratee, this), defaultVal, context); |
|
91 }; |
|
92 default: return function() { |
|
93 var args = slice.call(arguments); |
|
94 args.unshift(this[attribute]); |
|
95 return _[method].apply(_, args); |
|
96 }; |
|
97 } |
|
98 }; |
|
99 var addUnderscoreMethods = function(Class, methods, attribute) { |
|
100 _.each(methods, function(length, method) { |
|
101 if (_[method]) Class.prototype[method] = addMethod(length, method, attribute); |
|
102 }); |
|
103 }; |
|
104 |
|
105 // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. |
|
106 var cb = function(iteratee, instance) { |
|
107 if (_.isFunction(iteratee)) return iteratee; |
|
108 if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); |
|
109 if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; |
|
110 return iteratee; |
|
111 }; |
|
112 var modelMatcher = function(attrs) { |
|
113 var matcher = _.matches(attrs); |
|
114 return function(model) { |
|
115 return matcher(model.attributes); |
|
116 }; |
|
117 }; |
|
118 |
70 |
119 // Backbone.Events |
71 // Backbone.Events |
120 // --------------- |
72 // --------------- |
121 |
73 |
122 // A module that can be mixed in to *any object* in order to provide it with |
74 // A module that can be mixed in to *any object* in order to provide it with |
132 var Events = Backbone.Events = {}; |
84 var Events = Backbone.Events = {}; |
133 |
85 |
134 // Regular expression used to split event strings. |
86 // Regular expression used to split event strings. |
135 var eventSplitter = /\s+/; |
87 var eventSplitter = /\s+/; |
136 |
88 |
89 // A private global variable to share between listeners and listenees. |
|
90 var _listening; |
|
91 |
|
137 // Iterates over the standard `event, callback` (as well as the fancy multiple |
92 // Iterates over the standard `event, callback` (as well as the fancy multiple |
138 // space-separated events `"change blur", callback` and jQuery-style event |
93 // space-separated events `"change blur", callback` and jQuery-style event |
139 // maps `{event: callback}`). |
94 // maps `{event: callback}`). |
140 var eventsApi = function(iteratee, events, name, callback, opts) { |
95 var eventsApi = function(iteratee, events, name, callback, opts) { |
141 var i = 0, names; |
96 var i = 0, names; |
158 }; |
113 }; |
159 |
114 |
160 // Bind an event to a `callback` function. Passing `"all"` will bind |
115 // Bind an event to a `callback` function. Passing `"all"` will bind |
161 // the callback to all events fired. |
116 // the callback to all events fired. |
162 Events.on = function(name, callback, context) { |
117 Events.on = function(name, callback, context) { |
163 return internalOn(this, name, callback, context); |
118 this._events = eventsApi(onApi, this._events || {}, name, callback, { |
164 }; |
|
165 |
|
166 // Guard the `listening` argument from the public API. |
|
167 var internalOn = function(obj, name, callback, context, listening) { |
|
168 obj._events = eventsApi(onApi, obj._events || {}, name, callback, { |
|
169 context: context, |
119 context: context, |
170 ctx: obj, |
120 ctx: this, |
171 listening: listening |
121 listening: _listening |
172 }); |
122 }); |
173 |
123 |
174 if (listening) { |
124 if (_listening) { |
175 var listeners = obj._listeners || (obj._listeners = {}); |
125 var listeners = this._listeners || (this._listeners = {}); |
176 listeners[listening.id] = listening; |
126 listeners[_listening.id] = _listening; |
177 } |
127 // Allow the listening to use a counter, instead of tracking |
178 |
128 // callbacks for library interop |
179 return obj; |
129 _listening.interop = false; |
130 } |
|
131 |
|
132 return this; |
|
180 }; |
133 }; |
181 |
134 |
182 // Inversion-of-control versions of `on`. Tell *this* object to listen to |
135 // Inversion-of-control versions of `on`. Tell *this* object to listen to |
183 // an event in another object... keeping track of what it's listening to |
136 // an event in another object... keeping track of what it's listening to |
184 // for easier unbinding later. |
137 // for easier unbinding later. |
185 Events.listenTo = function(obj, name, callback) { |
138 Events.listenTo = function(obj, name, callback) { |
186 if (!obj) return this; |
139 if (!obj) return this; |
187 var id = obj._listenId || (obj._listenId = _.uniqueId('l')); |
140 var id = obj._listenId || (obj._listenId = _.uniqueId('l')); |
188 var listeningTo = this._listeningTo || (this._listeningTo = {}); |
141 var listeningTo = this._listeningTo || (this._listeningTo = {}); |
189 var listening = listeningTo[id]; |
142 var listening = _listening = listeningTo[id]; |
190 |
143 |
191 // This object is not listening to any other events on `obj` yet. |
144 // This object is not listening to any other events on `obj` yet. |
192 // Setup the necessary references to track the listening callbacks. |
145 // Setup the necessary references to track the listening callbacks. |
193 if (!listening) { |
146 if (!listening) { |
194 var thisId = this._listenId || (this._listenId = _.uniqueId('l')); |
147 this._listenId || (this._listenId = _.uniqueId('l')); |
195 listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0}; |
148 listening = _listening = listeningTo[id] = new Listening(this, obj); |
196 } |
149 } |
197 |
150 |
198 // Bind callbacks on obj, and keep track of them on listening. |
151 // Bind callbacks on obj. |
199 internalOn(obj, name, callback, this, listening); |
152 var error = tryCatchOn(obj, name, callback, this); |
153 _listening = void 0; |
|
154 |
|
155 if (error) throw error; |
|
156 // If the target obj is not Backbone.Events, track events manually. |
|
157 if (listening.interop) listening.on(name, callback); |
|
158 |
|
200 return this; |
159 return this; |
201 }; |
160 }; |
202 |
161 |
203 // The reducing API that adds a callback to the `events` object. |
162 // The reducing API that adds a callback to the `events` object. |
204 var onApi = function(events, name, callback, options) { |
163 var onApi = function(events, name, callback, options) { |
208 if (listening) listening.count++; |
167 if (listening) listening.count++; |
209 |
168 |
210 handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); |
169 handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); |
211 } |
170 } |
212 return events; |
171 return events; |
172 }; |
|
173 |
|
174 // An try-catch guarded #on function, to prevent poisoning the global |
|
175 // `_listening` variable. |
|
176 var tryCatchOn = function(obj, name, callback, context) { |
|
177 try { |
|
178 obj.on(name, callback, context); |
|
179 } catch (e) { |
|
180 return e; |
|
181 } |
|
213 }; |
182 }; |
214 |
183 |
215 // Remove one or many callbacks. If `context` is null, removes all |
184 // Remove one or many callbacks. If `context` is null, removes all |
216 // callbacks with that function. If `callback` is null, removes all |
185 // callbacks with that function. If `callback` is null, removes all |
217 // callbacks for the event. If `name` is null, removes all bound |
186 // callbacks for the event. If `name` is null, removes all bound |
220 if (!this._events) return this; |
189 if (!this._events) return this; |
221 this._events = eventsApi(offApi, this._events, name, callback, { |
190 this._events = eventsApi(offApi, this._events, name, callback, { |
222 context: context, |
191 context: context, |
223 listeners: this._listeners |
192 listeners: this._listeners |
224 }); |
193 }); |
194 |
|
225 return this; |
195 return this; |
226 }; |
196 }; |
227 |
197 |
228 // Tell this object to stop listening to either specific events ... or |
198 // Tell this object to stop listening to either specific events ... or |
229 // to every object it's currently listening to. |
199 // to every object it's currently listening to. |
230 Events.stopListening = function(obj, name, callback) { |
200 Events.stopListening = function(obj, name, callback) { |
231 var listeningTo = this._listeningTo; |
201 var listeningTo = this._listeningTo; |
232 if (!listeningTo) return this; |
202 if (!listeningTo) return this; |
233 |
203 |
234 var ids = obj ? [obj._listenId] : _.keys(listeningTo); |
204 var ids = obj ? [obj._listenId] : _.keys(listeningTo); |
235 |
|
236 for (var i = 0; i < ids.length; i++) { |
205 for (var i = 0; i < ids.length; i++) { |
237 var listening = listeningTo[ids[i]]; |
206 var listening = listeningTo[ids[i]]; |
238 |
207 |
239 // If listening doesn't exist, this object is not currently |
208 // If listening doesn't exist, this object is not currently |
240 // listening to obj. Break out early. |
209 // listening to obj. Break out early. |
241 if (!listening) break; |
210 if (!listening) break; |
242 |
211 |
243 listening.obj.off(name, callback, this); |
212 listening.obj.off(name, callback, this); |
244 } |
213 if (listening.interop) listening.off(name, callback); |
214 } |
|
215 if (_.isEmpty(listeningTo)) this._listeningTo = void 0; |
|
245 |
216 |
246 return this; |
217 return this; |
247 }; |
218 }; |
248 |
219 |
249 // The reducing API that removes a callback from the `events` object. |
220 // The reducing API that removes a callback from the `events` object. |
250 var offApi = function(events, name, callback, options) { |
221 var offApi = function(events, name, callback, options) { |
251 if (!events) return; |
222 if (!events) return; |
252 |
223 |
253 var i = 0, listening; |
|
254 var context = options.context, listeners = options.listeners; |
224 var context = options.context, listeners = options.listeners; |
255 |
225 var i = 0, names; |
256 // Delete all events listeners and "drop" events. |
226 |
257 if (!name && !callback && !context) { |
227 // Delete all event listeners and "drop" events. |
258 var ids = _.keys(listeners); |
228 if (!name && !context && !callback) { |
259 for (; i < ids.length; i++) { |
229 for (names = _.keys(listeners); i < names.length; i++) { |
260 listening = listeners[ids[i]]; |
230 listeners[names[i]].cleanup(); |
261 delete listeners[listening.id]; |
|
262 delete listening.listeningTo[listening.objId]; |
|
263 } |
231 } |
264 return; |
232 return; |
265 } |
233 } |
266 |
234 |
267 var names = name ? [name] : _.keys(events); |
235 names = name ? [name] : _.keys(events); |
268 for (; i < names.length; i++) { |
236 for (; i < names.length; i++) { |
269 name = names[i]; |
237 name = names[i]; |
270 var handlers = events[name]; |
238 var handlers = events[name]; |
271 |
239 |
272 // Bail out if there are no events stored. |
240 // Bail out if there are no events stored. |
273 if (!handlers) break; |
241 if (!handlers) break; |
274 |
242 |
275 // Replace events if there are any remaining. Otherwise, clean up. |
243 // Find any remaining events. |
276 var remaining = []; |
244 var remaining = []; |
277 for (var j = 0; j < handlers.length; j++) { |
245 for (var j = 0; j < handlers.length; j++) { |
278 var handler = handlers[j]; |
246 var handler = handlers[j]; |
279 if ( |
247 if ( |
280 callback && callback !== handler.callback && |
248 callback && callback !== handler.callback && |
281 callback !== handler.callback._callback || |
249 callback !== handler.callback._callback || |
282 context && context !== handler.context |
250 context && context !== handler.context |
283 ) { |
251 ) { |
284 remaining.push(handler); |
252 remaining.push(handler); |
285 } else { |
253 } else { |
286 listening = handler.listening; |
254 var listening = handler.listening; |
287 if (listening && --listening.count === 0) { |
255 if (listening) listening.off(name, callback); |
288 delete listeners[listening.id]; |
|
289 delete listening.listeningTo[listening.objId]; |
|
290 } |
|
291 } |
256 } |
292 } |
257 } |
293 |
258 |
294 // Update tail event if the list has any events. Otherwise, clean up. |
259 // Replace events if there are any remaining. Otherwise, clean up. |
295 if (remaining.length) { |
260 if (remaining.length) { |
296 events[name] = remaining; |
261 events[name] = remaining; |
297 } else { |
262 } else { |
298 delete events[name]; |
263 delete events[name]; |
299 } |
264 } |
300 } |
265 } |
266 |
|
301 return events; |
267 return events; |
302 }; |
268 }; |
303 |
269 |
304 // Bind an event to only be triggered a single time. After the first time |
270 // Bind an event to only be triggered a single time. After the first time |
305 // the callback is invoked, its listener will be removed. If multiple events |
271 // the callback is invoked, its listener will be removed. If multiple events |
306 // are passed in using the space-separated syntax, the handler will fire |
272 // are passed in using the space-separated syntax, the handler will fire |
307 // once for each event, not once for a combination of all events. |
273 // once for each event, not once for a combination of all events. |
308 Events.once = function(name, callback, context) { |
274 Events.once = function(name, callback, context) { |
309 // Map the event into a `{event: once}` object. |
275 // Map the event into a `{event: once}` object. |
310 var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this)); |
276 var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this)); |
311 if (typeof name === 'string' && context == null) callback = void 0; |
277 if (typeof name === 'string' && context == null) callback = void 0; |
312 return this.on(events, callback, context); |
278 return this.on(events, callback, context); |
313 }; |
279 }; |
314 |
280 |
315 // Inversion-of-control versions of `once`. |
281 // Inversion-of-control versions of `once`. |
316 Events.listenToOnce = function(obj, name, callback) { |
282 Events.listenToOnce = function(obj, name, callback) { |
317 // Map the event into a `{event: once}` object. |
283 // Map the event into a `{event: once}` object. |
318 var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj)); |
284 var events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj)); |
319 return this.listenTo(obj, events); |
285 return this.listenTo(obj, events); |
320 }; |
286 }; |
321 |
287 |
322 // Reduces the event callbacks into a map of `{event: onceWrapper}`. |
288 // Reduces the event callbacks into a map of `{event: onceWrapper}`. |
323 // `offer` unbinds the `onceWrapper` after it has been called. |
289 // `offer` unbinds the `onceWrapper` after it has been called. |
371 case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; |
337 case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; |
372 default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; |
338 default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; |
373 } |
339 } |
374 }; |
340 }; |
375 |
341 |
342 // A listening class that tracks and cleans up memory bindings |
|
343 // when all callbacks have been offed. |
|
344 var Listening = function(listener, obj) { |
|
345 this.id = listener._listenId; |
|
346 this.listener = listener; |
|
347 this.obj = obj; |
|
348 this.interop = true; |
|
349 this.count = 0; |
|
350 this._events = void 0; |
|
351 }; |
|
352 |
|
353 Listening.prototype.on = Events.on; |
|
354 |
|
355 // Offs a callback (or several). |
|
356 // Uses an optimized counter if the listenee uses Backbone.Events. |
|
357 // Otherwise, falls back to manual tracking to support events |
|
358 // library interop. |
|
359 Listening.prototype.off = function(name, callback) { |
|
360 var cleanup; |
|
361 if (this.interop) { |
|
362 this._events = eventsApi(offApi, this._events, name, callback, { |
|
363 context: void 0, |
|
364 listeners: void 0 |
|
365 }); |
|
366 cleanup = !this._events; |
|
367 } else { |
|
368 this.count--; |
|
369 cleanup = this.count === 0; |
|
370 } |
|
371 if (cleanup) this.cleanup(); |
|
372 }; |
|
373 |
|
374 // Cleans up memory bindings between the listener and the listenee. |
|
375 Listening.prototype.cleanup = function() { |
|
376 delete this.listener._listeningTo[this.obj._listenId]; |
|
377 if (!this.interop) delete this.obj._listeners[this.id]; |
|
378 }; |
|
379 |
|
376 // Aliases for backwards compatibility. |
380 // Aliases for backwards compatibility. |
377 Events.bind = Events.on; |
381 Events.bind = Events.on; |
378 Events.unbind = Events.off; |
382 Events.unbind = Events.off; |
379 |
383 |
380 // Allow the `Backbone` object to serve as a global event bus, for folks who |
384 // Allow the `Backbone` object to serve as a global event bus, for folks who |
392 // Create a new model with the specified attributes. A client id (`cid`) |
396 // Create a new model with the specified attributes. A client id (`cid`) |
393 // is automatically generated and assigned for you. |
397 // is automatically generated and assigned for you. |
394 var Model = Backbone.Model = function(attributes, options) { |
398 var Model = Backbone.Model = function(attributes, options) { |
395 var attrs = attributes || {}; |
399 var attrs = attributes || {}; |
396 options || (options = {}); |
400 options || (options = {}); |
401 this.preinitialize.apply(this, arguments); |
|
397 this.cid = _.uniqueId(this.cidPrefix); |
402 this.cid = _.uniqueId(this.cidPrefix); |
398 this.attributes = {}; |
403 this.attributes = {}; |
399 if (options.collection) this.collection = options.collection; |
404 if (options.collection) this.collection = options.collection; |
400 if (options.parse) attrs = this.parse(attrs, options) || {}; |
405 if (options.parse) attrs = this.parse(attrs, options) || {}; |
401 var defaults = _.result(this, 'defaults'); |
406 var defaults = _.result(this, 'defaults'); |
419 idAttribute: 'id', |
424 idAttribute: 'id', |
420 |
425 |
421 // The prefix is used to create the client id which is used to identify models locally. |
426 // The prefix is used to create the client id which is used to identify models locally. |
422 // You may want to override this if you're experiencing name clashes with model ids. |
427 // You may want to override this if you're experiencing name clashes with model ids. |
423 cidPrefix: 'c', |
428 cidPrefix: 'c', |
429 |
|
430 // preinitialize is an empty function by default. You can override it with a function |
|
431 // or object. preinitialize will run before any instantiation logic is run in the Model. |
|
432 preinitialize: function(){}, |
|
424 |
433 |
425 // Initialize is an empty function by default. Override it with your own |
434 // Initialize is an empty function by default. Override it with your own |
426 // initialization logic. |
435 // initialization logic. |
427 initialize: function(){}, |
436 initialize: function(){}, |
428 |
437 |
560 // determining if there *would be* a change. |
569 // determining if there *would be* a change. |
561 changedAttributes: function(diff) { |
570 changedAttributes: function(diff) { |
562 if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; |
571 if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; |
563 var old = this._changing ? this._previousAttributes : this.attributes; |
572 var old = this._changing ? this._previousAttributes : this.attributes; |
564 var changed = {}; |
573 var changed = {}; |
574 var hasChanged; |
|
565 for (var attr in diff) { |
575 for (var attr in diff) { |
566 var val = diff[attr]; |
576 var val = diff[attr]; |
567 if (_.isEqual(old[attr], val)) continue; |
577 if (_.isEqual(old[attr], val)) continue; |
568 changed[attr] = val; |
578 changed[attr] = val; |
569 } |
579 hasChanged = true; |
570 return _.size(changed) ? changed : false; |
580 } |
581 return hasChanged ? changed : false; |
|
571 }, |
582 }, |
572 |
583 |
573 // Get the previous value of an attribute, recorded at the time the last |
584 // Get the previous value of an attribute, recorded at the time the last |
574 // `"change"` event was fired. |
585 // `"change"` event was fired. |
575 previous: function(attr) { |
586 previous: function(attr) { |
641 wrapError(this, options); |
652 wrapError(this, options); |
642 |
653 |
643 // Set temporary attributes if `{wait: true}` to properly find new ids. |
654 // Set temporary attributes if `{wait: true}` to properly find new ids. |
644 if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); |
655 if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); |
645 |
656 |
646 var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update'); |
657 var method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update'; |
647 if (method === 'patch' && !options.attrs) options.attrs = attrs; |
658 if (method === 'patch' && !options.attrs) options.attrs = attrs; |
648 var xhr = this.sync(method, this, options); |
659 var xhr = this.sync(method, this, options); |
649 |
660 |
650 // Restore attributes. |
661 // Restore attributes. |
651 this.attributes = attributes; |
662 this.attributes = attributes; |
729 return false; |
740 return false; |
730 } |
741 } |
731 |
742 |
732 }); |
743 }); |
733 |
744 |
734 // Underscore methods that we want to implement on the Model, mapped to the |
|
735 // number of arguments they take. |
|
736 var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, |
|
737 omit: 0, chain: 1, isEmpty: 1}; |
|
738 |
|
739 // Mix in each Underscore method as a proxy to `Model#attributes`. |
|
740 addUnderscoreMethods(Model, modelMethods, 'attributes'); |
|
741 |
|
742 // Backbone.Collection |
745 // Backbone.Collection |
743 // ------------------- |
746 // ------------------- |
744 |
747 |
745 // If models tend to represent a single row of data, a Backbone Collection is |
748 // If models tend to represent a single row of data, a Backbone Collection is |
746 // more analogous to a table full of data ... or a small slice or page of that |
749 // more analogous to a table full of data ... or a small slice or page of that |
752 // Create a new **Collection**, perhaps to contain a specific type of `model`. |
755 // Create a new **Collection**, perhaps to contain a specific type of `model`. |
753 // If a `comparator` is specified, the Collection will maintain |
756 // If a `comparator` is specified, the Collection will maintain |
754 // its models in sort order, as they're added and removed. |
757 // its models in sort order, as they're added and removed. |
755 var Collection = Backbone.Collection = function(models, options) { |
758 var Collection = Backbone.Collection = function(models, options) { |
756 options || (options = {}); |
759 options || (options = {}); |
760 this.preinitialize.apply(this, arguments); |
|
757 if (options.model) this.model = options.model; |
761 if (options.model) this.model = options.model; |
758 if (options.comparator !== void 0) this.comparator = options.comparator; |
762 if (options.comparator !== void 0) this.comparator = options.comparator; |
759 this._reset(); |
763 this._reset(); |
760 this.initialize.apply(this, arguments); |
764 this.initialize.apply(this, arguments); |
761 if (models) this.reset(models, _.extend({silent: true}, options)); |
765 if (models) this.reset(models, _.extend({silent: true}, options)); |
780 _.extend(Collection.prototype, Events, { |
784 _.extend(Collection.prototype, Events, { |
781 |
785 |
782 // The default model for a collection is just a **Backbone.Model**. |
786 // The default model for a collection is just a **Backbone.Model**. |
783 // This should be overridden in most cases. |
787 // This should be overridden in most cases. |
784 model: Model, |
788 model: Model, |
789 |
|
790 |
|
791 // preinitialize is an empty function by default. You can override it with a function |
|
792 // or object. preinitialize will run before any instantiation logic is run in the Collection. |
|
793 preinitialize: function(){}, |
|
785 |
794 |
786 // Initialize is an empty function by default. Override it with your own |
795 // Initialize is an empty function by default. Override it with your own |
787 // initialization logic. |
796 // initialization logic. |
788 initialize: function(){}, |
797 initialize: function(){}, |
789 |
798 |
983 // Get a model from the set by id, cid, model object with id or cid |
992 // Get a model from the set by id, cid, model object with id or cid |
984 // properties, or an attributes object that is transformed through modelId. |
993 // properties, or an attributes object that is transformed through modelId. |
985 get: function(obj) { |
994 get: function(obj) { |
986 if (obj == null) return void 0; |
995 if (obj == null) return void 0; |
987 return this._byId[obj] || |
996 return this._byId[obj] || |
988 this._byId[this.modelId(obj.attributes || obj)] || |
997 this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj)] || |
989 obj.cid && this._byId[obj.cid]; |
998 obj.cid && this._byId[obj.cid]; |
990 }, |
999 }, |
991 |
1000 |
992 // Returns `true` if the model is in the collection. |
1001 // Returns `true` if the model is in the collection. |
993 has: function(obj) { |
1002 has: function(obj) { |
1019 var comparator = this.comparator; |
1028 var comparator = this.comparator; |
1020 if (!comparator) throw new Error('Cannot sort a set without a comparator'); |
1029 if (!comparator) throw new Error('Cannot sort a set without a comparator'); |
1021 options || (options = {}); |
1030 options || (options = {}); |
1022 |
1031 |
1023 var length = comparator.length; |
1032 var length = comparator.length; |
1024 if (_.isFunction(comparator)) comparator = _.bind(comparator, this); |
1033 if (_.isFunction(comparator)) comparator = comparator.bind(this); |
1025 |
1034 |
1026 // Run sort based on type of `comparator`. |
1035 // Run sort based on type of `comparator`. |
1027 if (length === 1 || _.isString(comparator)) { |
1036 if (length === 1 || _.isString(comparator)) { |
1028 this.models = this.sortBy(comparator); |
1037 this.models = this.sortBy(comparator); |
1029 } else { |
1038 } else { |
1091 // Define how to uniquely identify models in the collection. |
1100 // Define how to uniquely identify models in the collection. |
1092 modelId: function(attrs) { |
1101 modelId: function(attrs) { |
1093 return attrs[this.model.prototype.idAttribute || 'id']; |
1102 return attrs[this.model.prototype.idAttribute || 'id']; |
1094 }, |
1103 }, |
1095 |
1104 |
1105 // Get an iterator of all models in this collection. |
|
1106 values: function() { |
|
1107 return new CollectionIterator(this, ITERATOR_VALUES); |
|
1108 }, |
|
1109 |
|
1110 // Get an iterator of all model IDs in this collection. |
|
1111 keys: function() { |
|
1112 return new CollectionIterator(this, ITERATOR_KEYS); |
|
1113 }, |
|
1114 |
|
1115 // Get an iterator of all [ID, model] tuples in this collection. |
|
1116 entries: function() { |
|
1117 return new CollectionIterator(this, ITERATOR_KEYSVALUES); |
|
1118 }, |
|
1119 |
|
1096 // Private method to reset all internal state. Called when the collection |
1120 // Private method to reset all internal state. Called when the collection |
1097 // is first initialized or reset. |
1121 // is first initialized or reset. |
1098 _reset: function() { |
1122 _reset: function() { |
1099 this.length = 0; |
1123 this.length = 0; |
1100 this.models = []; |
1124 this.models = []; |
1187 this.trigger.apply(this, arguments); |
1211 this.trigger.apply(this, arguments); |
1188 } |
1212 } |
1189 |
1213 |
1190 }); |
1214 }); |
1191 |
1215 |
1192 // Underscore methods that we want to implement on the Collection. |
1216 // Defining an @@iterator method implements JavaScript's Iterable protocol. |
1193 // 90% of the core usefulness of Backbone Collections is actually implemented |
1217 // In modern ES2015 browsers, this value is found at Symbol.iterator. |
1194 // right here: |
1218 /* global Symbol */ |
1195 var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, |
1219 var $$iterator = typeof Symbol === 'function' && Symbol.iterator; |
1196 foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, |
1220 if ($$iterator) { |
1197 select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, |
1221 Collection.prototype[$$iterator] = Collection.prototype.values; |
1198 contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, |
1222 } |
1199 head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, |
1223 |
1200 without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, |
1224 // CollectionIterator |
1201 isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, |
1225 // ------------------ |
1202 sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; |
1226 |
1203 |
1227 // A CollectionIterator implements JavaScript's Iterator protocol, allowing the |
1204 // Mix in each Underscore method as a proxy to `Collection#models`. |
1228 // use of `for of` loops in modern browsers and interoperation between |
1205 addUnderscoreMethods(Collection, collectionMethods, 'models'); |
1229 // Backbone.Collection and other JavaScript functions and third-party libraries |
1230 // which can operate on Iterables. |
|
1231 var CollectionIterator = function(collection, kind) { |
|
1232 this._collection = collection; |
|
1233 this._kind = kind; |
|
1234 this._index = 0; |
|
1235 }; |
|
1236 |
|
1237 // This "enum" defines the three possible kinds of values which can be emitted |
|
1238 // by a CollectionIterator that correspond to the values(), keys() and entries() |
|
1239 // methods on Collection, respectively. |
|
1240 var ITERATOR_VALUES = 1; |
|
1241 var ITERATOR_KEYS = 2; |
|
1242 var ITERATOR_KEYSVALUES = 3; |
|
1243 |
|
1244 // All Iterators should themselves be Iterable. |
|
1245 if ($$iterator) { |
|
1246 CollectionIterator.prototype[$$iterator] = function() { |
|
1247 return this; |
|
1248 }; |
|
1249 } |
|
1250 |
|
1251 CollectionIterator.prototype.next = function() { |
|
1252 if (this._collection) { |
|
1253 |
|
1254 // Only continue iterating if the iterated collection is long enough. |
|
1255 if (this._index < this._collection.length) { |
|
1256 var model = this._collection.at(this._index); |
|
1257 this._index++; |
|
1258 |
|
1259 // Construct a value depending on what kind of values should be iterated. |
|
1260 var value; |
|
1261 if (this._kind === ITERATOR_VALUES) { |
|
1262 value = model; |
|
1263 } else { |
|
1264 var id = this._collection.modelId(model.attributes); |
|
1265 if (this._kind === ITERATOR_KEYS) { |
|
1266 value = id; |
|
1267 } else { // ITERATOR_KEYSVALUES |
|
1268 value = [id, model]; |
|
1269 } |
|
1270 } |
|
1271 return {value: value, done: false}; |
|
1272 } |
|
1273 |
|
1274 // Once exhausted, remove the reference to the collection so future |
|
1275 // calls to the next method always return done. |
|
1276 this._collection = void 0; |
|
1277 } |
|
1278 |
|
1279 return {value: void 0, done: true}; |
|
1280 }; |
|
1206 |
1281 |
1207 // Backbone.View |
1282 // Backbone.View |
1208 // ------------- |
1283 // ------------- |
1209 |
1284 |
1210 // Backbone Views are almost more convention than they are actual code. A View |
1285 // Backbone Views are almost more convention than they are actual code. A View |
1217 |
1292 |
1218 // Creating a Backbone.View creates its initial element outside of the DOM, |
1293 // Creating a Backbone.View creates its initial element outside of the DOM, |
1219 // if an existing element is not provided... |
1294 // if an existing element is not provided... |
1220 var View = Backbone.View = function(options) { |
1295 var View = Backbone.View = function(options) { |
1221 this.cid = _.uniqueId('view'); |
1296 this.cid = _.uniqueId('view'); |
1297 this.preinitialize.apply(this, arguments); |
|
1222 _.extend(this, _.pick(options, viewOptions)); |
1298 _.extend(this, _.pick(options, viewOptions)); |
1223 this._ensureElement(); |
1299 this._ensureElement(); |
1224 this.initialize.apply(this, arguments); |
1300 this.initialize.apply(this, arguments); |
1225 }; |
1301 }; |
1226 |
1302 |
1239 // jQuery delegate for element lookup, scoped to DOM elements within the |
1315 // jQuery delegate for element lookup, scoped to DOM elements within the |
1240 // current view. This should be preferred to global lookups where possible. |
1316 // current view. This should be preferred to global lookups where possible. |
1241 $: function(selector) { |
1317 $: function(selector) { |
1242 return this.$el.find(selector); |
1318 return this.$el.find(selector); |
1243 }, |
1319 }, |
1320 |
|
1321 // preinitialize is an empty function by default. You can override it with a function |
|
1322 // or object. preinitialize will run before any instantiation logic is run in the View |
|
1323 preinitialize: function(){}, |
|
1244 |
1324 |
1245 // Initialize is an empty function by default. Override it with your own |
1325 // Initialize is an empty function by default. Override it with your own |
1246 // initialization logic. |
1326 // initialization logic. |
1247 initialize: function(){}, |
1327 initialize: function(){}, |
1248 |
1328 |
1307 for (var key in events) { |
1387 for (var key in events) { |
1308 var method = events[key]; |
1388 var method = events[key]; |
1309 if (!_.isFunction(method)) method = this[method]; |
1389 if (!_.isFunction(method)) method = this[method]; |
1310 if (!method) continue; |
1390 if (!method) continue; |
1311 var match = key.match(delegateEventSplitter); |
1391 var match = key.match(delegateEventSplitter); |
1312 this.delegate(match[1], match[2], _.bind(method, this)); |
1392 this.delegate(match[1], match[2], method.bind(this)); |
1313 } |
1393 } |
1314 return this; |
1394 return this; |
1315 }, |
1395 }, |
1316 |
1396 |
1317 // Add a single event listener to the view's element (or a child element |
1397 // Add a single event listener to the view's element (or a child element |
1363 // subclasses using an alternative DOM manipulation API. |
1443 // subclasses using an alternative DOM manipulation API. |
1364 _setAttributes: function(attributes) { |
1444 _setAttributes: function(attributes) { |
1365 this.$el.attr(attributes); |
1445 this.$el.attr(attributes); |
1366 } |
1446 } |
1367 |
1447 |
1448 }); |
|
1449 |
|
1450 // Proxy Backbone class methods to Underscore functions, wrapping the model's |
|
1451 // `attributes` object or collection's `models` array behind the scenes. |
|
1452 // |
|
1453 // collection.filter(function(model) { return model.get('age') > 10 }); |
|
1454 // collection.each(this.addView); |
|
1455 // |
|
1456 // `Function#apply` can be slow so we use the method's arg count, if we know it. |
|
1457 var addMethod = function(base, length, method, attribute) { |
|
1458 switch (length) { |
|
1459 case 1: return function() { |
|
1460 return base[method](this[attribute]); |
|
1461 }; |
|
1462 case 2: return function(value) { |
|
1463 return base[method](this[attribute], value); |
|
1464 }; |
|
1465 case 3: return function(iteratee, context) { |
|
1466 return base[method](this[attribute], cb(iteratee, this), context); |
|
1467 }; |
|
1468 case 4: return function(iteratee, defaultVal, context) { |
|
1469 return base[method](this[attribute], cb(iteratee, this), defaultVal, context); |
|
1470 }; |
|
1471 default: return function() { |
|
1472 var args = slice.call(arguments); |
|
1473 args.unshift(this[attribute]); |
|
1474 return base[method].apply(base, args); |
|
1475 }; |
|
1476 } |
|
1477 }; |
|
1478 |
|
1479 var addUnderscoreMethods = function(Class, base, methods, attribute) { |
|
1480 _.each(methods, function(length, method) { |
|
1481 if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute); |
|
1482 }); |
|
1483 }; |
|
1484 |
|
1485 // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. |
|
1486 var cb = function(iteratee, instance) { |
|
1487 if (_.isFunction(iteratee)) return iteratee; |
|
1488 if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); |
|
1489 if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; |
|
1490 return iteratee; |
|
1491 }; |
|
1492 var modelMatcher = function(attrs) { |
|
1493 var matcher = _.matches(attrs); |
|
1494 return function(model) { |
|
1495 return matcher(model.attributes); |
|
1496 }; |
|
1497 }; |
|
1498 |
|
1499 // Underscore methods that we want to implement on the Collection. |
|
1500 // 90% of the core usefulness of Backbone Collections is actually implemented |
|
1501 // right here: |
|
1502 var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, |
|
1503 foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, |
|
1504 select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, |
|
1505 contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, |
|
1506 head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, |
|
1507 without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, |
|
1508 isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, |
|
1509 sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; |
|
1510 |
|
1511 |
|
1512 // Underscore methods that we want to implement on the Model, mapped to the |
|
1513 // number of arguments they take. |
|
1514 var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, |
|
1515 omit: 0, chain: 1, isEmpty: 1}; |
|
1516 |
|
1517 // Mix in each Underscore method as a proxy to `Collection#models`. |
|
1518 |
|
1519 _.each([ |
|
1520 [Collection, collectionMethods, 'models'], |
|
1521 [Model, modelMethods, 'attributes'] |
|
1522 ], function(config) { |
|
1523 var Base = config[0], |
|
1524 methods = config[1], |
|
1525 attribute = config[2]; |
|
1526 |
|
1527 Base.mixin = function(obj) { |
|
1528 var mappings = _.reduce(_.functions(obj), function(memo, name) { |
|
1529 memo[name] = 0; |
|
1530 return memo; |
|
1531 }, {}); |
|
1532 addUnderscoreMethods(Base, obj, mappings, attribute); |
|
1533 }; |
|
1534 |
|
1535 addUnderscoreMethods(Base, _, methods, attribute); |
|
1368 }); |
1536 }); |
1369 |
1537 |
1370 // Backbone.sync |
1538 // Backbone.sync |
1371 // ------------- |
1539 // ------------- |
1372 |
1540 |
1445 return xhr; |
1613 return xhr; |
1446 }; |
1614 }; |
1447 |
1615 |
1448 // Map from CRUD to HTTP for our default `Backbone.sync` implementation. |
1616 // Map from CRUD to HTTP for our default `Backbone.sync` implementation. |
1449 var methodMap = { |
1617 var methodMap = { |
1450 'create': 'POST', |
1618 create: 'POST', |
1451 'update': 'PUT', |
1619 update: 'PUT', |
1452 'patch': 'PATCH', |
1620 patch: 'PATCH', |
1453 'delete': 'DELETE', |
1621 delete: 'DELETE', |
1454 'read': 'GET' |
1622 read: 'GET' |
1455 }; |
1623 }; |
1456 |
1624 |
1457 // Set the default implementation of `Backbone.ajax` to proxy through to `$`. |
1625 // Set the default implementation of `Backbone.ajax` to proxy through to `$`. |
1458 // Override this if you'd like to use a different library. |
1626 // Override this if you'd like to use a different library. |
1459 Backbone.ajax = function() { |
1627 Backbone.ajax = function() { |
1465 |
1633 |
1466 // Routers map faux-URLs to actions, and fire events when routes are |
1634 // Routers map faux-URLs to actions, and fire events when routes are |
1467 // matched. Creating a new one sets its `routes` hash, if not set statically. |
1635 // matched. Creating a new one sets its `routes` hash, if not set statically. |
1468 var Router = Backbone.Router = function(options) { |
1636 var Router = Backbone.Router = function(options) { |
1469 options || (options = {}); |
1637 options || (options = {}); |
1638 this.preinitialize.apply(this, arguments); |
|
1470 if (options.routes) this.routes = options.routes; |
1639 if (options.routes) this.routes = options.routes; |
1471 this._bindRoutes(); |
1640 this._bindRoutes(); |
1472 this.initialize.apply(this, arguments); |
1641 this.initialize.apply(this, arguments); |
1473 }; |
1642 }; |
1474 |
1643 |
1479 var splatParam = /\*\w+/g; |
1648 var splatParam = /\*\w+/g; |
1480 var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; |
1649 var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; |
1481 |
1650 |
1482 // Set up all inheritable **Backbone.Router** properties and methods. |
1651 // Set up all inheritable **Backbone.Router** properties and methods. |
1483 _.extend(Router.prototype, Events, { |
1652 _.extend(Router.prototype, Events, { |
1653 |
|
1654 // preinitialize is an empty function by default. You can override it with a function |
|
1655 // or object. preinitialize will run before any instantiation logic is run in the Router. |
|
1656 preinitialize: function(){}, |
|
1484 |
1657 |
1485 // Initialize is an empty function by default. Override it with your own |
1658 // Initialize is an empty function by default. Override it with your own |
1486 // initialization logic. |
1659 // initialization logic. |
1487 initialize: function(){}, |
1660 initialize: function(){}, |
1488 |
1661 |
1537 |
1710 |
1538 // Convert a route string into a regular expression, suitable for matching |
1711 // Convert a route string into a regular expression, suitable for matching |
1539 // against the current location hash. |
1712 // against the current location hash. |
1540 _routeToRegExp: function(route) { |
1713 _routeToRegExp: function(route) { |
1541 route = route.replace(escapeRegExp, '\\$&') |
1714 route = route.replace(escapeRegExp, '\\$&') |
1542 .replace(optionalParam, '(?:$1)?') |
1715 .replace(optionalParam, '(?:$1)?') |
1543 .replace(namedParam, function(match, optional) { |
1716 .replace(namedParam, function(match, optional) { |
1544 return optional ? match : '([^/?]+)'; |
1717 return optional ? match : '([^/?]+)'; |
1545 }) |
1718 }) |
1546 .replace(splatParam, '([^?]*?)'); |
1719 .replace(splatParam, '([^?]*?)'); |
1547 return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); |
1720 return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); |
1548 }, |
1721 }, |
1549 |
1722 |
1550 // Given a route, and a URL fragment that it matches, return the array of |
1723 // Given a route, and a URL fragment that it matches, return the array of |
1551 // extracted decoded parameters. Empty or unmatched parameters will be |
1724 // extracted decoded parameters. Empty or unmatched parameters will be |
1569 // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) |
1742 // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) |
1570 // and URL fragments. If the browser supports neither (old IE, natch), |
1743 // and URL fragments. If the browser supports neither (old IE, natch), |
1571 // falls back to polling. |
1744 // falls back to polling. |
1572 var History = Backbone.History = function() { |
1745 var History = Backbone.History = function() { |
1573 this.handlers = []; |
1746 this.handlers = []; |
1574 this.checkUrl = _.bind(this.checkUrl, this); |
1747 this.checkUrl = this.checkUrl.bind(this); |
1575 |
1748 |
1576 // Ensure that `History` can be used outside of the browser. |
1749 // Ensure that `History` can be used outside of the browser. |
1577 if (typeof window !== 'undefined') { |
1750 if (typeof window !== 'undefined') { |
1578 this.location = window.location; |
1751 this.location = window.location; |
1579 this.history = window.history; |
1752 this.history = window.history; |
1810 if (fragment === '' || fragment.charAt(0) === '?') { |
1983 if (fragment === '' || fragment.charAt(0) === '?') { |
1811 rootPath = rootPath.slice(0, -1) || '/'; |
1984 rootPath = rootPath.slice(0, -1) || '/'; |
1812 } |
1985 } |
1813 var url = rootPath + fragment; |
1986 var url = rootPath + fragment; |
1814 |
1987 |
1815 // Strip the hash and decode for matching. |
1988 // Strip the fragment of the query and hash for matching. |
1816 fragment = this.decodeFragment(fragment.replace(pathStripper, '')); |
1989 fragment = fragment.replace(pathStripper, ''); |
1817 |
1990 |
1818 if (this.fragment === fragment) return; |
1991 // Decode for matching. |
1819 this.fragment = fragment; |
1992 var decodedFragment = this.decodeFragment(fragment); |
1993 |
|
1994 if (this.fragment === decodedFragment) return; |
|
1995 this.fragment = decodedFragment; |
|
1820 |
1996 |
1821 // If pushState is available, we use it to set the fragment as a real URL. |
1997 // If pushState is available, we use it to set the fragment as a real URL. |
1822 if (this._usePushState) { |
1998 if (this._usePushState) { |
1823 this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); |
1999 this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); |
1824 |
2000 |