Skip to content

Commit

Permalink
Merge branch '0.5.0'
Browse files Browse the repository at this point in the history
Conflicts:
	backbone.js
  • Loading branch information
PaulUithol committed May 26, 2011
2 parents 6b229ff + d7e3d5b commit dae2527
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 186 deletions.
167 changes: 105 additions & 62 deletions backbone.js
@@ -1,4 +1,4 @@
// Backbone.js 0.3.3
// Backbone.js 0.5.0-pre
// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
Expand All @@ -25,7 +25,7 @@
}

// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.3.3';
Backbone.VERSION = '0.5.0-pre';

// Require Underscore, if we're on the server, and it's not already present.
var _ = root._;
Expand Down Expand Up @@ -70,7 +70,7 @@
// Passing `"all"` will bind the callback to all events fired.
bind : function(ev, callback) {
var calls = this._callbacks || (this._callbacks = {});
var list = this._callbacks[ev] || (this._callbacks[ev] = []);
var list = calls[ev] || (calls[ev] = []);
list.push(callback);
return this;
},
Expand Down Expand Up @@ -410,13 +410,10 @@
// its models in sort order, as they're added and removed.
Backbone.Collection = function(models, options) {
options || (options = {});
if (options.comparator) {
this.comparator = options.comparator;
delete options.comparator;
}
if (options.comparator) this.comparator = options.comparator;
_.bindAll(this, '_onModelEvent', '_removeReference');
this._reset();
if (models) this.refresh(models, {silent: true});
if (models) this.reset(models, {silent: true});
this.initialize(models, options);
};

Expand Down Expand Up @@ -485,7 +482,7 @@
options || (options = {});
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
this.models = this.sortBy(this.comparator);
if (!options.silent) this.trigger('refresh', this, options);
if (!options.silent) this.trigger('reset', this, options);
return this;
},

Expand All @@ -495,27 +492,27 @@
},

// When you have more items than you want to add or remove individually,
// you can refresh the entire set with a new list of models, without firing
// any `added` or `removed` events. Fires `refresh` when finished.
refresh : function(models, options) {
// you can reset the entire set with a new list of models, without firing
// any `added` or `removed` events. Fires `reset` when finished.
reset : function(models, options) {
models || (models = []);
options || (options = {});
this.each(this._removeReference);
this._reset();
this.add(models, {silent: true});
if (!options.silent) this.trigger('refresh', this, options);
if (!options.silent) this.trigger('reset', this, options);
return this;
},

// Fetch the default set of models for this collection, refreshing the
// Fetch the default set of models for this collection, resetting the
// collection when they arrive. If `add: true` is passed, appends the
// models to the collection instead of refreshing.
// models to the collection instead of resetting.
fetch : function(options) {
options || (options = {});
var collection = this;
var success = options.success;
options.success = function(resp, status, xhr) {
collection[options.add ? 'add' : 'refresh'](collection.parse(resp, xhr), options);
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
options.error = wrapError(options.error, collection, options);
Expand All @@ -532,7 +529,7 @@
if (!model) return false;
var success = options.success;
options.success = function(nextModel, resp, xhr) {
coll.add(nextModel);
coll.add(nextModel, options);
if (success) success(nextModel, resp, xhr);
};
model.save(null, options);
Expand Down Expand Up @@ -584,7 +581,9 @@
if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
this._byId[model.id] = model;
this._byCid[model.cid] = model;
var index = this.comparator ? this.sortedIndex(model, this.comparator) : this.length;
var index = options.at != null ? options.at :
this.comparator ? this.sortedIndex(model, this.comparator) :
this.length;
this.models.splice(index, 0, model);
model.bind('all', this._onModelEvent);
this.length++;
Expand Down Expand Up @@ -646,12 +645,12 @@
};
});

// Backbone.Controller
// Backbone.Router
// -------------------

