3959 lines
171 KiB
HTML
3959 lines
171 KiB
HTML
<!DOCTYPE html>
|
|
|
|
<html>
|
|
<head>
|
|
<title>backbone.js</title>
|
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
|
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
|
|
<link rel="stylesheet" media="all" href="docco.css" />
|
|
</head>
|
|
<body>
|
|
<div id="container">
|
|
<div id="background"></div>
|
|
|
|
<ul class="sections">
|
|
|
|
<li id="title">
|
|
<div class="annotation">
|
|
<h1>backbone.js</h1>
|
|
</div>
|
|
</li>
|
|
|
|
|
|
|
|
<li id="section-1">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-1">¶</a>
|
|
</div>
|
|
<pre><code>Backbone.js 1.1.0
|
|
|
|
(c) 2010-2011 Jeremy Ashkenas, DocumentCloud Inc.
|
|
(c) 2011-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
|
Backbone may be freely distributed under the MIT license.
|
|
For all details and documentation:
|
|
http://backbonejs.org</code></pre>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre>(<span class="keyword">function</span>(){</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-2">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-2">¶</a>
|
|
</div>
|
|
<h2>Initial Setup</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-3">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-3">¶</a>
|
|
</div>
|
|
<p>Save a reference to the global object (<code>window</code> in the browser, <code>exports</code>
|
|
on the server).</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> root = <span class="keyword">this</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-4">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-4">¶</a>
|
|
</div>
|
|
<p>Save the previous value of the <code>Backbone</code> variable, so that it can be
|
|
restored later on, if <code>noConflict</code> is used.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> previousBackbone = root.Backbone;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-5">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-5">¶</a>
|
|
</div>
|
|
<p>Create local references to array methods we'll want to use later.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> array = [];
|
|
<span class="keyword">var</span> push = array.push;
|
|
<span class="keyword">var</span> slice = array.slice;
|
|
<span class="keyword">var</span> splice = array.splice;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-6">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-6">¶</a>
|
|
</div>
|
|
<p>The top-level namespace. All public Backbone classes and modules will
|
|
be attached to this. Exported for both the browser and the server.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Backbone;
|
|
<span class="keyword">if</span> (<span class="keyword">typeof</span> exports !== <span class="string">'undefined'</span>) {
|
|
Backbone = exports;
|
|
} <span class="keyword">else</span> {
|
|
Backbone = root.Backbone = {};
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-7">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-7">¶</a>
|
|
</div>
|
|
<p>Current version of the library. Keep in sync with <code>package.json</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Backbone.VERSION = <span class="string">'1.1.0'</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-8">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-8">¶</a>
|
|
</div>
|
|
<p>Require Underscore, if we're on the server, and it's not already present.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> _ = root._;
|
|
<span class="keyword">if</span> (!_ && (<span class="keyword">typeof</span> require !== <span class="string">'undefined'</span>)) _ = require(<span class="string">'underscore'</span>);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-9">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-9">¶</a>
|
|
</div>
|
|
<p>For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
|
|
the <code>$</code> variable.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Backbone.$ = root.jQuery || root.Zepto || root.ender || root.$;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-10">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-10">¶</a>
|
|
</div>
|
|
<p>Runs Backbone.js in <em>noConflict</em> mode, returning the <code>Backbone</code> variable
|
|
to its previous owner. Returns a reference to this Backbone object.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Backbone.noConflict = <span class="keyword">function</span>() {
|
|
root.Backbone = previousBackbone;
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-11">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-11">¶</a>
|
|
</div>
|
|
<p>Turn on <code>emulateHTTP</code> to support legacy HTTP servers. Setting this option
|
|
will fake <code>"PATCH"</code>, <code>"PUT"</code> and <code>"DELETE"</code> requests via the <code>_method</code> parameter and
|
|
set a <code>X-Http-Method-Override</code> header.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Backbone.emulateHTTP = <span class="literal">false</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-12">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-12">¶</a>
|
|
</div>
|
|
<p>Turn on <code>emulateJSON</code> to support legacy servers that can't deal with direct
|
|
<code>application/json</code> requests ... will encode the body as
|
|
<code>application/x-www-form-urlencoded</code> instead and will send the model in a
|
|
form param named <code>model</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Backbone.emulateJSON = <span class="literal">false</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-13">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-13">¶</a>
|
|
</div>
|
|
<h2>Backbone.Events</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-14">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-14">¶</a>
|
|
</div>
|
|
<p>A module that can be mixed in to <em>any object</em> in order to provide it with
|
|
custom events. You may bind with <code>on</code> or remove with <code>off</code> callback
|
|
functions to an event; <code>trigger</code>-ing an event fires all callbacks in
|
|
succession.</p>
|
|
<pre><code>var object = {};
|
|
_.extend(object, Backbone.Events);
|
|
object.on('expand', function(){ alert('expanded'); });
|
|
object.trigger('expand');</code></pre>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Events = Backbone.Events = {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-15">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-15">¶</a>
|
|
</div>
|
|
<p>Bind an event to a <code>callback</code> function. Passing <code>"all"</code> will bind
|
|
the callback to all events fired.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> on: <span class="keyword">function</span>(name, callback, context) {
|
|
<span class="keyword">if</span> (!eventsApi(<span class="keyword">this</span>, <span class="string">'on'</span>, name, [callback, context]) || !callback) <span class="keyword">return</span> <span class="keyword">this</span>;
|
|
<span class="keyword">this</span>._events || (<span class="keyword">this</span>._events = {});
|
|
<span class="keyword">var</span> events = <span class="keyword">this</span>._events[name] || (<span class="keyword">this</span>._events[name] = []);
|
|
events.push({callback: callback, context: context, ctx: context || <span class="keyword">this</span>});
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-16">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-16">¶</a>
|
|
</div>
|
|
<p>Bind an event to only be triggered a single time. After the first time
|
|
the callback is invoked, it will be removed.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> once: <span class="keyword">function</span>(name, callback, context) {
|
|
<span class="keyword">if</span> (!eventsApi(<span class="keyword">this</span>, <span class="string">'once'</span>, name, [callback, context]) || !callback) <span class="keyword">return</span> <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> self = <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> once = _.once(<span class="keyword">function</span>() {
|
|
self.off(name, once);
|
|
callback.apply(<span class="keyword">this</span>, arguments);
|
|
});
|
|
once._callback = callback;
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.on(name, once, context);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-17">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-17">¶</a>
|
|
</div>
|
|
<p>Remove one or many callbacks. If <code>context</code> is null, removes all
|
|
callbacks with that function. If <code>callback</code> is null, removes all
|
|
callbacks for the event. If <code>name</code> is null, removes all bound
|
|
callbacks for all events.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> off: <span class="keyword">function</span>(name, callback, context) {
|
|
<span class="keyword">var</span> retain, ev, events, names, i, l, j, k;
|
|
<span class="keyword">if</span> (!<span class="keyword">this</span>._events || !eventsApi(<span class="keyword">this</span>, <span class="string">'off'</span>, name, [callback, context])) <span class="keyword">return</span> <span class="keyword">this</span>;
|
|
<span class="keyword">if</span> (!name && !callback && !context) {
|
|
<span class="keyword">this</span>._events = {};
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
}
|
|
names = name ? [name] : _.keys(<span class="keyword">this</span>._events);
|
|
<span class="keyword">for</span> (i = <span class="number">0</span>, l = names.length; i < l; i++) {
|
|
name = names[i];
|
|
<span class="keyword">if</span> (events = <span class="keyword">this</span>._events[name]) {
|
|
<span class="keyword">this</span>._events[name] = retain = [];
|
|
<span class="keyword">if</span> (callback || context) {
|
|
<span class="keyword">for</span> (j = <span class="number">0</span>, k = events.length; j < k; j++) {
|
|
ev = events[j];
|
|
<span class="keyword">if</span> ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
|
|
(context && context !== ev.context)) {
|
|
retain.push(ev);
|
|
}
|
|
}
|
|
}
|
|
<span class="keyword">if</span> (!retain.length) <span class="keyword">delete</span> <span class="keyword">this</span>._events[name];
|
|
}
|
|
}
|
|
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-18">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-18">¶</a>
|
|
</div>
|
|
<p>Trigger one or many events, firing all bound callbacks. Callbacks are
|
|
passed the same arguments as <code>trigger</code> is, apart from the event name
|
|
(unless you're listening on <code>"all"</code>, which will cause your callback to
|
|
receive the true name of the event as the first argument).</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> trigger: <span class="keyword">function</span>(name) {
|
|
<span class="keyword">if</span> (!<span class="keyword">this</span>._events) <span class="keyword">return</span> <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> args = slice.call(arguments, <span class="number">1</span>);
|
|
<span class="keyword">if</span> (!eventsApi(<span class="keyword">this</span>, <span class="string">'trigger'</span>, name, args)) <span class="keyword">return</span> <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> events = <span class="keyword">this</span>._events[name];
|
|
<span class="keyword">var</span> allEvents = <span class="keyword">this</span>._events.all;
|
|
<span class="keyword">if</span> (events) triggerEvents(events, args);
|
|
<span class="keyword">if</span> (allEvents) triggerEvents(allEvents, arguments);
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-19">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-19">¶</a>
|
|
</div>
|
|
<p>Tell this object to stop listening to either specific events ... or
|
|
to every object it's currently listening to.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> stopListening: <span class="keyword">function</span>(obj, name, callback) {
|
|
<span class="keyword">var</span> listeningTo = <span class="keyword">this</span>._listeningTo;
|
|
<span class="keyword">if</span> (!listeningTo) <span class="keyword">return</span> <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> remove = !name && !callback;
|
|
<span class="keyword">if</span> (!callback && <span class="keyword">typeof</span> name === <span class="string">'object'</span>) callback = <span class="keyword">this</span>;
|
|
<span class="keyword">if</span> (obj) (listeningTo = {})[obj._listenId] = obj;
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> id <span class="keyword">in</span> listeningTo) {
|
|
obj = listeningTo[id];
|
|
obj.off(name, callback, <span class="keyword">this</span>);
|
|
<span class="keyword">if</span> (remove || _.isEmpty(obj._events)) <span class="keyword">delete</span> <span class="keyword">this</span>._listeningTo[id];
|
|
}
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
}
|
|
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-20">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-20">¶</a>
|
|
</div>
|
|
<p>Regular expression used to split event strings.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> eventSplitter = <span class="regexp">/\s+/</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-21">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-21">¶</a>
|
|
</div>
|
|
<p>Implement fancy features of the Events API such as multiple event
|
|
names <code>"change blur"</code> and jQuery-style event maps <code>{change: action}</code>
|
|
in terms of the existing API.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> eventsApi = <span class="keyword">function</span>(obj, action, name, rest) {
|
|
<span class="keyword">if</span> (!name) <span class="keyword">return</span> <span class="literal">true</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-22">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-22">¶</a>
|
|
</div>
|
|
<p>Handle event maps.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (<span class="keyword">typeof</span> name === <span class="string">'object'</span>) {
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> name) {
|
|
obj[action].apply(obj, [key, name[key]].concat(rest));
|
|
}
|
|
<span class="keyword">return</span> <span class="literal">false</span>;
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-23">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-23">¶</a>
|
|
</div>
|
|
<p>Handle space separated event names.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (eventSplitter.test(name)) {
|
|
<span class="keyword">var</span> names = name.split(eventSplitter);
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = names.length; i < l; i++) {
|
|
obj[action].apply(obj, [names[i]].concat(rest));
|
|
}
|
|
<span class="keyword">return</span> <span class="literal">false</span>;
|
|
}
|
|
|
|
<span class="keyword">return</span> <span class="literal">true</span>;
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-24">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-24">¶</a>
|
|
</div>
|
|
<p>A difficult-to-believe, but optimized internal dispatch function for
|
|
triggering events. Tries to keep the usual cases speedy (most internal
|
|
Backbone events have 3 arguments).</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> triggerEvents = <span class="keyword">function</span>(events, args) {
|
|
<span class="keyword">var</span> ev, i = -<span class="number">1</span>, l = events.length, a1 = args[<span class="number">0</span>], a2 = args[<span class="number">1</span>], a3 = args[<span class="number">2</span>];
|
|
<span class="keyword">switch</span> (args.length) {
|
|
<span class="keyword">case</span> <span class="number">0</span>: <span class="keyword">while</span> (++i < l) (ev = events[i]).callback.call(ev.ctx); <span class="keyword">return</span>;
|
|
<span class="keyword">case</span> <span class="number">1</span>: <span class="keyword">while</span> (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); <span class="keyword">return</span>;
|
|
<span class="keyword">case</span> <span class="number">2</span>: <span class="keyword">while</span> (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); <span class="keyword">return</span>;
|
|
<span class="keyword">case</span> <span class="number">3</span>: <span class="keyword">while</span> (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); <span class="keyword">return</span>;
|
|
<span class="keyword">default</span>: <span class="keyword">while</span> (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
|
|
}
|
|
};
|
|
|
|
<span class="keyword">var</span> listenMethods = {listenTo: <span class="string">'on'</span>, listenToOnce: <span class="string">'once'</span>};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-25">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-25">¶</a>
|
|
</div>
|
|
<p>Inversion-of-control versions of <code>on</code> and <code>once</code>. Tell <em>this</em> object to
|
|
listen to an event in another object ... keeping track of what it's
|
|
listening to.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.each(listenMethods, <span class="keyword">function</span>(implementation, method) {
|
|
Events[method] = <span class="keyword">function</span>(obj, name, callback) {
|
|
<span class="keyword">var</span> listeningTo = <span class="keyword">this</span>._listeningTo || (<span class="keyword">this</span>._listeningTo = {});
|
|
<span class="keyword">var</span> id = obj._listenId || (obj._listenId = _.uniqueId(<span class="string">'l'</span>));
|
|
listeningTo[id] = obj;
|
|
<span class="keyword">if</span> (!callback && <span class="keyword">typeof</span> name === <span class="string">'object'</span>) callback = <span class="keyword">this</span>;
|
|
obj[implementation](name, callback, <span class="keyword">this</span>);
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
};
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-26">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-26">¶</a>
|
|
</div>
|
|
<p>Aliases for backwards compatibility.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Events.bind = Events.on;
|
|
Events.unbind = Events.off;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-27">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-27">¶</a>
|
|
</div>
|
|
<p>Allow the <code>Backbone</code> object to serve as a global event bus, for folks who
|
|
want global "pubsub" in a convenient place.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.extend(Backbone, Events);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-28">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-28">¶</a>
|
|
</div>
|
|
<h2>Backbone.Model</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-29">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-29">¶</a>
|
|
</div>
|
|
<p>Backbone <strong>Models</strong> are the basic data object in the framework --
|
|
frequently representing a row in a table in a database on your server.
|
|
A discrete chunk of data and a bunch of useful, related methods for
|
|
performing computations and transformations on that data.</p>
|
|
<p>Create a new model with the specified attributes. A client id (<code>cid</code>)
|
|
is automatically generated and assigned for you.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Model = Backbone.Model = <span class="keyword">function</span>(attributes, options) {
|
|
<span class="keyword">var</span> attrs = attributes || {};
|
|
options || (options = {});
|
|
<span class="keyword">this</span>.cid = _.uniqueId(<span class="string">'c'</span>);
|
|
<span class="keyword">this</span>.attributes = {};
|
|
<span class="keyword">if</span> (options.collection) <span class="keyword">this</span>.collection = options.collection;
|
|
<span class="keyword">if</span> (options.parse) attrs = <span class="keyword">this</span>.parse(attrs, options) || {};
|
|
attrs = _.defaults({}, attrs, _.result(<span class="keyword">this</span>, <span class="string">'defaults'</span>));
|
|
<span class="keyword">this</span>.set(attrs, options);
|
|
<span class="keyword">this</span>.changed = {};
|
|
<span class="keyword">this</span>.initialize.apply(<span class="keyword">this</span>, arguments);
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-30">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-30">¶</a>
|
|
</div>
|
|
<p>Attach all inheritable methods to the Model prototype.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.extend(Model.prototype, Events, {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-31">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-31">¶</a>
|
|
</div>
|
|
<p>A hash of attributes whose current and previous value differ.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> changed: <span class="literal">null</span>,</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-32">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-32">¶</a>
|
|
</div>
|
|
<p>The value returned during the last failed validation.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> validationError: <span class="literal">null</span>,</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-33">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-33">¶</a>
|
|
</div>
|
|
<p>The default name for the JSON <code>id</code> attribute is <code>"id"</code>. MongoDB and
|
|
CouchDB users may want to set this to <code>"_id"</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> idAttribute: <span class="string">'id'</span>,</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-34">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-34">¶</a>
|
|
</div>
|
|
<p>Initialize is an empty function by default. Override it with your own
|
|
initialization logic.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>(){},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-35">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-35">¶</a>
|
|
</div>
|
|
<p>Return a copy of the model's <code>attributes</code> object.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> toJSON: <span class="keyword">function</span>(options) {
|
|
<span class="keyword">return</span> _.clone(<span class="keyword">this</span>.attributes);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-36">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-36">¶</a>
|
|
</div>
|
|
<p>Proxy <code>Backbone.sync</code> by default -- but override this if you need
|
|
custom syncing semantics for <em>this</em> particular model.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> sync: <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> Backbone.sync.apply(<span class="keyword">this</span>, arguments);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-37">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-37">¶</a>
|
|
</div>
|
|
<p>Get the value of an attribute.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> get: <span class="keyword">function</span>(attr) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.attributes[attr];
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-38">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-38">¶</a>
|
|
</div>
|
|
<p>Get the HTML-escaped value of an attribute.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> escape: <span class="keyword">function</span>(attr) {
|
|
<span class="keyword">return</span> _.escape(<span class="keyword">this</span>.get(attr));
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-39">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-39">¶</a>
|
|
</div>
|
|
<p>Returns <code>true</code> if the attribute contains a value that is not null
|
|
or undefined.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> has: <span class="keyword">function</span>(attr) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.get(attr) != <span class="literal">null</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-40">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-40">¶</a>
|
|
</div>
|
|
<p>Set a hash of model attributes on the object, firing <code>"change"</code>. This is
|
|
the core primitive operation of a model, updating the data and notifying
|
|
anyone who needs to know about the change in state. The heart of the beast.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> set: <span class="keyword">function</span>(key, val, options) {
|
|
<span class="keyword">var</span> attr, attrs, unset, changes, silent, changing, prev, current;
|
|
<span class="keyword">if</span> (key == <span class="literal">null</span>) <span class="keyword">return</span> <span class="keyword">this</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-41">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-41">¶</a>
|
|
</div>
|
|
<p>Handle both <code>"key", value</code> and <code>{key: value}</code> -style arguments.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (<span class="keyword">typeof</span> key === <span class="string">'object'</span>) {
|
|
attrs = key;
|
|
options = val;
|
|
} <span class="keyword">else</span> {
|
|
(attrs = {})[key] = val;
|
|
}
|
|
|
|
options || (options = {});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-42">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-42">¶</a>
|
|
</div>
|
|
<p>Run validation.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (!<span class="keyword">this</span>._validate(attrs, options)) <span class="keyword">return</span> <span class="literal">false</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-43">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-43">¶</a>
|
|
</div>
|
|
<p>Extract attributes and options.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> unset = options.unset;
|
|
silent = options.silent;
|
|
changes = [];
|
|
changing = <span class="keyword">this</span>._changing;
|
|
<span class="keyword">this</span>._changing = <span class="literal">true</span>;
|
|
|
|
<span class="keyword">if</span> (!changing) {
|
|
<span class="keyword">this</span>._previousAttributes = _.clone(<span class="keyword">this</span>.attributes);
|
|
<span class="keyword">this</span>.changed = {};
|
|
}
|
|
current = <span class="keyword">this</span>.attributes, prev = <span class="keyword">this</span>._previousAttributes;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-44">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-44">¶</a>
|
|
</div>
|
|
<p>Check for changes of <code>id</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (<span class="keyword">this</span>.idAttribute <span class="keyword">in</span> attrs) <span class="keyword">this</span>.id = attrs[<span class="keyword">this</span>.idAttribute];</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-45">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-45">¶</a>
|
|
</div>
|
|
<p>For each <code>set</code> attribute, update or delete the current value.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">for</span> (attr <span class="keyword">in</span> attrs) {
|
|
val = attrs[attr];
|
|
<span class="keyword">if</span> (!_.isEqual(current[attr], val)) changes.push(attr);
|
|
<span class="keyword">if</span> (!_.isEqual(prev[attr], val)) {
|
|
<span class="keyword">this</span>.changed[attr] = val;
|
|
} <span class="keyword">else</span> {
|
|
<span class="keyword">delete</span> <span class="keyword">this</span>.changed[attr];
|
|
}
|
|
unset ? <span class="keyword">delete</span> current[attr] : current[attr] = val;
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-46">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-46">¶</a>
|
|
</div>
|
|
<p>Trigger all relevant attribute changes.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (!silent) {
|
|
<span class="keyword">if</span> (changes.length) <span class="keyword">this</span>._pending = <span class="literal">true</span>;
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = changes.length; i < l; i++) {
|
|
<span class="keyword">this</span>.trigger(<span class="string">'change:'</span> + changes[i], <span class="keyword">this</span>, current[changes[i]], options);
|
|
}
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-47">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-47">¶</a>
|
|
</div>
|
|
<p>You might be wondering why there's a <code>while</code> loop here. Changes can
|
|
be recursively nested within <code>"change"</code> events.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (changing) <span class="keyword">return</span> <span class="keyword">this</span>;
|
|
<span class="keyword">if</span> (!silent) {
|
|
<span class="keyword">while</span> (<span class="keyword">this</span>._pending) {
|
|
<span class="keyword">this</span>._pending = <span class="literal">false</span>;
|
|
<span class="keyword">this</span>.trigger(<span class="string">'change'</span>, <span class="keyword">this</span>, options);
|
|
}
|
|
}
|
|
<span class="keyword">this</span>._pending = <span class="literal">false</span>;
|
|
<span class="keyword">this</span>._changing = <span class="literal">false</span>;
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-48">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-48">¶</a>
|
|
</div>
|
|
<p>Remove an attribute from the model, firing <code>"change"</code>. <code>unset</code> is a noop
|
|
if the attribute doesn't exist.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> unset: <span class="keyword">function</span>(attr, options) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.set(attr, <span class="keyword">void</span> <span class="number">0</span>, _.extend({}, options, {unset: <span class="literal">true</span>}));
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-49">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-49">¶</a>
|
|
</div>
|
|
<p>Clear all attributes on the model, firing <code>"change"</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> clear: <span class="keyword">function</span>(options) {
|
|
<span class="keyword">var</span> attrs = {};
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> <span class="keyword">this</span>.attributes) attrs[key] = <span class="keyword">void</span> <span class="number">0</span>;
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.set(attrs, _.extend({}, options, {unset: <span class="literal">true</span>}));
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-50">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-50">¶</a>
|
|
</div>
|
|
<p>Determine if the model has changed since the last <code>"change"</code> event.
|
|
If you specify an attribute name, determine if that attribute has changed.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> hasChanged: <span class="keyword">function</span>(attr) {
|
|
<span class="keyword">if</span> (attr == <span class="literal">null</span>) <span class="keyword">return</span> !_.isEmpty(<span class="keyword">this</span>.changed);
|
|
<span class="keyword">return</span> _.has(<span class="keyword">this</span>.changed, attr);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-51">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-51">¶</a>
|
|
</div>
|
|
<p>Return an object containing all the attributes that have changed, or
|
|
false if there are no changed attributes. Useful for determining what
|
|
parts of a view need to be updated and/or what attributes need to be
|
|
persisted to the server. Unset attributes will be set to undefined.
|
|
You can also pass an attributes object to diff against the model,
|
|
determining if there <em>would be</em> a change.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> changedAttributes: <span class="keyword">function</span>(diff) {
|
|
<span class="keyword">if</span> (!diff) <span class="keyword">return</span> <span class="keyword">this</span>.hasChanged() ? _.clone(<span class="keyword">this</span>.changed) : <span class="literal">false</span>;
|
|
<span class="keyword">var</span> val, changed = <span class="literal">false</span>;
|
|
<span class="keyword">var</span> old = <span class="keyword">this</span>._changing ? <span class="keyword">this</span>._previousAttributes : <span class="keyword">this</span>.attributes;
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> attr <span class="keyword">in</span> diff) {
|
|
<span class="keyword">if</span> (_.isEqual(old[attr], (val = diff[attr]))) <span class="keyword">continue</span>;
|
|
(changed || (changed = {}))[attr] = val;
|
|
}
|
|
<span class="keyword">return</span> changed;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-52">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-52">¶</a>
|
|
</div>
|
|
<p>Get the previous value of an attribute, recorded at the time the last
|
|
<code>"change"</code> event was fired.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> previous: <span class="keyword">function</span>(attr) {
|
|
<span class="keyword">if</span> (attr == <span class="literal">null</span> || !<span class="keyword">this</span>._previousAttributes) <span class="keyword">return</span> <span class="literal">null</span>;
|
|
<span class="keyword">return</span> <span class="keyword">this</span>._previousAttributes[attr];
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-53">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-53">¶</a>
|
|
</div>
|
|
<p>Get all of the attributes of the model at the time of the previous
|
|
<code>"change"</code> event.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> previousAttributes: <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> _.clone(<span class="keyword">this</span>._previousAttributes);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-54">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-54">¶</a>
|
|
</div>
|
|
<p>Fetch the model from the server. If the server's representation of the
|
|
model differs from its current attributes, they will be overridden,
|
|
triggering a <code>"change"</code> event.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> fetch: <span class="keyword">function</span>(options) {
|
|
options = options ? _.clone(options) : {};
|
|
<span class="keyword">if</span> (options.parse === <span class="keyword">void</span> <span class="number">0</span>) options.parse = <span class="literal">true</span>;
|
|
<span class="keyword">var</span> model = <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> success = options.success;
|
|
options.success = <span class="keyword">function</span>(resp) {
|
|
<span class="keyword">if</span> (!model.set(model.parse(resp, options), options)) <span class="keyword">return</span> <span class="literal">false</span>;
|
|
<span class="keyword">if</span> (success) success(model, resp, options);
|
|
model.trigger(<span class="string">'sync'</span>, model, resp, options);
|
|
};
|
|
wrapError(<span class="keyword">this</span>, options);
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.sync(<span class="string">'read'</span>, <span class="keyword">this</span>, options);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-55">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-55">¶</a>
|
|
</div>
|
|
<p>Set a hash of model attributes, and sync the model to the server.
|
|
If the server returns an attributes hash that differs, the model's
|
|
state will be <code>set</code> again.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> save: <span class="keyword">function</span>(key, val, options) {
|
|
<span class="keyword">var</span> attrs, method, xhr, attributes = <span class="keyword">this</span>.attributes;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-56">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-56">¶</a>
|
|
</div>
|
|
<p>Handle both <code>"key", value</code> and <code>{key: value}</code> -style arguments.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (key == <span class="literal">null</span> || <span class="keyword">typeof</span> key === <span class="string">'object'</span>) {
|
|
attrs = key;
|
|
options = val;
|
|
} <span class="keyword">else</span> {
|
|
(attrs = {})[key] = val;
|
|
}
|
|
|
|
options = _.extend({validate: <span class="literal">true</span>}, options);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-57">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-57">¶</a>
|
|
</div>
|
|
<p>If we're not waiting and attributes exist, save acts as
|
|
<code>set(attr).save(null, opts)</code> with validation. Otherwise, check if
|
|
the model will be valid when the attributes, if any, are set.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (attrs && !options.wait) {
|
|
<span class="keyword">if</span> (!<span class="keyword">this</span>.set(attrs, options)) <span class="keyword">return</span> <span class="literal">false</span>;
|
|
} <span class="keyword">else</span> {
|
|
<span class="keyword">if</span> (!<span class="keyword">this</span>._validate(attrs, options)) <span class="keyword">return</span> <span class="literal">false</span>;
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-58">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-58">¶</a>
|
|
</div>
|
|
<p>Set temporary attributes if <code>{wait: true}</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (attrs && options.wait) {
|
|
<span class="keyword">this</span>.attributes = _.extend({}, attributes, attrs);
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-59">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-59">¶</a>
|
|
</div>
|
|
<p>After a successful server-side save, the client is (optionally)
|
|
updated with the server-side state.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (options.parse === <span class="keyword">void</span> <span class="number">0</span>) options.parse = <span class="literal">true</span>;
|
|
<span class="keyword">var</span> model = <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> success = options.success;
|
|
options.success = <span class="keyword">function</span>(resp) {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-60">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-60">¶</a>
|
|
</div>
|
|
<p>Ensure attributes are restored during synchronous saves.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> model.attributes = attributes;
|
|
<span class="keyword">var</span> serverAttrs = model.parse(resp, options);
|
|
<span class="keyword">if</span> (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs);
|
|
<span class="keyword">if</span> (_.isObject(serverAttrs) && !model.set(serverAttrs, options)) {
|
|
<span class="keyword">return</span> <span class="literal">false</span>;
|
|
}
|
|
<span class="keyword">if</span> (success) success(model, resp, options);
|
|
model.trigger(<span class="string">'sync'</span>, model, resp, options);
|
|
};
|
|
wrapError(<span class="keyword">this</span>, options);
|
|
|
|
method = <span class="keyword">this</span>.isNew() ? <span class="string">'create'</span> : (options.patch ? <span class="string">'patch'</span> : <span class="string">'update'</span>);
|
|
<span class="keyword">if</span> (method === <span class="string">'patch'</span>) options.attrs = attrs;
|
|
xhr = <span class="keyword">this</span>.sync(method, <span class="keyword">this</span>, options);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-61">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-61">¶</a>
|
|
</div>
|
|
<p>Restore attributes.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (attrs && options.wait) <span class="keyword">this</span>.attributes = attributes;
|
|
|
|
<span class="keyword">return</span> xhr;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-62">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-62">¶</a>
|
|
</div>
|
|
<p>Destroy this model on the server if it was already persisted.
|
|
Optimistically removes the model from its collection, if it has one.
|
|
If <code>wait: true</code> is passed, waits for the server to respond before removal.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> destroy: <span class="keyword">function</span>(options) {
|
|
options = options ? _.clone(options) : {};
|
|
<span class="keyword">var</span> model = <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> success = options.success;
|
|
|
|
<span class="keyword">var</span> destroy = <span class="keyword">function</span>() {
|
|
model.trigger(<span class="string">'destroy'</span>, model, model.collection, options);
|
|
};
|
|
|
|
options.success = <span class="keyword">function</span>(resp) {
|
|
<span class="keyword">if</span> (options.wait || model.isNew()) destroy();
|
|
<span class="keyword">if</span> (success) success(model, resp, options);
|
|
<span class="keyword">if</span> (!model.isNew()) model.trigger(<span class="string">'sync'</span>, model, resp, options);
|
|
};
|
|
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>.isNew()) {
|
|
options.success();
|
|
<span class="keyword">return</span> <span class="literal">false</span>;
|
|
}
|
|
wrapError(<span class="keyword">this</span>, options);
|
|
|
|
<span class="keyword">var</span> xhr = <span class="keyword">this</span>.sync(<span class="string">'delete'</span>, <span class="keyword">this</span>, options);
|
|
<span class="keyword">if</span> (!options.wait) destroy();
|
|
<span class="keyword">return</span> xhr;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-63">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-63">¶</a>
|
|
</div>
|
|
<p>Default URL for the model's representation on the server -- if you're
|
|
using Backbone's restful methods, override this to change the endpoint
|
|
that will be called.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> url: <span class="keyword">function</span>() {
|
|
<span class="keyword">var</span> base = _.result(<span class="keyword">this</span>, <span class="string">'urlRoot'</span>) || _.result(<span class="keyword">this</span>.collection, <span class="string">'url'</span>) || urlError();
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>.isNew()) <span class="keyword">return</span> base;
|
|
<span class="keyword">return</span> base + (base.charAt(base.length - <span class="number">1</span>) === <span class="string">'/'</span> ? <span class="string">''</span> : <span class="string">'/'</span>) + encodeURIComponent(<span class="keyword">this</span>.id);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-64">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-64">¶</a>
|
|
</div>
|
|
<p><strong>parse</strong> converts a response into the hash of attributes to be <code>set</code> on
|
|
the model. The default implementation is just to pass the response along.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> parse: <span class="keyword">function</span>(resp, options) {
|
|
<span class="keyword">return</span> resp;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-65">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-65">¶</a>
|
|
</div>
|
|
<p>Create a new model with identical attributes to this one.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> clone: <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> <span class="keyword">new</span> <span class="keyword">this</span>.constructor(<span class="keyword">this</span>.attributes);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-66">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-66">¶</a>
|
|
</div>
|
|
<p>A model is new if it has never been saved to the server, and lacks an id.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> isNew: <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.id == <span class="literal">null</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-67">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-67">¶</a>
|
|
</div>
|
|
<p>Check if the model is currently in a valid state.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> isValid: <span class="keyword">function</span>(options) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>._validate({}, _.extend(options || {}, { validate: <span class="literal">true</span> }));
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-68">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-68">¶</a>
|
|
</div>
|
|
<p>Run validation against the next complete set of model attributes,
|
|
returning <code>true</code> if all is well. Otherwise, fire an <code>"invalid"</code> event.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _validate: <span class="keyword">function</span>(attrs, options) {
|
|
<span class="keyword">if</span> (!options.validate || !<span class="keyword">this</span>.validate) <span class="keyword">return</span> <span class="literal">true</span>;
|
|
attrs = _.extend({}, <span class="keyword">this</span>.attributes, attrs);
|
|
<span class="keyword">var</span> error = <span class="keyword">this</span>.validationError = <span class="keyword">this</span>.validate(attrs, options) || <span class="literal">null</span>;
|
|
<span class="keyword">if</span> (!error) <span class="keyword">return</span> <span class="literal">true</span>;
|
|
<span class="keyword">this</span>.trigger(<span class="string">'invalid'</span>, <span class="keyword">this</span>, error, _.extend(options, {validationError: error}));
|
|
<span class="keyword">return</span> <span class="literal">false</span>;
|
|
}
|
|
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-69">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-69">¶</a>
|
|
</div>
|
|
<p>Underscore methods that we want to implement on the Model.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> modelMethods = [<span class="string">'keys'</span>, <span class="string">'values'</span>, <span class="string">'pairs'</span>, <span class="string">'invert'</span>, <span class="string">'pick'</span>, <span class="string">'omit'</span>];</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-70">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-70">¶</a>
|
|
</div>
|
|
<p>Mix in each Underscore method as a proxy to <code>Model#attributes</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.each(modelMethods, <span class="keyword">function</span>(method) {
|
|
Model.prototype[method] = <span class="keyword">function</span>() {
|
|
<span class="keyword">var</span> args = slice.call(arguments);
|
|
args.unshift(<span class="keyword">this</span>.attributes);
|
|
<span class="keyword">return</span> _[method].apply(_, args);
|
|
};
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-71">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-71">¶</a>
|
|
</div>
|
|
<h2>Backbone.Collection</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-72">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-72">¶</a>
|
|
</div>
|
|
<p>If models tend to represent a single row of data, a Backbone Collection is
|
|
more analagous to a table full of data ... or a small slice or page of that
|
|
table, or a collection of rows that belong together for a particular reason
|
|
-- all of the messages in this particular folder, all of the documents
|
|
belonging to this particular author, and so on. Collections maintain
|
|
indexes of their models, both in order, and for lookup by <code>id</code>.</p>
|
|
<p>Create a new <strong>Collection</strong>, perhaps to contain a specific type of <code>model</code>.
|
|
If a <code>comparator</code> is specified, the Collection will maintain
|
|
its models in sort order, as they're added and removed.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Collection = Backbone.Collection = <span class="keyword">function</span>(models, options) {
|
|
options || (options = {});
|
|
<span class="keyword">if</span> (options.model) <span class="keyword">this</span>.model = options.model;
|
|
<span class="keyword">if</span> (options.comparator !== <span class="keyword">void</span> <span class="number">0</span>) <span class="keyword">this</span>.comparator = options.comparator;
|
|
<span class="keyword">this</span>._reset();
|
|
<span class="keyword">this</span>.initialize.apply(<span class="keyword">this</span>, arguments);
|
|
<span class="keyword">if</span> (models) <span class="keyword">this</span>.reset(models, _.extend({silent: <span class="literal">true</span>}, options));
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-73">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-73">¶</a>
|
|
</div>
|
|
<p>Default options for <code>Collection#set</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> setOptions = {add: <span class="literal">true</span>, remove: <span class="literal">true</span>, merge: <span class="literal">true</span>};
|
|
<span class="keyword">var</span> addOptions = {add: <span class="literal">true</span>, remove: <span class="literal">false</span>};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-74">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-74">¶</a>
|
|
</div>
|
|
<p>Define the Collection's inheritable methods.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.extend(Collection.prototype, Events, {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-75">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-75">¶</a>
|
|
</div>
|
|
<p>The default model for a collection is just a <strong>Backbone.Model</strong>.
|
|
This should be overridden in most cases.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> model: Model,</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-76">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-76">¶</a>
|
|
</div>
|
|
<p>Initialize is an empty function by default. Override it with your own
|
|
initialization logic.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>(){},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-77">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-77">¶</a>
|
|
</div>
|
|
<p>The JSON representation of a Collection is an array of the
|
|
models' attributes.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> toJSON: <span class="keyword">function</span>(options) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.map(<span class="keyword">function</span>(model){ <span class="keyword">return</span> model.toJSON(options); });
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-78">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-78">¶</a>
|
|
</div>
|
|
<p>Proxy <code>Backbone.sync</code> by default.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> sync: <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> Backbone.sync.apply(<span class="keyword">this</span>, arguments);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-79">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-79">¶</a>
|
|
</div>
|
|
<p>Add a model, or list of models to the set.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> add: <span class="keyword">function</span>(models, options) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.set(models, _.extend({merge: <span class="literal">false</span>}, options, addOptions));
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-80">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-80">¶</a>
|
|
</div>
|
|
<p>Remove a model, or a list of models from the set.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> remove: <span class="keyword">function</span>(models, options) {
|
|
<span class="keyword">var</span> singular = !_.isArray(models);
|
|
models = singular ? [models] : _.clone(models);
|
|
options || (options = {});
|
|
<span class="keyword">var</span> i, l, index, model;
|
|
<span class="keyword">for</span> (i = <span class="number">0</span>, l = models.length; i < l; i++) {
|
|
model = models[i] = <span class="keyword">this</span>.get(models[i]);
|
|
<span class="keyword">if</span> (!model) <span class="keyword">continue</span>;
|
|
<span class="keyword">delete</span> <span class="keyword">this</span>._byId[model.id];
|
|
<span class="keyword">delete</span> <span class="keyword">this</span>._byId[model.cid];
|
|
index = <span class="keyword">this</span>.indexOf(model);
|
|
<span class="keyword">this</span>.models.splice(index, <span class="number">1</span>);
|
|
<span class="keyword">this</span>.length--;
|
|
<span class="keyword">if</span> (!options.silent) {
|
|
options.index = index;
|
|
model.trigger(<span class="string">'remove'</span>, model, <span class="keyword">this</span>, options);
|
|
}
|
|
<span class="keyword">this</span>._removeReference(model);
|
|
}
|
|
<span class="keyword">return</span> singular ? models[<span class="number">0</span>] : models;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-81">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-81">¶</a>
|
|
</div>
|
|
<p>Update a collection by <code>set</code>-ing a new list of models, adding new ones,
|
|
removing models that are no longer present, and merging models that
|
|
already exist in the collection, as necessary. Similar to <strong>Model#set</strong>,
|
|
the core operation for updating the data contained by the collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> set: <span class="keyword">function</span>(models, options) {
|
|
options = _.defaults({}, options, setOptions);
|
|
<span class="keyword">if</span> (options.parse) models = <span class="keyword">this</span>.parse(models, options);
|
|
<span class="keyword">var</span> singular = !_.isArray(models);
|
|
models = singular ? (models ? [models] : []) : _.clone(models);
|
|
<span class="keyword">var</span> i, l, id, model, attrs, existing, sort;
|
|
<span class="keyword">var</span> at = options.at;
|
|
<span class="keyword">var</span> targetModel = <span class="keyword">this</span>.model;
|
|
<span class="keyword">var</span> sortable = <span class="keyword">this</span>.comparator && (at == <span class="literal">null</span>) && options.sort !== <span class="literal">false</span>;
|
|
<span class="keyword">var</span> sortAttr = _.isString(<span class="keyword">this</span>.comparator) ? <span class="keyword">this</span>.comparator : <span class="literal">null</span>;
|
|
<span class="keyword">var</span> toAdd = [], toRemove = [], modelMap = {};
|
|
<span class="keyword">var</span> add = options.add, merge = options.merge, remove = options.remove;
|
|
<span class="keyword">var</span> order = !sortable && add && remove ? [] : <span class="literal">false</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-82">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-82">¶</a>
|
|
</div>
|
|
<p>Turn bare objects into model references, and prevent invalid models
|
|
from being added.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">for</span> (i = <span class="number">0</span>, l = models.length; i < l; i++) {
|
|
attrs = models[i];
|
|
<span class="keyword">if</span> (attrs <span class="keyword">instanceof</span> Model) {
|
|
id = model = attrs;
|
|
} <span class="keyword">else</span> {
|
|
id = attrs[targetModel.prototype.idAttribute];
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-83">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-83">¶</a>
|
|
</div>
|
|
<p>If a duplicate is found, prevent it from being added and
|
|
optionally merge it into the existing model.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (existing = <span class="keyword">this</span>.get(id)) {
|
|
<span class="keyword">if</span> (remove) modelMap[existing.cid] = <span class="literal">true</span>;
|
|
<span class="keyword">if</span> (merge) {
|
|
attrs = attrs === model ? model.attributes : attrs;
|
|
<span class="keyword">if</span> (options.parse) attrs = existing.parse(attrs, options);
|
|
existing.set(attrs, options);
|
|
<span class="keyword">if</span> (sortable && !sort && existing.hasChanged(sortAttr)) sort = <span class="literal">true</span>;
|
|
}
|
|
models[i] = existing;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-84">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-84">¶</a>
|
|
</div>
|
|
<p>If this is a new, valid model, push it to the <code>toAdd</code> list.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> } <span class="keyword">else</span> <span class="keyword">if</span> (add) {
|
|
model = models[i] = <span class="keyword">this</span>._prepareModel(attrs, options);
|
|
<span class="keyword">if</span> (!model) <span class="keyword">continue</span>;
|
|
toAdd.push(model);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-85">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-85">¶</a>
|
|
</div>
|
|
<p>Listen to added models' events, and index models for lookup by
|
|
<code>id</code> and by <code>cid</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> model.on(<span class="string">'all'</span>, <span class="keyword">this</span>._onModelEvent, <span class="keyword">this</span>);
|
|
<span class="keyword">this</span>._byId[model.cid] = model;
|
|
<span class="keyword">if</span> (model.id != <span class="literal">null</span>) <span class="keyword">this</span>._byId[model.id] = model;
|
|
}
|
|
<span class="keyword">if</span> (order) order.push(existing || model);
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-86">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-86">¶</a>
|
|
</div>
|
|
<p>Remove nonexistent models if appropriate.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (remove) {
|
|
<span class="keyword">for</span> (i = <span class="number">0</span>, l = <span class="keyword">this</span>.length; i < l; ++i) {
|
|
<span class="keyword">if</span> (!modelMap[(model = <span class="keyword">this</span>.models[i]).cid]) toRemove.push(model);
|
|
}
|
|
<span class="keyword">if</span> (toRemove.length) <span class="keyword">this</span>.remove(toRemove, options);
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-87">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-87">¶</a>
|
|
</div>
|
|
<p>See if sorting is needed, update <code>length</code> and splice in new models.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (toAdd.length || (order && order.length)) {
|
|
<span class="keyword">if</span> (sortable) sort = <span class="literal">true</span>;
|
|
<span class="keyword">this</span>.length += toAdd.length;
|
|
<span class="keyword">if</span> (at != <span class="literal">null</span>) {
|
|
<span class="keyword">for</span> (i = <span class="number">0</span>, l = toAdd.length; i < l; i++) {
|
|
<span class="keyword">this</span>.models.splice(at + i, <span class="number">0</span>, toAdd[i]);
|
|
}
|
|
} <span class="keyword">else</span> {
|
|
<span class="keyword">if</span> (order) <span class="keyword">this</span>.models.length = <span class="number">0</span>;
|
|
<span class="keyword">var</span> orderedModels = order || toAdd;
|
|
<span class="keyword">for</span> (i = <span class="number">0</span>, l = orderedModels.length; i < l; i++) {
|
|
<span class="keyword">this</span>.models.push(orderedModels[i]);
|
|
}
|
|
}
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-88">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-88">¶</a>
|
|
</div>
|
|
<p>Silently sort the collection if appropriate.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (sort) <span class="keyword">this</span>.sort({silent: <span class="literal">true</span>});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-89">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-89">¶</a>
|
|
</div>
|
|
<p>Unless silenced, it's time to fire all appropriate add/sort events.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (!options.silent) {
|
|
<span class="keyword">for</span> (i = <span class="number">0</span>, l = toAdd.length; i < l; i++) {
|
|
(model = toAdd[i]).trigger(<span class="string">'add'</span>, model, <span class="keyword">this</span>, options);
|
|
}
|
|
<span class="keyword">if</span> (sort || (order && order.length)) <span class="keyword">this</span>.trigger(<span class="string">'sort'</span>, <span class="keyword">this</span>, options);
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-90">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-90">¶</a>
|
|
</div>
|
|
<p>Return the added (or merged) model (or models).</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">return</span> singular ? models[<span class="number">0</span>] : models;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-91">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-91">¶</a>
|
|
</div>
|
|
<p>When you have more items than you want to add or remove individually,
|
|
you can reset the entire set with a new list of models, without firing
|
|
any granular <code>add</code> or <code>remove</code> events. Fires <code>reset</code> when finished.
|
|
Useful for bulk operations and optimizations.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> reset: <span class="keyword">function</span>(models, options) {
|
|
options || (options = {});
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = <span class="keyword">this</span>.models.length; i < l; i++) {
|
|
<span class="keyword">this</span>._removeReference(<span class="keyword">this</span>.models[i]);
|
|
}
|
|
options.previousModels = <span class="keyword">this</span>.models;
|
|
<span class="keyword">this</span>._reset();
|
|
models = <span class="keyword">this</span>.add(models, _.extend({silent: <span class="literal">true</span>}, options));
|
|
<span class="keyword">if</span> (!options.silent) <span class="keyword">this</span>.trigger(<span class="string">'reset'</span>, <span class="keyword">this</span>, options);
|
|
<span class="keyword">return</span> models;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-92">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-92">¶</a>
|
|
</div>
|
|
<p>Add a model to the end of the collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> push: <span class="keyword">function</span>(model, options) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.add(model, _.extend({at: <span class="keyword">this</span>.length}, options));
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-93">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-93">¶</a>
|
|
</div>
|
|
<p>Remove a model from the end of the collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> pop: <span class="keyword">function</span>(options) {
|
|
<span class="keyword">var</span> model = <span class="keyword">this</span>.at(<span class="keyword">this</span>.length - <span class="number">1</span>);
|
|
<span class="keyword">this</span>.remove(model, options);
|
|
<span class="keyword">return</span> model;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-94">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-94">¶</a>
|
|
</div>
|
|
<p>Add a model to the beginning of the collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> unshift: <span class="keyword">function</span>(model, options) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.add(model, _.extend({at: <span class="number">0</span>}, options));
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-95">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-95">¶</a>
|
|
</div>
|
|
<p>Remove a model from the beginning of the collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> shift: <span class="keyword">function</span>(options) {
|
|
<span class="keyword">var</span> model = <span class="keyword">this</span>.at(<span class="number">0</span>);
|
|
<span class="keyword">this</span>.remove(model, options);
|
|
<span class="keyword">return</span> model;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-96">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-96">¶</a>
|
|
</div>
|
|
<p>Slice out a sub-array of models from the collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> slice: <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> slice.apply(<span class="keyword">this</span>.models, arguments);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-97">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-97">¶</a>
|
|
</div>
|
|
<p>Get a model from the set by id.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> get: <span class="keyword">function</span>(obj) {
|
|
<span class="keyword">if</span> (obj == <span class="literal">null</span>) <span class="keyword">return</span> <span class="keyword">void</span> <span class="number">0</span>;
|
|
<span class="keyword">return</span> <span class="keyword">this</span>._byId[obj.id] || <span class="keyword">this</span>._byId[obj.cid] || <span class="keyword">this</span>._byId[obj];
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-98">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-98">¶</a>
|
|
</div>
|
|
<p>Get the model at the given index.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> at: <span class="keyword">function</span>(index) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.models[index];
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-99">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-99">¶</a>
|
|
</div>
|
|
<p>Return models with matching attributes. Useful for simple cases of
|
|
<code>filter</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> where: <span class="keyword">function</span>(attrs, first) {
|
|
<span class="keyword">if</span> (_.isEmpty(attrs)) <span class="keyword">return</span> first ? <span class="keyword">void</span> <span class="number">0</span> : [];
|
|
<span class="keyword">return</span> <span class="keyword">this</span>[first ? <span class="string">'find'</span> : <span class="string">'filter'</span>](<span class="keyword">function</span>(model) {
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> attrs) {
|
|
<span class="keyword">if</span> (attrs[key] !== model.get(key)) <span class="keyword">return</span> <span class="literal">false</span>;
|
|
}
|
|
<span class="keyword">return</span> <span class="literal">true</span>;
|
|
});
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-100">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-100">¶</a>
|
|
</div>
|
|
<p>Return the first model with matching attributes. Useful for simple cases
|
|
of <code>find</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> findWhere: <span class="keyword">function</span>(attrs) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.where(attrs, <span class="literal">true</span>);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-101">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-101">¶</a>
|
|
</div>
|
|
<p>Force the collection to re-sort itself. You don't need to call this under
|
|
normal circumstances, as the set will maintain sort order as each item
|
|
is added.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> sort: <span class="keyword">function</span>(options) {
|
|
<span class="keyword">if</span> (!<span class="keyword">this</span>.comparator) <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">'Cannot sort a set without a comparator'</span>);
|
|
options || (options = {});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-102">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-102">¶</a>
|
|
</div>
|
|
<p>Run sort based on type of <code>comparator</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (_.isString(<span class="keyword">this</span>.comparator) || <span class="keyword">this</span>.comparator.length === <span class="number">1</span>) {
|
|
<span class="keyword">this</span>.models = <span class="keyword">this</span>.sortBy(<span class="keyword">this</span>.comparator, <span class="keyword">this</span>);
|
|
} <span class="keyword">else</span> {
|
|
<span class="keyword">this</span>.models.sort(_.bind(<span class="keyword">this</span>.comparator, <span class="keyword">this</span>));
|
|
}
|
|
|
|
<span class="keyword">if</span> (!options.silent) <span class="keyword">this</span>.trigger(<span class="string">'sort'</span>, <span class="keyword">this</span>, options);
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-103">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-103">¶</a>
|
|
</div>
|
|
<p>Pluck an attribute from each model in the collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> pluck: <span class="keyword">function</span>(attr) {
|
|
<span class="keyword">return</span> _.invoke(<span class="keyword">this</span>.models, <span class="string">'get'</span>, attr);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-104">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-104">¶</a>
|
|
</div>
|
|
<p>Fetch the default set of models for this collection, resetting the
|
|
collection when they arrive. If <code>reset: true</code> is passed, the response
|
|
data will be passed through the <code>reset</code> method instead of <code>set</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> fetch: <span class="keyword">function</span>(options) {
|
|
options = options ? _.clone(options) : {};
|
|
<span class="keyword">if</span> (options.parse === <span class="keyword">void</span> <span class="number">0</span>) options.parse = <span class="literal">true</span>;
|
|
<span class="keyword">var</span> success = options.success;
|
|
<span class="keyword">var</span> collection = <span class="keyword">this</span>;
|
|
options.success = <span class="keyword">function</span>(resp) {
|
|
<span class="keyword">var</span> method = options.reset ? <span class="string">'reset'</span> : <span class="string">'set'</span>;
|
|
collection[method](resp, options);
|
|
<span class="keyword">if</span> (success) success(collection, resp, options);
|
|
collection.trigger(<span class="string">'sync'</span>, collection, resp, options);
|
|
};
|
|
wrapError(<span class="keyword">this</span>, options);
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.sync(<span class="string">'read'</span>, <span class="keyword">this</span>, options);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-105">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-105">¶</a>
|
|
</div>
|
|
<p>Create a new instance of a model in this collection. Add the model to the
|
|
collection immediately, unless <code>wait: true</code> is passed, in which case we
|
|
wait for the server to agree.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> create: <span class="keyword">function</span>(model, options) {
|
|
options = options ? _.clone(options) : {};
|
|
<span class="keyword">if</span> (!(model = <span class="keyword">this</span>._prepareModel(model, options))) <span class="keyword">return</span> <span class="literal">false</span>;
|
|
<span class="keyword">if</span> (!options.wait) <span class="keyword">this</span>.add(model, options);
|
|
<span class="keyword">var</span> collection = <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> success = options.success;
|
|
options.success = <span class="keyword">function</span>(model, resp, options) {
|
|
<span class="keyword">if</span> (options.wait) collection.add(model, options);
|
|
<span class="keyword">if</span> (success) success(model, resp, options);
|
|
};
|
|
model.save(<span class="literal">null</span>, options);
|
|
<span class="keyword">return</span> model;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-106">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-106">¶</a>
|
|
</div>
|
|
<p><strong>parse</strong> converts a response into a list of models to be added to the
|
|
collection. The default implementation is just to pass it through.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> parse: <span class="keyword">function</span>(resp, options) {
|
|
<span class="keyword">return</span> resp;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-107">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-107">¶</a>
|
|
</div>
|
|
<p>Create a new collection with an identical list of models as this one.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> clone: <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> <span class="keyword">new</span> <span class="keyword">this</span>.constructor(<span class="keyword">this</span>.models);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-108">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-108">¶</a>
|
|
</div>
|
|
<p>Private method to reset all internal state. Called when the collection
|
|
is first initialized or reset.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _reset: <span class="keyword">function</span>() {
|
|
<span class="keyword">this</span>.length = <span class="number">0</span>;
|
|
<span class="keyword">this</span>.models = [];
|
|
<span class="keyword">this</span>._byId = {};
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-109">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-109">¶</a>
|
|
</div>
|
|
<p>Prepare a hash of attributes (or other model) to be added to this
|
|
collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _prepareModel: <span class="keyword">function</span>(attrs, options) {
|
|
<span class="keyword">if</span> (attrs <span class="keyword">instanceof</span> Model) {
|
|
<span class="keyword">if</span> (!attrs.collection) attrs.collection = <span class="keyword">this</span>;
|
|
<span class="keyword">return</span> attrs;
|
|
}
|
|
options = options ? _.clone(options) : {};
|
|
options.collection = <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> model = <span class="keyword">new</span> <span class="keyword">this</span>.model(attrs, options);
|
|
<span class="keyword">if</span> (!model.validationError) <span class="keyword">return</span> model;
|
|
<span class="keyword">this</span>.trigger(<span class="string">'invalid'</span>, <span class="keyword">this</span>, model.validationError, options);
|
|
<span class="keyword">return</span> <span class="literal">false</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-110">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-110">¶</a>
|
|
</div>
|
|
<p>Internal method to sever a model's ties to a collection.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _removeReference: <span class="keyword">function</span>(model) {
|
|
<span class="keyword">if</span> (<span class="keyword">this</span> === model.collection) <span class="keyword">delete</span> model.collection;
|
|
model.off(<span class="string">'all'</span>, <span class="keyword">this</span>._onModelEvent, <span class="keyword">this</span>);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-111">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-111">¶</a>
|
|
</div>
|
|
<p>Internal method called every time a model in the set fires an event.
|
|
Sets need to update their indexes when models change ids. All other
|
|
events simply proxy through. "add" and "remove" events that originate
|
|
in other collections are ignored.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _onModelEvent: <span class="keyword">function</span>(event, model, collection, options) {
|
|
<span class="keyword">if</span> ((event === <span class="string">'add'</span> || event === <span class="string">'remove'</span>) && collection !== <span class="keyword">this</span>) <span class="keyword">return</span>;
|
|
<span class="keyword">if</span> (event === <span class="string">'destroy'</span>) <span class="keyword">this</span>.remove(model, options);
|
|
<span class="keyword">if</span> (model && event === <span class="string">'change:'</span> + model.idAttribute) {
|
|
<span class="keyword">delete</span> <span class="keyword">this</span>._byId[model.previous(model.idAttribute)];
|
|
<span class="keyword">if</span> (model.id != <span class="literal">null</span>) <span class="keyword">this</span>._byId[model.id] = model;
|
|
}
|
|
<span class="keyword">this</span>.trigger.apply(<span class="keyword">this</span>, arguments);
|
|
}
|
|
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-112">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-112">¶</a>
|
|
</div>
|
|
<p>Underscore methods that we want to implement on the Collection.
|
|
90% of the core usefulness of Backbone Collections is actually implemented
|
|
right here:</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> methods = [<span class="string">'forEach'</span>, <span class="string">'each'</span>, <span class="string">'map'</span>, <span class="string">'collect'</span>, <span class="string">'reduce'</span>, <span class="string">'foldl'</span>,
|
|
<span class="string">'inject'</span>, <span class="string">'reduceRight'</span>, <span class="string">'foldr'</span>, <span class="string">'find'</span>, <span class="string">'detect'</span>, <span class="string">'filter'</span>, <span class="string">'select'</span>,
|
|
<span class="string">'reject'</span>, <span class="string">'every'</span>, <span class="string">'all'</span>, <span class="string">'some'</span>, <span class="string">'any'</span>, <span class="string">'include'</span>, <span class="string">'contains'</span>, <span class="string">'invoke'</span>,
|
|
<span class="string">'max'</span>, <span class="string">'min'</span>, <span class="string">'toArray'</span>, <span class="string">'size'</span>, <span class="string">'first'</span>, <span class="string">'head'</span>, <span class="string">'take'</span>, <span class="string">'initial'</span>, <span class="string">'rest'</span>,
|
|
<span class="string">'tail'</span>, <span class="string">'drop'</span>, <span class="string">'last'</span>, <span class="string">'without'</span>, <span class="string">'difference'</span>, <span class="string">'indexOf'</span>, <span class="string">'shuffle'</span>,
|
|
<span class="string">'lastIndexOf'</span>, <span class="string">'isEmpty'</span>, <span class="string">'chain'</span>];</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-113">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-113">¶</a>
|
|
</div>
|
|
<p>Mix in each Underscore method as a proxy to <code>Collection#models</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.each(methods, <span class="keyword">function</span>(method) {
|
|
Collection.prototype[method] = <span class="keyword">function</span>() {
|
|
<span class="keyword">var</span> args = slice.call(arguments);
|
|
args.unshift(<span class="keyword">this</span>.models);
|
|
<span class="keyword">return</span> _[method].apply(_, args);
|
|
};
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-114">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-114">¶</a>
|
|
</div>
|
|
<p>Underscore methods that take a property name as an argument.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> attributeMethods = [<span class="string">'groupBy'</span>, <span class="string">'countBy'</span>, <span class="string">'sortBy'</span>];</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-115">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-115">¶</a>
|
|
</div>
|
|
<p>Use attributes instead of properties.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.each(attributeMethods, <span class="keyword">function</span>(method) {
|
|
Collection.prototype[method] = <span class="keyword">function</span>(value, context) {
|
|
<span class="keyword">var</span> iterator = _.isFunction(value) ? value : <span class="keyword">function</span>(model) {
|
|
<span class="keyword">return</span> model.get(value);
|
|
};
|
|
<span class="keyword">return</span> _[method](<span class="keyword">this</span>.models, iterator, context);
|
|
};
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-116">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-116">¶</a>
|
|
</div>
|
|
<h2>Backbone.View</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-117">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-117">¶</a>
|
|
</div>
|
|
<p>Backbone Views are almost more convention than they are actual code. A View
|
|
is simply a JavaScript object that represents a logical chunk of UI in the
|
|
DOM. This might be a single item, an entire list, a sidebar or panel, or
|
|
even the surrounding frame which wraps your whole app. Defining a chunk of
|
|
UI as a <strong>View</strong> allows you to define your DOM events declaratively, without
|
|
having to worry about render order ... and makes it easy for the view to
|
|
react to specific changes in the state of your models.</p>
|
|
<p>Creating a Backbone.View creates its initial element outside of the DOM,
|
|
if an existing element is not provided...</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> View = Backbone.View = <span class="keyword">function</span>(options) {
|
|
<span class="keyword">this</span>.cid = _.uniqueId(<span class="string">'view'</span>);
|
|
options || (options = {});
|
|
_.extend(<span class="keyword">this</span>, _.pick(options, viewOptions));
|
|
<span class="keyword">this</span>._ensureElement();
|
|
<span class="keyword">this</span>.initialize.apply(<span class="keyword">this</span>, arguments);
|
|
<span class="keyword">this</span>.delegateEvents();
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-118">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-118">¶</a>
|
|
</div>
|
|
<p>Cached regex to split keys for <code>delegate</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> delegateEventSplitter = <span class="regexp">/^(\S+)\s*(.*)$/</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-119">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-119">¶</a>
|
|
</div>
|
|
<p>List of view options to be merged as properties.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> viewOptions = [<span class="string">'model'</span>, <span class="string">'collection'</span>, <span class="string">'el'</span>, <span class="string">'id'</span>, <span class="string">'attributes'</span>, <span class="string">'className'</span>, <span class="string">'tagName'</span>, <span class="string">'events'</span>];</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-120">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-120">¶</a>
|
|
</div>
|
|
<p>Set up all inheritable <strong>Backbone.View</strong> properties and methods.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.extend(View.prototype, Events, {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-121">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-121">¶</a>
|
|
</div>
|
|
<p>The default <code>tagName</code> of a View's element is <code>"div"</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> tagName: <span class="string">'div'</span>,</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-122">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-122">¶</a>
|
|
</div>
|
|
<p>jQuery delegate for element lookup, scoped to DOM elements within the
|
|
current view. This should be preferred to global lookups where possible.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> $: <span class="keyword">function</span>(selector) {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.$el.find(selector);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-123">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-123">¶</a>
|
|
</div>
|
|
<p>Initialize is an empty function by default. Override it with your own
|
|
initialization logic.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>(){},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-124">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-124">¶</a>
|
|
</div>
|
|
<p><strong>render</strong> is the core function that your view should override, in order
|
|
to populate its element (<code>this.el</code>), with the appropriate HTML. The
|
|
convention is for <strong>render</strong> to always return <code>this</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> render: <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-125">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-125">¶</a>
|
|
</div>
|
|
<p>Remove this view by taking the element out of the DOM, and removing any
|
|
applicable Backbone.Events listeners.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> remove: <span class="keyword">function</span>() {
|
|
<span class="keyword">this</span>.$el.remove();
|
|
<span class="keyword">this</span>.stopListening();
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-126">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-126">¶</a>
|
|
</div>
|
|
<p>Change the view's element (<code>this.el</code> property), including event
|
|
re-delegation.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> setElement: <span class="keyword">function</span>(element, delegate) {
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>.$el) <span class="keyword">this</span>.undelegateEvents();
|
|
<span class="keyword">this</span>.$el = element <span class="keyword">instanceof</span> Backbone.$ ? element : Backbone.$(element);
|
|
<span class="keyword">this</span>.el = <span class="keyword">this</span>.$el[<span class="number">0</span>];
|
|
<span class="keyword">if</span> (delegate !== <span class="literal">false</span>) <span class="keyword">this</span>.delegateEvents();
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-127">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-127">¶</a>
|
|
</div>
|
|
<p>Set callbacks, where <code>this.events</code> is a hash of</p>
|
|
<p><em>{"event selector": "callback"}</em></p>
|
|
<pre><code>{
|
|
'mousedown .title': 'edit',
|
|
'click .button': 'save',
|
|
'click .open': function(e) { ... }
|
|
}</code></pre>
|
|
<p>pairs. Callbacks will be bound to the view, with <code>this</code> set properly.
|
|
Uses event delegation for efficiency.
|
|
Omitting the selector binds the event to <code>this.el</code>.
|
|
This only works for delegate-able events: not <code>focus</code>, <code>blur</code>, and
|
|
not <code>change</code>, <code>submit</code>, and <code>reset</code> in Internet Explorer.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> delegateEvents: <span class="keyword">function</span>(events) {
|
|
<span class="keyword">if</span> (!(events || (events = _.result(<span class="keyword">this</span>, <span class="string">'events'</span>)))) <span class="keyword">return</span> <span class="keyword">this</span>;
|
|
<span class="keyword">this</span>.undelegateEvents();
|
|
<span class="keyword">for</span> (<span class="keyword">var</span> key <span class="keyword">in</span> events) {
|
|
<span class="keyword">var</span> method = events[key];
|
|
<span class="keyword">if</span> (!_.isFunction(method)) method = <span class="keyword">this</span>[events[key]];
|
|
<span class="keyword">if</span> (!method) <span class="keyword">continue</span>;
|
|
|
|
<span class="keyword">var</span> match = key.match(delegateEventSplitter);
|
|
<span class="keyword">var</span> eventName = match[<span class="number">1</span>], selector = match[<span class="number">2</span>];
|
|
method = _.bind(method, <span class="keyword">this</span>);
|
|
eventName += <span class="string">'.delegateEvents'</span> + <span class="keyword">this</span>.cid;
|
|
<span class="keyword">if</span> (selector === <span class="string">''</span>) {
|
|
<span class="keyword">this</span>.$el.on(eventName, method);
|
|
} <span class="keyword">else</span> {
|
|
<span class="keyword">this</span>.$el.on(eventName, selector, method);
|
|
}
|
|
}
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-128">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-128">¶</a>
|
|
</div>
|
|
<p>Clears all callbacks previously bound to the view with <code>delegateEvents</code>.
|
|
You usually don't need to use this, but may wish to if you have multiple
|
|
Backbone views attached to the same DOM element.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> undelegateEvents: <span class="keyword">function</span>() {
|
|
<span class="keyword">this</span>.$el.off(<span class="string">'.delegateEvents'</span> + <span class="keyword">this</span>.cid);
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-129">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-129">¶</a>
|
|
</div>
|
|
<p>Ensure that the View has a DOM element to render into.
|
|
If <code>this.el</code> is a string, pass it through <code>$()</code>, take the first
|
|
matching element, and re-assign it to <code>el</code>. Otherwise, create
|
|
an element from the <code>id</code>, <code>className</code> and <code>tagName</code> properties.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _ensureElement: <span class="keyword">function</span>() {
|
|
<span class="keyword">if</span> (!<span class="keyword">this</span>.el) {
|
|
<span class="keyword">var</span> attrs = _.extend({}, _.result(<span class="keyword">this</span>, <span class="string">'attributes'</span>));
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>.id) attrs.id = _.result(<span class="keyword">this</span>, <span class="string">'id'</span>);
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>.className) attrs[<span class="string">'class'</span>] = _.result(<span class="keyword">this</span>, <span class="string">'className'</span>);
|
|
<span class="keyword">var</span> $el = Backbone.$(<span class="string">'<'</span> + _.result(<span class="keyword">this</span>, <span class="string">'tagName'</span>) + <span class="string">'>'</span>).attr(attrs);
|
|
<span class="keyword">this</span>.setElement($el, <span class="literal">false</span>);
|
|
} <span class="keyword">else</span> {
|
|
<span class="keyword">this</span>.setElement(_.result(<span class="keyword">this</span>, <span class="string">'el'</span>), <span class="literal">false</span>);
|
|
}
|
|
}
|
|
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-130">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-130">¶</a>
|
|
</div>
|
|
<h2>Backbone.sync</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-131">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-131">¶</a>
|
|
</div>
|
|
<p>Override this function to change the manner in which Backbone persists
|
|
models to the server. You will be passed the type of request, and the
|
|
model in question. By default, makes a RESTful Ajax request
|
|
to the model's <code>url()</code>. Some possible customizations could be:</p>
|
|
<ul>
|
|
<li>Use <code>setTimeout</code> to batch rapid-fire updates into a single request.</li>
|
|
<li>Send up the models as XML instead of JSON.</li>
|
|
<li>Persist models via WebSockets instead of Ajax.</li>
|
|
</ul>
|
|
<p>Turn on <code>Backbone.emulateHTTP</code> in order to send <code>PUT</code> and <code>DELETE</code> requests
|
|
as <code>POST</code>, with a <code>_method</code> parameter containing the true HTTP method,
|
|
as well as all requests with the body as <code>application/x-www-form-urlencoded</code>
|
|
instead of <code>application/json</code> with the model in a param named <code>model</code>.
|
|
Useful when interfacing with server-side languages like <strong>PHP</strong> that make
|
|
it difficult to read the body of <code>PUT</code> requests.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Backbone.sync = <span class="keyword">function</span>(method, model, options) {
|
|
<span class="keyword">var</span> type = methodMap[method];</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-132">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-132">¶</a>
|
|
</div>
|
|
<p>Default options, unless specified.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.defaults(options || (options = {}), {
|
|
emulateHTTP: Backbone.emulateHTTP,
|
|
emulateJSON: Backbone.emulateJSON
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-133">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-133">¶</a>
|
|
</div>
|
|
<p>Default JSON-request options.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> params = {type: type, dataType: <span class="string">'json'</span>};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-134">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-134">¶</a>
|
|
</div>
|
|
<p>Ensure that we have a URL.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (!options.url) {
|
|
params.url = _.result(model, <span class="string">'url'</span>) || urlError();
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-135">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-135">¶</a>
|
|
</div>
|
|
<p>Ensure that we have the appropriate request data.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (options.data == <span class="literal">null</span> && model && (method === <span class="string">'create'</span> || method === <span class="string">'update'</span> || method === <span class="string">'patch'</span>)) {
|
|
params.contentType = <span class="string">'application/json'</span>;
|
|
params.data = JSON.stringify(options.attrs || model.toJSON(options));
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-136">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-136">¶</a>
|
|
</div>
|
|
<p>For older servers, emulate JSON by encoding the request into an HTML-form.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (options.emulateJSON) {
|
|
params.contentType = <span class="string">'application/x-www-form-urlencoded'</span>;
|
|
params.data = params.data ? {model: params.data} : {};
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-137">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-137">¶</a>
|
|
</div>
|
|
<p>For older servers, emulate HTTP by mimicking the HTTP method with <code>_method</code>
|
|
And an <code>X-HTTP-Method-Override</code> header.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (options.emulateHTTP && (type === <span class="string">'PUT'</span> || type === <span class="string">'DELETE'</span> || type === <span class="string">'PATCH'</span>)) {
|
|
params.type = <span class="string">'POST'</span>;
|
|
<span class="keyword">if</span> (options.emulateJSON) params.data._method = type;
|
|
<span class="keyword">var</span> beforeSend = options.beforeSend;
|
|
options.beforeSend = <span class="keyword">function</span>(xhr) {
|
|
xhr.setRequestHeader(<span class="string">'X-HTTP-Method-Override'</span>, type);
|
|
<span class="keyword">if</span> (beforeSend) <span class="keyword">return</span> beforeSend.apply(<span class="keyword">this</span>, arguments);
|
|
};
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-138">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-138">¶</a>
|
|
</div>
|
|
<p>Don't process data on a non-GET request.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (params.type !== <span class="string">'GET'</span> && !options.emulateJSON) {
|
|
params.processData = <span class="literal">false</span>;
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-139">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-139">¶</a>
|
|
</div>
|
|
<p>If we're sending a <code>PATCH</code> request, and we're in an old Internet Explorer
|
|
that still has ActiveX enabled by default, override jQuery to use that
|
|
for XHR instead. Remove this line when jQuery supports <code>PATCH</code> on IE8.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (params.type === <span class="string">'PATCH'</span> && noXhrPatch) {
|
|
params.xhr = <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> <span class="keyword">new</span> ActiveXObject(<span class="string">"Microsoft.XMLHTTP"</span>);
|
|
};
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-140">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-140">¶</a>
|
|
</div>
|
|
<p>Make the request, allowing the user to override any Ajax options.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> xhr = options.xhr = Backbone.ajax(_.extend(params, options));
|
|
model.trigger(<span class="string">'request'</span>, model, xhr, options);
|
|
<span class="keyword">return</span> xhr;
|
|
};
|
|
|
|
<span class="keyword">var</span> noXhrPatch = <span class="keyword">typeof</span> window !== <span class="string">'undefined'</span> && !!window.ActiveXObject && !(window.XMLHttpRequest && (<span class="keyword">new</span> XMLHttpRequest).dispatchEvent);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-141">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-141">¶</a>
|
|
</div>
|
|
<p>Map from CRUD to HTTP for our default <code>Backbone.sync</code> implementation.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> methodMap = {
|
|
<span class="string">'create'</span>: <span class="string">'POST'</span>,
|
|
<span class="string">'update'</span>: <span class="string">'PUT'</span>,
|
|
<span class="string">'patch'</span>: <span class="string">'PATCH'</span>,
|
|
<span class="string">'delete'</span>: <span class="string">'DELETE'</span>,
|
|
<span class="string">'read'</span>: <span class="string">'GET'</span>
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-142">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-142">¶</a>
|
|
</div>
|
|
<p>Set the default implementation of <code>Backbone.ajax</code> to proxy through to <code>$</code>.
|
|
Override this if you'd like to use a different library.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Backbone.ajax = <span class="keyword">function</span>() {
|
|
<span class="keyword">return</span> Backbone.$.ajax.apply(Backbone.$, arguments);
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-143">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-143">¶</a>
|
|
</div>
|
|
<h2>Backbone.Router</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-144">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-144">¶</a>
|
|
</div>
|
|
<p>Routers map faux-URLs to actions, and fire events when routes are
|
|
matched. Creating a new one sets its <code>routes</code> hash, if not set statically.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Router = Backbone.Router = <span class="keyword">function</span>(options) {
|
|
options || (options = {});
|
|
<span class="keyword">if</span> (options.routes) <span class="keyword">this</span>.routes = options.routes;
|
|
<span class="keyword">this</span>._bindRoutes();
|
|
<span class="keyword">this</span>.initialize.apply(<span class="keyword">this</span>, arguments);
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-145">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-145">¶</a>
|
|
</div>
|
|
<p>Cached regular expressions for matching named param parts and splatted
|
|
parts of route strings.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> optionalParam = <span class="regexp">/\((.*?)\)/g</span>;
|
|
<span class="keyword">var</span> namedParam = <span class="regexp">/(\(\?)?:\w+/g</span>;
|
|
<span class="keyword">var</span> splatParam = <span class="regexp">/\*\w+/g</span>;
|
|
<span class="keyword">var</span> escapeRegExp = <span class="regexp">/[\-{}\[\]+?.,\\\^$|#\s]/g</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-146">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-146">¶</a>
|
|
</div>
|
|
<p>Set up all inheritable <strong>Backbone.Router</strong> properties and methods.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.extend(Router.prototype, Events, {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-147">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-147">¶</a>
|
|
</div>
|
|
<p>Initialize is an empty function by default. Override it with your own
|
|
initialization logic.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> initialize: <span class="keyword">function</span>(){},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-148">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-148">¶</a>
|
|
</div>
|
|
<p>Manually bind a single named route to a callback. For example:</p>
|
|
<pre><code>this.route('search/:query/p:num', 'search', function(query, num) {
|
|
...
|
|
});</code></pre>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> route: <span class="keyword">function</span>(route, name, callback) {
|
|
<span class="keyword">if</span> (!_.isRegExp(route)) route = <span class="keyword">this</span>._routeToRegExp(route);
|
|
<span class="keyword">if</span> (_.isFunction(name)) {
|
|
callback = name;
|
|
name = <span class="string">''</span>;
|
|
}
|
|
<span class="keyword">if</span> (!callback) callback = <span class="keyword">this</span>[name];
|
|
<span class="keyword">var</span> router = <span class="keyword">this</span>;
|
|
Backbone.history.route(route, <span class="keyword">function</span>(fragment) {
|
|
<span class="keyword">var</span> args = router._extractParameters(route, fragment);
|
|
callback && callback.apply(router, args);
|
|
router.trigger.apply(router, [<span class="string">'route:'</span> + name].concat(args));
|
|
router.trigger(<span class="string">'route'</span>, name, args);
|
|
Backbone.history.trigger(<span class="string">'route'</span>, router, name, args);
|
|
});
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-149">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-149">¶</a>
|
|
</div>
|
|
<p>Simple proxy to <code>Backbone.history</code> to save a fragment into the history.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> navigate: <span class="keyword">function</span>(fragment, options) {
|
|
Backbone.history.navigate(fragment, options);
|
|
<span class="keyword">return</span> <span class="keyword">this</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-150">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-150">¶</a>
|
|
</div>
|
|
<p>Bind all defined routes to <code>Backbone.history</code>. We have to reverse the
|
|
order of the routes here to support behavior where the most general
|
|
routes can be defined at the bottom of the route map.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _bindRoutes: <span class="keyword">function</span>() {
|
|
<span class="keyword">if</span> (!<span class="keyword">this</span>.routes) <span class="keyword">return</span>;
|
|
<span class="keyword">this</span>.routes = _.result(<span class="keyword">this</span>, <span class="string">'routes'</span>);
|
|
<span class="keyword">var</span> route, routes = _.keys(<span class="keyword">this</span>.routes);
|
|
<span class="keyword">while</span> ((route = routes.pop()) != <span class="literal">null</span>) {
|
|
<span class="keyword">this</span>.route(route, <span class="keyword">this</span>.routes[route]);
|
|
}
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-151">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-151">¶</a>
|
|
</div>
|
|
<p>Convert a route string into a regular expression, suitable for matching
|
|
against the current location hash.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _routeToRegExp: <span class="keyword">function</span>(route) {
|
|
route = route.replace(escapeRegExp, <span class="string">'\\$&'</span>)
|
|
.replace(optionalParam, <span class="string">'(?:$1)?'</span>)
|
|
.replace(namedParam, <span class="keyword">function</span>(match, optional) {
|
|
<span class="keyword">return</span> optional ? match : <span class="string">'([^\/]+)'</span>;
|
|
})
|
|
.replace(splatParam, <span class="string">'(.*?)'</span>);
|
|
<span class="keyword">return</span> <span class="keyword">new</span> RegExp(<span class="string">'^'</span> + route + <span class="string">'$'</span>);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-152">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-152">¶</a>
|
|
</div>
|
|
<p>Given a route, and a URL fragment that it matches, return the array of
|
|
extracted decoded parameters. Empty or unmatched parameters will be
|
|
treated as <code>null</code> to normalize cross-browser behavior.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _extractParameters: <span class="keyword">function</span>(route, fragment) {
|
|
<span class="keyword">var</span> params = route.exec(fragment).slice(<span class="number">1</span>);
|
|
<span class="keyword">return</span> _.map(params, <span class="keyword">function</span>(param) {
|
|
<span class="keyword">return</span> param ? decodeURIComponent(param) : <span class="literal">null</span>;
|
|
});
|
|
}
|
|
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-153">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-153">¶</a>
|
|
</div>
|
|
<h2>Backbone.History</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-154">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-154">¶</a>
|
|
</div>
|
|
<p>Handles cross-browser history management, based on either
|
|
<a href="http://diveintohtml5.info/history.html">pushState</a> and real URLs, or
|
|
<a href="https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange">onhashchange</a>
|
|
and URL fragments. If the browser supports neither (old IE, natch),
|
|
falls back to polling.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> History = Backbone.History = <span class="keyword">function</span>() {
|
|
<span class="keyword">this</span>.handlers = [];
|
|
_.bindAll(<span class="keyword">this</span>, <span class="string">'checkUrl'</span>);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-155">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-155">¶</a>
|
|
</div>
|
|
<p>Ensure that <code>History</code> can be used outside of the browser.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (<span class="keyword">typeof</span> window !== <span class="string">'undefined'</span>) {
|
|
<span class="keyword">this</span>.location = window.location;
|
|
<span class="keyword">this</span>.history = window.history;
|
|
}
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-156">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-156">¶</a>
|
|
</div>
|
|
<p>Cached regex for stripping a leading hash/slash and trailing space.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> routeStripper = <span class="regexp">/^[#\/]|\s+$/g</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-157">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-157">¶</a>
|
|
</div>
|
|
<p>Cached regex for stripping leading and trailing slashes.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> rootStripper = <span class="regexp">/^\/+|\/+$/g</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-158">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-158">¶</a>
|
|
</div>
|
|
<p>Cached regex for detecting MSIE.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> isExplorer = <span class="regexp">/msie [\w.]+/</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-159">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-159">¶</a>
|
|
</div>
|
|
<p>Cached regex for removing a trailing slash.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> trailingSlash = <span class="regexp">/\/$/</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-160">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-160">¶</a>
|
|
</div>
|
|
<p>Cached regex for stripping urls of hash and query.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> pathStripper = <span class="regexp">/[?#].*$/</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-161">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-161">¶</a>
|
|
</div>
|
|
<p>Has the history handling already been started?</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> History.started = <span class="literal">false</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-162">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-162">¶</a>
|
|
</div>
|
|
<p>Set up all inheritable <strong>Backbone.History</strong> properties and methods.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.extend(History.prototype, Events, {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-163">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-163">¶</a>
|
|
</div>
|
|
<p>The default interval to poll for hash changes, if necessary, is
|
|
twenty times a second.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> interval: <span class="number">50</span>,</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-164">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-164">¶</a>
|
|
</div>
|
|
<p>Gets the true hash value. Cannot use location.hash directly due to bug
|
|
in Firefox where location.hash will always be decoded.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> getHash: <span class="keyword">function</span>(window) {
|
|
<span class="keyword">var</span> match = (window || <span class="keyword">this</span>).location.href.match(<span class="regexp">/#(.*)$/</span>);
|
|
<span class="keyword">return</span> match ? match[<span class="number">1</span>] : <span class="string">''</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-165">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-165">¶</a>
|
|
</div>
|
|
<p>Get the cross-browser normalized URL fragment, either from the URL,
|
|
the hash, or the override.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> getFragment: <span class="keyword">function</span>(fragment, forcePushState) {
|
|
<span class="keyword">if</span> (fragment == <span class="literal">null</span>) {
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>._hasPushState || !<span class="keyword">this</span>._wantsHashChange || forcePushState) {
|
|
fragment = <span class="keyword">this</span>.location.pathname;
|
|
<span class="keyword">var</span> root = <span class="keyword">this</span>.root.replace(trailingSlash, <span class="string">''</span>);
|
|
<span class="keyword">if</span> (!fragment.indexOf(root)) fragment = fragment.slice(root.length);
|
|
} <span class="keyword">else</span> {
|
|
fragment = <span class="keyword">this</span>.getHash();
|
|
}
|
|
}
|
|
<span class="keyword">return</span> fragment.replace(routeStripper, <span class="string">''</span>);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-166">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-166">¶</a>
|
|
</div>
|
|
<p>Start the hash change handling, returning <code>true</code> if the current URL matches
|
|
an existing route, and <code>false</code> otherwise.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> start: <span class="keyword">function</span>(options) {
|
|
<span class="keyword">if</span> (History.started) <span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">"Backbone.history has already been started"</span>);
|
|
History.started = <span class="literal">true</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-167">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-167">¶</a>
|
|
</div>
|
|
<p>Figure out the initial configuration. Do we need an iframe?
|
|
Is pushState desired ... is it available?</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">this</span>.options = _.extend({root: <span class="string">'/'</span>}, <span class="keyword">this</span>.options, options);
|
|
<span class="keyword">this</span>.root = <span class="keyword">this</span>.options.root;
|
|
<span class="keyword">this</span>._wantsHashChange = <span class="keyword">this</span>.options.hashChange !== <span class="literal">false</span>;
|
|
<span class="keyword">this</span>._wantsPushState = !!<span class="keyword">this</span>.options.pushState;
|
|
<span class="keyword">this</span>._hasPushState = !!(<span class="keyword">this</span>.options.pushState && <span class="keyword">this</span>.history && <span class="keyword">this</span>.history.pushState);
|
|
<span class="keyword">var</span> fragment = <span class="keyword">this</span>.getFragment();
|
|
<span class="keyword">var</span> docMode = document.documentMode;
|
|
<span class="keyword">var</span> oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= <span class="number">7</span>));</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-168">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-168">¶</a>
|
|
</div>
|
|
<p>Normalize root to always include a leading and trailing slash.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">this</span>.root = (<span class="string">'/'</span> + <span class="keyword">this</span>.root + <span class="string">'/'</span>).replace(rootStripper, <span class="string">'/'</span>);
|
|
|
|
<span class="keyword">if</span> (oldIE && <span class="keyword">this</span>._wantsHashChange) {
|
|
<span class="keyword">this</span>.iframe = Backbone.$(<span class="string">'<iframe src="javascript:0" tabindex="-1" />'</span>).hide().appendTo(<span class="string">'body'</span>)[<span class="number">0</span>].contentWindow;
|
|
<span class="keyword">this</span>.navigate(fragment);
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-169">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-169">¶</a>
|
|
</div>
|
|
<p>Depending on whether we're using pushState or hashes, and whether
|
|
'onhashchange' is supported, determine how we check the URL state.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (<span class="keyword">this</span>._hasPushState) {
|
|
Backbone.$(window).on(<span class="string">'popstate'</span>, <span class="keyword">this</span>.checkUrl);
|
|
} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>._wantsHashChange && (<span class="string">'onhashchange'</span> <span class="keyword">in</span> window) && !oldIE) {
|
|
Backbone.$(window).on(<span class="string">'hashchange'</span>, <span class="keyword">this</span>.checkUrl);
|
|
} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>._wantsHashChange) {
|
|
<span class="keyword">this</span>._checkUrlInterval = setInterval(<span class="keyword">this</span>.checkUrl, <span class="keyword">this</span>.interval);
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-170">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-170">¶</a>
|
|
</div>
|
|
<p>Determine if we need to change the base url, for a pushState link
|
|
opened by a non-pushState browser.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">this</span>.fragment = fragment;
|
|
<span class="keyword">var</span> loc = <span class="keyword">this</span>.location;
|
|
<span class="keyword">var</span> atRoot = loc.pathname.replace(<span class="regexp">/[^\/]$/</span>, <span class="string">'$&/'</span>) === <span class="keyword">this</span>.root;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-171">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-171">¶</a>
|
|
</div>
|
|
<p>Transition from hashChange to pushState or vice versa if both are
|
|
requested.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (<span class="keyword">this</span>._wantsHashChange && <span class="keyword">this</span>._wantsPushState) {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-172">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-172">¶</a>
|
|
</div>
|
|
<p>If we've started off with a route from a <code>pushState</code>-enabled
|
|
browser, but we're currently in a browser that doesn't support it...</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (!<span class="keyword">this</span>._hasPushState && !atRoot) {
|
|
<span class="keyword">this</span>.fragment = <span class="keyword">this</span>.getFragment(<span class="literal">null</span>, <span class="literal">true</span>);
|
|
<span class="keyword">this</span>.location.replace(<span class="keyword">this</span>.root + <span class="keyword">this</span>.location.search + <span class="string">'#'</span> + <span class="keyword">this</span>.fragment);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-173">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-173">¶</a>
|
|
</div>
|
|
<p>Return immediately as browser will do redirect to new url</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">return</span> <span class="literal">true</span>;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-174">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-174">¶</a>
|
|
</div>
|
|
<p>Or if we've started out with a hash-based route, but we're currently
|
|
in a browser where it could be <code>pushState</code>-based instead...</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>._hasPushState && atRoot && loc.hash) {
|
|
<span class="keyword">this</span>.fragment = <span class="keyword">this</span>.getHash().replace(routeStripper, <span class="string">''</span>);
|
|
<span class="keyword">this</span>.history.replaceState({}, document.title, <span class="keyword">this</span>.root + <span class="keyword">this</span>.fragment + loc.search);
|
|
}
|
|
|
|
}
|
|
|
|
<span class="keyword">if</span> (!<span class="keyword">this</span>.options.silent) <span class="keyword">return</span> <span class="keyword">this</span>.loadUrl();
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-175">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-175">¶</a>
|
|
</div>
|
|
<p>Disable Backbone.history, perhaps temporarily. Not useful in a real app,
|
|
but possibly useful for unit testing Routers.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> stop: <span class="keyword">function</span>() {
|
|
Backbone.$(window).off(<span class="string">'popstate'</span>, <span class="keyword">this</span>.checkUrl).off(<span class="string">'hashchange'</span>, <span class="keyword">this</span>.checkUrl);
|
|
clearInterval(<span class="keyword">this</span>._checkUrlInterval);
|
|
History.started = <span class="literal">false</span>;
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-176">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-176">¶</a>
|
|
</div>
|
|
<p>Add a route to be tested when the fragment changes. Routes added later
|
|
may override previous routes.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> route: <span class="keyword">function</span>(route, callback) {
|
|
<span class="keyword">this</span>.handlers.unshift({route: route, callback: callback});
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-177">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-177">¶</a>
|
|
</div>
|
|
<p>Checks the current URL to see if it has changed, and if it has,
|
|
calls <code>loadUrl</code>, normalizing across the hidden iframe.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> checkUrl: <span class="keyword">function</span>(e) {
|
|
<span class="keyword">var</span> current = <span class="keyword">this</span>.getFragment();
|
|
<span class="keyword">if</span> (current === <span class="keyword">this</span>.fragment && <span class="keyword">this</span>.iframe) {
|
|
current = <span class="keyword">this</span>.getFragment(<span class="keyword">this</span>.getHash(<span class="keyword">this</span>.iframe));
|
|
}
|
|
<span class="keyword">if</span> (current === <span class="keyword">this</span>.fragment) <span class="keyword">return</span> <span class="literal">false</span>;
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>.iframe) <span class="keyword">this</span>.navigate(current);
|
|
<span class="keyword">this</span>.loadUrl();
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-178">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-178">¶</a>
|
|
</div>
|
|
<p>Attempt to load the current URL fragment. If a route succeeds with a
|
|
match, returns <code>true</code>. If no defined routes matches the fragment,
|
|
returns <code>false</code>.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> loadUrl: <span class="keyword">function</span>(fragment) {
|
|
fragment = <span class="keyword">this</span>.fragment = <span class="keyword">this</span>.getFragment(fragment);
|
|
<span class="keyword">return</span> _.any(<span class="keyword">this</span>.handlers, <span class="keyword">function</span>(handler) {
|
|
<span class="keyword">if</span> (handler.route.test(fragment)) {
|
|
handler.callback(fragment);
|
|
<span class="keyword">return</span> <span class="literal">true</span>;
|
|
}
|
|
});
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-179">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-179">¶</a>
|
|
</div>
|
|
<p>Save a fragment into the hash history, or replace the URL state if the
|
|
'replace' option is passed. You are responsible for properly URL-encoding
|
|
the fragment in advance.</p>
|
|
<p>The options object can contain <code>trigger: true</code> if you wish to have the
|
|
route callback be fired (not usually desirable), or <code>replace: true</code>, if
|
|
you wish to modify the current URL without adding an entry to the history.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> navigate: <span class="keyword">function</span>(fragment, options) {
|
|
<span class="keyword">if</span> (!History.started) <span class="keyword">return</span> <span class="literal">false</span>;
|
|
<span class="keyword">if</span> (!options || options === <span class="literal">true</span>) options = {trigger: !!options};
|
|
|
|
<span class="keyword">var</span> url = <span class="keyword">this</span>.root + (fragment = <span class="keyword">this</span>.getFragment(fragment || <span class="string">''</span>));</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-180">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-180">¶</a>
|
|
</div>
|
|
<p>Strip the fragment of the query and hash for matching.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> fragment = fragment.replace(pathStripper, <span class="string">''</span>);
|
|
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>.fragment === fragment) <span class="keyword">return</span>;
|
|
<span class="keyword">this</span>.fragment = fragment;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-181">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-181">¶</a>
|
|
</div>
|
|
<p>Don't include a trailing slash on the root.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (fragment === <span class="string">''</span> && url !== <span class="string">'/'</span>) url = url.slice(<span class="number">0</span>, -<span class="number">1</span>);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-182">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-182">¶</a>
|
|
</div>
|
|
<p>If pushState is available, we use it to set the fragment as a real URL.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (<span class="keyword">this</span>._hasPushState) {
|
|
<span class="keyword">this</span>.history[options.replace ? <span class="string">'replaceState'</span> : <span class="string">'pushState'</span>]({}, document.title, url);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-183">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-183">¶</a>
|
|
</div>
|
|
<p>If hash changes haven't been explicitly disabled, update the hash
|
|
fragment to store history.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> } <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">this</span>._wantsHashChange) {
|
|
<span class="keyword">this</span>._updateHash(<span class="keyword">this</span>.location, fragment, options.replace);
|
|
<span class="keyword">if</span> (<span class="keyword">this</span>.iframe && (fragment !== <span class="keyword">this</span>.getFragment(<span class="keyword">this</span>.getHash(<span class="keyword">this</span>.iframe)))) {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-184">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-184">¶</a>
|
|
</div>
|
|
<p>Opening and closing the iframe tricks IE7 and earlier to push a
|
|
history entry on hash-tag change. When replace is true, we don't
|
|
want this.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span>(!options.replace) <span class="keyword">this</span>.iframe.document.open().close();
|
|
<span class="keyword">this</span>._updateHash(<span class="keyword">this</span>.iframe.location, fragment, options.replace);
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-185">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-185">¶</a>
|
|
</div>
|
|
<p>If you've told us that you explicitly don't want fallback hashchange-
|
|
based history, then <code>navigate</code> becomes a page refresh.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> } <span class="keyword">else</span> {
|
|
<span class="keyword">return</span> <span class="keyword">this</span>.location.assign(url);
|
|
}
|
|
<span class="keyword">if</span> (options.trigger) <span class="keyword">return</span> <span class="keyword">this</span>.loadUrl(fragment);
|
|
},</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-186">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-186">¶</a>
|
|
</div>
|
|
<p>Update the hash location, either replacing the current entry, or adding
|
|
a new one to the browser history.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _updateHash: <span class="keyword">function</span>(location, fragment, replace) {
|
|
<span class="keyword">if</span> (replace) {
|
|
<span class="keyword">var</span> href = location.href.replace(<span class="regexp">/(javascript:|#).*$/</span>, <span class="string">''</span>);
|
|
location.replace(href + <span class="string">'#'</span> + fragment);
|
|
} <span class="keyword">else</span> {</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-187">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-187">¶</a>
|
|
</div>
|
|
<p>Some browsers require that <code>hash</code> contains a leading #.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> location.hash = <span class="string">'#'</span> + fragment;
|
|
}
|
|
}
|
|
|
|
});</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-188">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-188">¶</a>
|
|
</div>
|
|
<p>Create the default Backbone.history.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Backbone.history = <span class="keyword">new</span> History;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-189">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap for-h2">
|
|
<a class="pilcrow" href="#section-189">¶</a>
|
|
</div>
|
|
<h2>Helpers</h2>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-190">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-190">¶</a>
|
|
</div>
|
|
<p>Helper function to correctly set up the prototype chain, for subclasses.
|
|
Similar to <code>goog.inherits</code>, but uses a hash of prototype properties and
|
|
class properties to be extended.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> extend = <span class="keyword">function</span>(protoProps, staticProps) {
|
|
<span class="keyword">var</span> parent = <span class="keyword">this</span>;
|
|
<span class="keyword">var</span> child;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-191">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-191">¶</a>
|
|
</div>
|
|
<p>The constructor function for the new subclass is either defined by you
|
|
(the "constructor" property in your <code>extend</code> definition), or defaulted
|
|
by us to simply call the parent's constructor.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (protoProps && _.has(protoProps, <span class="string">'constructor'</span>)) {
|
|
child = protoProps.constructor;
|
|
} <span class="keyword">else</span> {
|
|
child = <span class="keyword">function</span>(){ <span class="keyword">return</span> parent.apply(<span class="keyword">this</span>, arguments); };
|
|
}</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-192">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-192">¶</a>
|
|
</div>
|
|
<p>Add static properties to the constructor function, if supplied.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> _.extend(child, parent, staticProps);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-193">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-193">¶</a>
|
|
</div>
|
|
<p>Set the prototype chain to inherit from <code>parent</code>, without calling
|
|
<code>parent</code>'s constructor function.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> Surrogate = <span class="keyword">function</span>(){ <span class="keyword">this</span>.constructor = child; };
|
|
Surrogate.prototype = parent.prototype;
|
|
child.prototype = <span class="keyword">new</span> Surrogate;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-194">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-194">¶</a>
|
|
</div>
|
|
<p>Add prototype properties (instance properties) to the subclass,
|
|
if supplied.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">if</span> (protoProps) _.extend(child.prototype, protoProps);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-195">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-195">¶</a>
|
|
</div>
|
|
<p>Set a convenience property in case the parent's prototype is needed
|
|
later.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> child.__super__ = parent.prototype;
|
|
|
|
<span class="keyword">return</span> child;
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-196">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-196">¶</a>
|
|
</div>
|
|
<p>Set up inheritance for the model, collection, router, view and history.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-197">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-197">¶</a>
|
|
</div>
|
|
<p>Throw an error when a URL is needed, and none is supplied.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> urlError = <span class="keyword">function</span>() {
|
|
<span class="keyword">throw</span> <span class="keyword">new</span> Error(<span class="string">'A "url" property or function must be specified'</span>);
|
|
};</pre></div></div>
|
|
|
|
</li>
|
|
|
|
|
|
<li id="section-198">
|
|
<div class="annotation">
|
|
|
|
<div class="pilwrap ">
|
|
<a class="pilcrow" href="#section-198">¶</a>
|
|
</div>
|
|
<p>Wrap an optional error callback with a fallback error event.</p>
|
|
|
|
</div>
|
|
|
|
<div class="content"><div class='highlight'><pre> <span class="keyword">var</span> wrapError = <span class="keyword">function</span>(model, options) {
|
|
<span class="keyword">var</span> error = options.error;
|
|
options.error = <span class="keyword">function</span>(resp) {
|
|
<span class="keyword">if</span> (error) error(model, resp, options);
|
|
model.trigger(<span class="string">'error'</span>, model, resp, options);
|
|
};
|
|
};
|
|
|
|
}).call(<span class="keyword">this</span>);</pre></div></div>
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
</div>
|
|
</body>
|
|
</html>
|