// Controllers map faux-URLs to actions, and fire events when routes are
// Routers map faux-URLs to actions, and fire events when routes are
// matched. Creating a new one sets its `routes` hash, if not set statically.
Backbone.Controller = function(options) {
Backbone.Router = function(options) {
options || (options = {});
if (options.routes) this.routes = options.routes;
this._bindRoutes();
Expand All @@ -664,8 +663,8 @@
var splatParam = /\*([\w\d]+)/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;

// Set up all inheritable **Backbone.Controller** properties and methods.
_.extend(Backbone.Controller.prototype, Backbone.Events, {
// Set up all inheritable **Backbone.Router** properties and methods.
_.extend(Backbone.Router.prototype, Backbone.Events, {

// Initialize is an empty function by default. Override it with your own
// initialization logic.
Expand All @@ -680,17 +679,24 @@
route : function(route, name, callback) {
Backbone.history || (Backbone.history = new Backbone.History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
Backbone.history.route(route, _.bind(function(hash) {
var args = this._extractParameters(route, hash);
Backbone.history.route(route, _.bind(function(fragment) {
var args = this._extractParameters(route, fragment);
callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
}, this));
},

// Simple proxy to `Backbone.history` to save a fragment into the history,
// without triggering routes.
saveLocation : function(hash) {
Backbone.history.saveLocation(hash);
saveLocation : function(fragment) {
Backbone.history.saveLocation(fragment);
},

// Simple proxy to `Backbone.history` to both save a fragment into the
// history and to then load the route at that fragment.
setLocation : function(fragment) {
Backbone.history.saveLocation(fragment);
Backbone.history.loadUrl(fragment);
},

// Bind all defined routes to `Backbone.history`. We have to reverse the
Expand Down Expand Up @@ -718,16 +724,16 @@

// Given a route, and a URL fragment that it matches, return the array of
// extracted parameters.
_extractParameters : function(route, hash) {
return route.exec(hash).slice(1);
_extractParameters : function(route, fragment) {
return route.exec(fragment).slice(1);
}

});

// Backbone.History
// ----------------

// Handles cross-browser history management, based on URL hashes. If the
// Handles cross-browser history management, based on URL fragments. If the
// browser does not support `onhashchange`, falls back to polling.
Backbone.History = function() {
this.handlers = [];
Expand All @@ -750,57 +756,87 @@
// twenty times a second.
interval: 50,

// Get the cross-browser normalized URL fragment.
getHash : function(loc) {
return (loc || window.location).hash.replace(hashStrip, '');
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
getFragment : function(fragment, forcePushState) {
if (fragment == null) {
if (this._hasPushState || forcePushState) {
fragment = window.location.pathname;
var search = window.location.search;
if (search) fragment += search;
if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length);
} else {
fragment = window.location.hash;
}
}
return fragment.replace(hashStrip, '');
},

// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start : function() {
start : function(options) {

// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
if (historyStarted) throw new Error("Backbone.history has already been started");
var hash = this.getHash();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
this.options = _.extend({}, {root: '/'}, this.options, options);
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
var fragment = this.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
if (oldIE) {
this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
this.saveLocation(hash);
this.saveLocation(fragment);
}
if ('onhashchange' in window && !oldIE) {

// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
$(window).bind('popstate', this.checkUrl);
} else if ('onhashchange' in window && !oldIE) {
$(window).bind('hashchange', this.checkUrl);
} else {
setInterval(this.checkUrl, this.interval);
}
this.hash = hash;

// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
this.fragment = fragment;
historyStarted = true;
return this.loadUrl();
var started = this.loadUrl() || this.loadUrl(window.location.hash);
if (this._wantsPushState && !this._hasPushState && window.location.pathname != this.options.root) {
this.fragment = this.getFragment(null, true);
window.location = this.options.root + '#' + this.fragment;
} else {
return started;
}
},

// Add a route to be tested when the hash changes. Routes added later may
// Add a route to be tested when the fragment changes. Routes added later may
// override previous routes.
route : function(route, callback) {
this.handlers.unshift({route : route, callback : callback});
},

// Checks the current URL to see if it has changed, and if it has,
// calls `loadUrl`, normalizing across the hidden iframe.
checkUrl : function() {
var hash = this.getHash();
if (hash == this.hash && this.iframe) hash = this.getHash(this.iframe.location);
if (hash == this.hash || hash == decodeURIComponent(this.hash)) return false;
if (this.iframe) this.saveLocation(hash);
this.hash = hash;
this.loadUrl();
checkUrl : function(e) {
var current = this.getFragment();
if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
if (this.iframe) this.saveLocation(current);
this.loadUrl() || this.loadUrl(window.location.hash);
},

// Attempt to load the current URL fragment. If a route succeeds with a
// match, returns `true`. If no defined routes matches the fragment,
// returns `false`.
loadUrl : function() {
var hash = this.hash;
loadUrl : function(fragmentOverride) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers, function(handler) {
if (handler.route.test(hash)) {
handler.callback(hash);
if (handler.route.test(fragment)) {
handler.callback(fragment);
return true;
}
});
Expand All @@ -810,13 +846,20 @@
// Save a fragment into the hash history. You are responsible for properly
// URL-encoding the fragment in advance. This does not trigger
// a `hashchange` event.
saveLocation : function(hash) {
hash = (hash || '').replace(hashStrip, '');
if (this.hash == hash) return;
window.location.hash = this.hash = hash;
if (this.iframe && (hash != this.getHash(this.iframe.location))) {
this.iframe.document.open().close();
this.iframe.location.hash = hash;
saveLocation : function(fragment) {
fragment = (fragment || '').replace(hashStrip, '');
if (this.fragment == fragment || this.fragment == decodeURIComponent(fragment)) return;
if (this._hasPushState) {
var loc = window.location;
if (fragment.indexOf(this.options.root) != 0) fragment = this.options.root + fragment;
this.fragment = fragment;
window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + fragment);
} else {
window.location.hash = this.fragment = fragment;
if (this.iframe && (fragment != this.getFragment(this.iframe.location.hash))) {
this.iframe.document.open().close();
this.iframe.location.hash = fragment;
}
}
}

Expand Down Expand Up @@ -956,7 +999,7 @@

// Set up inheritance for the model, collection, and view.
Backbone.Model.extend = Backbone.Collection.extend =
Backbone.Controller.extend = Backbone.View.extend = extend;
Backbone.Router.extend = Backbone.View.extend = extend;

// Map from CRUD to HTTP for our default `Backbone.sync` implementation.
var methodMap = {
Expand Down Expand Up @@ -1098,7 +1141,7 @@

// Helper function to escape a string for HTML rendering.
var escapeHTML = function(string) {
return string.replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
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;');
};

}).call(this);
2 changes: 1 addition & 1 deletion examples/todos/todos.js
Expand Up @@ -187,7 +187,7 @@ $(function(){
this.input = this.$("#new-todo");

Todos.bind('add', this.addOne);
Todos.bind('refresh', this.addAll);
Todos.bind('reset', this.addAll);
Todos.bind('all', this.render);

Todos.fetch();
Expand Down

0 comments on commit dae2527

Please sign in to comment.