Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added missing files

  • Loading branch information...
commit 39e1b9be6f4d04b7fa3b5bd477f7715ad167a118 1 parent ec6bbde
lsolc lsolc authored
40 app.rb
... ... @@ -0,0 +1,40 @@
  1 +require 'rubygems'
  2 +require 'sinatra'
  3 +require 'json'
  4 +
  5 +
  6 +get '/profile' do
  7 + content_type 'application/json', :charset => 'utf-8'
  8 + {
  9 + :firstName => "Ladislav",
  10 + :lastName => "Solc",
  11 + :email => "ladislav.solc@gmail.com",
  12 + :phone => "723 743 208",
  13 + :country => "Czech Republic"
  14 + }.to_json
  15 +end
  16 +
  17 +get '/log_items' do
  18 +
  19 + items = []
  20 +
  21 + 100.times do |i|
  22 + items << {
  23 + :host => "sd.vanilladesk.com",
  24 + :short_message => "Failure #{i}",
  25 + :full_message => "app/helpers/text_helper.rb:35:in",
  26 + :timestamp => 1291899928,
  27 + :level => "#{i}",
  28 + :file => "/var/www/somefile.rb",
  29 + :line => "356",
  30 + :_user_id => "jkaramon",
  31 + :custom_data => {
  32 + :application => "VanillaDesk",
  33 + :app_version => "1.0.2345",
  34 + :component => "job_server"
  35 + }
  36 + }
  37 + end
  38 + content_type 'application/json', :charset => 'utf-8'
  39 + items.to_json
  40 +end
60 public/log_items.html
... ... @@ -0,0 +1,60 @@
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <title>Loading JSON files using jQuery</title>
  5 +
  6 + <script id="entry-template" type="text/x-handlebars-template" >
  7 + {{#each log_items}}
  8 + <li>
  9 + <div class="header" id="button_header">
  10 + <div class="left">
  11 + <span>Host: </span>
  12 + <span class="host">{{host}}</span>
  13 + </div>
  14 + <div class="right">
  15 + <span class="short_message">{{short_message}}</span>
  16 + </div>
  17 + </div>
  18 + <div class="content hidden">
  19 + <div class="_user_id">{{_user_id}}</div>
  20 + <div class="file">{{file}}</div>
  21 + <pre class="full_message">{{full_message}}</pre>
  22 + </div>
  23 + </li>
  24 + {{/each}}
  25 + </script>
  26 +
  27 + <script type="text/javascript" src="/scripts/jquery.js"></script>
  28 + <script type="text/javascript" src="/scripts/handlebars.js"></script>
  29 + <script src="scripts/underscore.js" type="text/javascript" charset="utf-8"></script>
  30 + <script src="scripts/backbone.js" type="text/javascript" charset="utf-8"></script>
  31 +
  32 + <script type="text/javascript" src="/scripts/log_items.js"></script>
  33 +
  34 + <link rel="stylesheet" type="text/css" href="styles/application.css" />
  35 + <link rel="stylesheet" type="text/css" href="styles/log_list.css" />
  36 +</head>
  37 +
  38 +
  39 +
  40 +<body id="index" onload="">
  41 +
  42 + <div id="head">
  43 + </div>
  44 + <div id="navigation" style="overflow-x: auto; overflow-y: auto">
  45 + <div id="nav">
  46 + <p>Navigation....................</p>
  47 + </div>
  48 + </div>
  49 +
  50 +
  51 + <div id="content">
  52 + <div id="content_header">
  53 + </div>
  54 + <div id="content_center">
  55 + <ul class="log_list"></ul>
  56 + </div>
  57 + </div>
  58 +
  59 +</body>
  60 +</html>
48 public/profile.html
... ... @@ -0,0 +1,48 @@
  1 +<!DOCTYPE html>
  2 +<html>
  3 +<head>
  4 + <title>User Profile</title>
  5 +
  6 + <script id="profile_template" type="text/x-handlebars-template">
  7 + <label for="">First Name: </label>
  8 + <span class="firstName">{{firstName}}</span>
  9 + <label for="">Last Name: </label>
  10 + <span class="lastName">{{lastName}}</span>
  11 + <label for="">Email: </label>
  12 + <span class="email">{{email}}</span>
  13 + <label for="">Phone: </label>
  14 + <span class="phone">{{phone}}</span>
  15 + <label for="">Country: </label>
  16 + <span class="country">{{country}}</span>
  17 + </script>
  18 +
  19 + <script type="text/javascript" src="/scripts/jquery.js"></script>
  20 + <script type="text/javascript" src="/scripts/handlebars.js"></script>
  21 + <script src="scripts/underscore.js" type="text/javascript" charset="utf-8"></script>
  22 + <script src="scripts/backbone.js" type="text/javascript" charset="utf-8"></script>
  23 +
  24 + <script type="text/javascript" src="/scripts/profile.js"></script>
  25 +
  26 + <link rel="stylesheet" type="text/css" href="styles/application.css" />
  27 + <link rel="stylesheet" type="text/css" href="styles/log_list.css" />
  28 +</head>
  29 +
  30 +<body id="profile" onload="">
  31 +
  32 + <div id="head">
  33 + </div>
  34 + <div id="navigation">
  35 + <div id="nav">
  36 + </div>
  37 + </div>
  38 + <div id="content">
  39 + <div id="content_header">
  40 + </div>
  41 + <div id="content_center">
  42 + <div class="user"></div>
  43 + </div>
  44 + </div>
  45 +
  46 +</body>
  47 +</html>
  48 +
1,158 public/scripts/backbone.js
... ... @@ -0,0 +1,1158 @@
  1 +// Backbone.js 0.5.3
  2 +// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
  3 +// Backbone may be freely distributed under the MIT license.
  4 +// For all details and documentation:
  5 +// http://documentcloud.github.com/backbone
  6 +
  7 +(function(){
  8 +
  9 + // Initial Setup
  10 + // -------------
  11 +
  12 + // Save a reference to the global object.
  13 + var root = this;
  14 +
  15 + // Save the previous value of the `Backbone` variable.
  16 + var previousBackbone = root.Backbone;
  17 +
  18 + // The top-level namespace. All public Backbone classes and modules will
  19 + // be attached to this. Exported for both CommonJS and the browser.
  20 + var Backbone;
  21 + if (typeof exports !== 'undefined') {
  22 + Backbone = exports;
  23 + } else {
  24 + Backbone = root.Backbone = {};
  25 + }
  26 +
  27 + // Current version of the library. Keep in sync with `package.json`.
  28 + Backbone.VERSION = '0.5.3';
  29 +
  30 + // Require Underscore, if we're on the server, and it's not already present.
  31 + var _ = root._;
  32 + if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
  33 +
  34 + // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
  35 + var $ = root.jQuery || root.Zepto;
  36 +
  37 + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
  38 + // to its previous owner. Returns a reference to this Backbone object.
  39 + Backbone.noConflict = function() {
  40 + root.Backbone = previousBackbone;
  41 + return this;
  42 + };
  43 +
  44 + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will
  45 + // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
  46 + // `X-Http-Method-Override` header.
  47 + Backbone.emulateHTTP = false;
  48 +
  49 + // Turn on `emulateJSON` to support legacy servers that can't deal with direct
  50 + // `application/json` requests ... will encode the body as
  51 + // `application/x-www-form-urlencoded` instead and will send the model in a
  52 + // form param named `model`.
  53 + Backbone.emulateJSON = false;
  54 +
  55 + // Backbone.Events
  56 + // -----------------
  57 +
  58 + // A module that can be mixed in to *any object* in order to provide it with
  59 + // custom events. You may `bind` or `unbind` a callback function to an event;
  60 + // `trigger`-ing an event fires all callbacks in succession.
  61 + //
  62 + // var object = {};
  63 + // _.extend(object, Backbone.Events);
  64 + // object.bind('expand', function(){ alert('expanded'); });
  65 + // object.trigger('expand');
  66 + //
  67 + Backbone.Events = {
  68 +
  69 + // Bind an event, specified by a string name, `ev`, to a `callback` function.
  70 + // Passing `"all"` will bind the callback to all events fired.
  71 + bind : function(ev, callback, context) {
  72 + var calls = this._callbacks || (this._callbacks = {});
  73 + var list = calls[ev] || (calls[ev] = []);
  74 + list.push([callback, context]);
  75 + return this;
  76 + },
  77 +
  78 + // Remove one or many callbacks. If `callback` is null, removes all
  79 + // callbacks for the event. If `ev` is null, removes all bound callbacks
  80 + // for all events.
  81 + unbind : function(ev, callback) {
  82 + var calls;
  83 + if (!ev) {
  84 + this._callbacks = {};
  85 + } else if (calls = this._callbacks) {
  86 + if (!callback) {
  87 + calls[ev] = [];
  88 + } else {
  89 + var list = calls[ev];
  90 + if (!list) return this;
  91 + for (var i = 0, l = list.length; i < l; i++) {
  92 + if (list[i] && callback === list[i][0]) {
  93 + list[i] = null;
  94 + break;
  95 + }
  96 + }
  97 + }
  98 + }
  99 + return this;
  100 + },
  101 +
  102 + // Trigger an event, firing all bound callbacks. Callbacks are passed the
  103 + // same arguments as `trigger` is, apart from the event name.
  104 + // Listening for `"all"` passes the true event name as the first argument.
  105 + trigger : function(eventName) {
  106 + var list, calls, ev, callback, args;
  107 + var both = 2;
  108 + if (!(calls = this._callbacks)) return this;
  109 + while (both--) {
  110 + ev = both ? eventName : 'all';
  111 + if (list = calls[ev]) {
  112 + for (var i = 0, l = list.length; i < l; i++) {
  113 + if (!(callback = list[i])) {
  114 + list.splice(i, 1); i--; l--;
  115 + } else {
  116 + args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
  117 + callback[0].apply(callback[1] || this, args);
  118 + }
  119 + }
  120 + }
  121 + }
  122 + return this;
  123 + }
  124 +
  125 + };
  126 +
  127 + // Backbone.Model
  128 + // --------------
  129 +
  130 + // Create a new model, with defined attributes. A client id (`cid`)
  131 + // is automatically generated and assigned for you.
  132 + Backbone.Model = function(attributes, options) {
  133 + var defaults;
  134 + attributes || (attributes = {});
  135 + if (defaults = this.defaults) {
  136 + if (_.isFunction(defaults)) defaults = defaults.call(this);
  137 + attributes = _.extend({}, defaults, attributes);
  138 + }
  139 + this.attributes = {};
  140 + this._escapedAttributes = {};
  141 + this.cid = _.uniqueId('c');
  142 + this.set(attributes, {silent : true});
  143 + this._changed = false;
  144 + this._previousAttributes = _.clone(this.attributes);
  145 + if (options && options.collection) this.collection = options.collection;
  146 + this.initialize(attributes, options);
  147 + };
  148 +
  149 + // Attach all inheritable methods to the Model prototype.
  150 + _.extend(Backbone.Model.prototype, Backbone.Events, {
  151 +
  152 + // A snapshot of the model's previous attributes, taken immediately
  153 + // after the last `"change"` event was fired.
  154 + _previousAttributes : null,
  155 +
  156 + // Has the item been changed since the last `"change"` event?
  157 + _changed : false,
  158 +
  159 + // The default name for the JSON `id` attribute is `"id"`. MongoDB and
  160 + // CouchDB users may want to set this to `"_id"`.
  161 + idAttribute : 'id',
  162 +
  163 + // Initialize is an empty function by default. Override it with your own
  164 + // initialization logic.
  165 + initialize : function(){},
  166 +
  167 + // Return a copy of the model's `attributes` object.
  168 + toJSON : function() {
  169 + return _.clone(this.attributes);
  170 + },
  171 +
  172 + // Get the value of an attribute.
  173 + get : function(attr) {
  174 + return this.attributes[attr];
  175 + },
  176 +
  177 + // Get the HTML-escaped value of an attribute.
  178 + escape : function(attr) {
  179 + var html;
  180 + if (html = this._escapedAttributes[attr]) return html;
  181 + var val = this.attributes[attr];
  182 + return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
  183 + },
  184 +
  185 + // Returns `true` if the attribute contains a value that is not null
  186 + // or undefined.
  187 + has : function(attr) {
  188 + return this.attributes[attr] != null;
  189 + },
  190 +
  191 + // Set a hash of model attributes on the object, firing `"change"` unless you
  192 + // choose to silence it.
  193 + set : function(attrs, options) {
  194 +
  195 + // Extract attributes and options.
  196 + options || (options = {});
  197 + if (!attrs) return this;
  198 + if (attrs.attributes) attrs = attrs.attributes;
  199 + var now = this.attributes, escaped = this._escapedAttributes;
  200 +
  201 + // Run validation.
  202 + if (!options.silent && this.validate && !this._performValidation(attrs, options)) return false;
  203 +
  204 + // Check for changes of `id`.
  205 + if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
  206 +
  207 + // We're about to start triggering change events.
  208 + var alreadyChanging = this._changing;
  209 + this._changing = true;
  210 +
  211 + // Update attributes.
  212 + for (var attr in attrs) {
  213 + var val = attrs[attr];
  214 + if (!_.isEqual(now[attr], val)) {
  215 + now[attr] = val;
  216 + delete escaped[attr];
  217 + this._changed = true;
  218 + if (!options.silent) this.trigger('change:' + attr, this, val, options);
  219 + }
  220 + }
  221 +
  222 + // Fire the `"change"` event, if the model has been changed.
  223 + if (!alreadyChanging && !options.silent && this._changed) this.change(options);
  224 + this._changing = false;
  225 + return this;
  226 + },
  227 +
  228 + // Remove an attribute from the model, firing `"change"` unless you choose
  229 + // to silence it. `unset` is a noop if the attribute doesn't exist.
  230 + unset : function(attr, options) {
  231 + if (!(attr in this.attributes)) return this;
  232 + options || (options = {});
  233 + var value = this.attributes[attr];
  234 +
  235 + // Run validation.
  236 + var validObj = {};
  237 + validObj[attr] = void 0;
  238 + if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
  239 +
  240 + // Remove the attribute.
  241 + delete this.attributes[attr];
  242 + delete this._escapedAttributes[attr];
  243 + if (attr == this.idAttribute) delete this.id;
  244 + this._changed = true;
  245 + if (!options.silent) {
  246 + this.trigger('change:' + attr, this, void 0, options);
  247 + this.change(options);
  248 + }
  249 + return this;
  250 + },
  251 +
  252 + // Clear all attributes on the model, firing `"change"` unless you choose
  253 + // to silence it.
  254 + clear : function(options) {
  255 + options || (options = {});
  256 + var attr;
  257 + var old = this.attributes;
  258 +
  259 + // Run validation.
  260 + var validObj = {};
  261 + for (attr in old) validObj[attr] = void 0;
  262 + if (!options.silent && this.validate && !this._performValidation(validObj, options)) return false;
  263 +
  264 + this.attributes = {};
  265 + this._escapedAttributes = {};
  266 + this._changed = true;
  267 + if (!options.silent) {
  268 + for (attr in old) {
  269 + this.trigger('change:' + attr, this, void 0, options);
  270 + }
  271 + this.change(options);
  272 + }
  273 + return this;
  274 + },
  275 +
  276 + // Fetch the model from the server. If the server's representation of the
  277 + // model differs from its current attributes, they will be overriden,
  278 + // triggering a `"change"` event.
  279 + fetch : function(options) {
  280 + options || (options = {});
  281 + var model = this;
  282 + var success = options.success;
  283 + options.success = function(resp, status, xhr) {
  284 + if (!model.set(model.parse(resp, xhr), options)) return false;
  285 + if (success) success(model, resp);
  286 + };
  287 + options.error = wrapError(options.error, model, options);
  288 + return (this.sync || Backbone.sync).call(this, 'read', this, options);
  289 + },
  290 +
  291 + // Set a hash of model attributes, and sync the model to the server.
  292 + // If the server returns an attributes hash that differs, the model's
  293 + // state will be `set` again.
  294 + save : function(attrs, options) {
  295 + options || (options = {});
  296 + if (attrs && !this.set(attrs, options)) return false;
  297 + var model = this;
  298 + var success = options.success;
  299 + options.success = function(resp, status, xhr) {
  300 + if (!model.set(model.parse(resp, xhr), options)) return false;
  301 + if (success) success(model, resp, xhr);
  302 + };
  303 + options.error = wrapError(options.error, model, options);
  304 + var method = this.isNew() ? 'create' : 'update';
  305 + return (this.sync || Backbone.sync).call(this, method, this, options);
  306 + },
  307 +
  308 + // Destroy this model on the server if it was already persisted. Upon success, the model is removed
  309 + // from its collection, if it has one.
  310 + destroy : function(options) {
  311 + options || (options = {});
  312 + if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
  313 + var model = this;
  314 + var success = options.success;
  315 + options.success = function(resp) {
  316 + model.trigger('destroy', model, model.collection, options);
  317 + if (success) success(model, resp);
  318 + };
  319 + options.error = wrapError(options.error, model, options);
  320 + return (this.sync || Backbone.sync).call(this, 'delete', this, options);
  321 + },
  322 +
  323 + // Default URL for the model's representation on the server -- if you're
  324 + // using Backbone's restful methods, override this to change the endpoint
  325 + // that will be called.
  326 + url : function() {
  327 + var base = getUrl(this.collection) || this.urlRoot || urlError();
  328 + if (this.isNew()) return base;
  329 + return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
  330 + },
  331 +
  332 + // **parse** converts a response into the hash of attributes to be `set` on
  333 + // the model. The default implementation is just to pass the response along.
  334 + parse : function(resp, xhr) {
  335 + return resp;
  336 + },
  337 +
  338 + // Create a new model with identical attributes to this one.
  339 + clone : function() {
  340 + return new this.constructor(this);
  341 + },
  342 +
  343 + // A model is new if it has never been saved to the server, and lacks an id.
  344 + isNew : function() {
  345 + return this.id == null;
  346 + },
  347 +
  348 + // Call this method to manually fire a `change` event for this model.
  349 + // Calling this will cause all objects observing the model to update.
  350 + change : function(options) {
  351 + this.trigger('change', this, options);
  352 + this._previousAttributes = _.clone(this.attributes);
  353 + this._changed = false;
  354 + },
  355 +
  356 + // Determine if the model has changed since the last `"change"` event.
  357 + // If you specify an attribute name, determine if that attribute has changed.
  358 + hasChanged : function(attr) {
  359 + if (attr) return this._previousAttributes[attr] != this.attributes[attr];
  360 + return this._changed;
  361 + },
  362 +
  363 + // Return an object containing all the attributes that have changed, or false
  364 + // if there are no changed attributes. Useful for determining what parts of a
  365 + // view need to be updated and/or what attributes need to be persisted to
  366 + // the server.
  367 + changedAttributes : function(now) {
  368 + now || (now = this.attributes);
  369 + var old = this._previousAttributes;
  370 + var changed = false;
  371 + for (var attr in now) {
  372 + if (!_.isEqual(old[attr], now[attr])) {
  373 + changed = changed || {};
  374 + changed[attr] = now[attr];
  375 + }
  376 + }
  377 + return changed;
  378 + },
  379 +
  380 + // Get the previous value of an attribute, recorded at the time the last
  381 + // `"change"` event was fired.
  382 + previous : function(attr) {
  383 + if (!attr || !this._previousAttributes) return null;
  384 + return this._previousAttributes[attr];
  385 + },
  386 +
  387 + // Get all of the attributes of the model at the time of the previous
  388 + // `"change"` event.
  389 + previousAttributes : function() {
  390 + return _.clone(this._previousAttributes);
  391 + },
  392 +
  393 + // Run validation against a set of incoming attributes, returning `true`
  394 + // if all is well. If a specific `error` callback has been passed,
  395 + // call that instead of firing the general `"error"` event.
  396 + _performValidation : function(attrs, options) {
  397 + var error = this.validate(attrs);
  398 + if (error) {
  399 + if (options.error) {
  400 + options.error(this, error, options);
  401 + } else {
  402 + this.trigger('error', this, error, options);
  403 + }
  404 + return false;
  405 + }
  406 + return true;
  407 + }
  408 +
  409 + });
  410 +
  411 + // Backbone.Collection
  412 + // -------------------
  413 +
  414 + // Provides a standard collection class for our sets of models, ordered
  415 + // or unordered. If a `comparator` is specified, the Collection will maintain
  416 + // its models in sort order, as they're added and removed.
  417 + Backbone.Collection = function(models, options) {
  418 + options || (options = {});
  419 + if (options.comparator) this.comparator = options.comparator;
  420 + _.bindAll(this, '_onModelEvent', '_removeReference');
  421 + this._reset();
  422 + if (models) this.reset(models, {silent: true});
  423 + this.initialize.apply(this, arguments);
  424 + };
  425 +
  426 + // Define the Collection's inheritable methods.
  427 + _.extend(Backbone.Collection.prototype, Backbone.Events, {
  428 +
  429 + // The default model for a collection is just a **Backbone.Model**.
  430 + // This should be overridden in most cases.
  431 + model : Backbone.Model,
  432 +
  433 + // Initialize is an empty function by default. Override it with your own
  434 + // initialization logic.
  435 + initialize : function(){},
  436 +
  437 + // The JSON representation of a Collection is an array of the
  438 + // models' attributes.
  439 + toJSON : function() {
  440 + return this.map(function(model){ return model.toJSON(); });
  441 + },
  442 +
  443 + // Add a model, or list of models to the set. Pass **silent** to avoid
  444 + // firing the `added` event for every new model.
  445 + add : function(models, options) {
  446 + if (_.isArray(models)) {
  447 + for (var i = 0, l = models.length; i < l; i++) {
  448 + this._add(models[i], options);
  449 + }
  450 + } else {
  451 + this._add(models, options);
  452 + }
  453 + return this;
  454 + },
  455 +
  456 + // Remove a model, or a list of models from the set. Pass silent to avoid
  457 + // firing the `removed` event for every model removed.
  458 + remove : function(models, options) {
  459 + if (_.isArray(models)) {
  460 + for (var i = 0, l = models.length; i < l; i++) {
  461 + this._remove(models[i], options);
  462 + }
  463 + } else {
  464 + this._remove(models, options);
  465 + }
  466 + return this;
  467 + },
  468 +
  469 + // Get a model from the set by id.
  470 + get : function(id) {
  471 + if (id == null) return null;
  472 + return this._byId[id.id != null ? id.id : id];
  473 + },
  474 +
  475 + // Get a model from the set by client id.
  476 + getByCid : function(cid) {
  477 + return cid && this._byCid[cid.cid || cid];
  478 + },
  479 +
  480 + // Get the model at the given index.
  481 + at: function(index) {
  482 + return this.models[index];
  483 + },
  484 +
  485 + // Force the collection to re-sort itself. You don't need to call this under normal
  486 + // circumstances, as the set will maintain sort order as each item is added.
  487 + sort : function(options) {
  488 + options || (options = {});
  489 + if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
  490 + this.models = this.sortBy(this.comparator);
  491 + if (!options.silent) this.trigger('reset', this, options);
  492 + return this;
  493 + },
  494 +
  495 + // Pluck an attribute from each model in the collection.
  496 + pluck : function(attr) {
  497 + return _.map(this.models, function(model){ return model.get(attr); });
  498 + },
  499 +
  500 + // When you have more items than you want to add or remove individually,
  501 + // you can reset the entire set with a new list of models, without firing
  502 + // any `added` or `removed` events. Fires `reset` when finished.
  503 + reset : function(models, options) {
  504 + models || (models = []);
  505 + options || (options = {});
  506 + this.each(this._removeReference);
  507 + this._reset();
  508 + this.add(models, {silent: true});
  509 + if (!options.silent) this.trigger('reset', this, options);
  510 + return this;
  511 + },
  512 +
  513 + // Fetch the default set of models for this collection, resetting the
  514 + // collection when they arrive. If `add: true` is passed, appends the
  515 + // models to the collection instead of resetting.
  516 + fetch : function(options) {
  517 + options || (options = {});
  518 + var collection = this;
  519 + var success = options.success;
  520 + options.success = function(resp, status, xhr) {
  521 + collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
  522 + if (success) success(collection, resp);
  523 + };
  524 + options.error = wrapError(options.error, collection, options);
  525 + return (this.sync || Backbone.sync).call(this, 'read', this, options);
  526 + },
  527 +
  528 + // Create a new instance of a model in this collection. After the model
  529 + // has been created on the server, it will be added to the collection.
  530 + // Returns the model, or 'false' if validation on a new model fails.
  531 + create : function(model, options) {
  532 + var coll = this;
  533 + options || (options = {});
  534 + model = this._prepareModel(model, options);
  535 + if (!model) return false;
  536 + var success = options.success;
  537 + options.success = function(nextModel, resp, xhr) {
  538 + coll.add(nextModel, options);
  539 + if (success) success(nextModel, resp, xhr);
  540 + };
  541 + model.save(null, options);
  542 + return model;
  543 + },
  544 +
  545 + // **parse** converts a response into a list of models to be added to the
  546 + // collection. The default implementation is just to pass it through.
  547 + parse : function(resp, xhr) {
  548 + return resp;
  549 + },
  550 +
  551 + // Proxy to _'s chain. Can't be proxied the same way the rest of the
  552 + // underscore methods are proxied because it relies on the underscore
  553 + // constructor.
  554 + chain: function () {
  555 + return _(this.models).chain();
  556 + },
  557 +
  558 + // Reset all internal state. Called when the collection is reset.
  559 + _reset : function(options) {
  560 + this.length = 0;
  561 + this.models = [];
  562 + this._byId = {};
  563 + this._byCid = {};
  564 + },
  565 +
  566 + // Prepare a model to be added to this collection
  567 + _prepareModel: function(model, options) {
  568 + if (!(model instanceof Backbone.Model)) {
  569 + var attrs = model;
  570 + model = new this.model(attrs, {collection: this});
  571 + if (model.validate && !model._performValidation(attrs, options)) model = false;
  572 + } else if (!model.collection) {
  573 + model.collection = this;
  574 + }
  575 + return model;
  576 + },
  577 +
  578 + // Internal implementation of adding a single model to the set, updating
  579 + // hash indexes for `id` and `cid` lookups.
  580 + // Returns the model, or 'false' if validation on a new model fails.
  581 + _add : function(model, options) {
  582 + options || (options = {});
  583 + model = this._prepareModel(model, options);
  584 + if (!model) return false;
  585 + var already = this.getByCid(model);
  586 + if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
  587 + this._byId[model.id] = model;
  588 + this._byCid[model.cid] = model;
  589 + var index = options.at != null ? options.at :
  590 + this.comparator ? this.sortedIndex(model, this.comparator) :
  591 + this.length;
  592 + this.models.splice(index, 0, model);
  593 + model.bind('all', this._onModelEvent);
  594 + this.length++;
  595 + if (!options.silent) model.trigger('add', model, this, options);
  596 + return model;
  597 + },
  598 +
  599 + // Internal implementation of removing a single model from the set, updating
  600 + // hash indexes for `id` and `cid` lookups.
  601 + _remove : function(model, options) {
  602 + options || (options = {});
  603 + model = this.getByCid(model) || this.get(model);
  604 + if (!model) return null;
  605 + delete this._byId[model.id];
  606 + delete this._byCid[model.cid];
  607 + this.models.splice(this.indexOf(model), 1);
  608 + this.length--;
  609 + if (!options.silent) model.trigger('remove', model, this, options);
  610 + this._removeReference(model);
  611 + return model;
  612 + },
  613 +
  614 + // Internal method to remove a model's ties to a collection.
  615 + _removeReference : function(model) {
  616 + if (this == model.collection) {
  617 + delete model.collection;
  618 + }
  619 + model.unbind('all', this._onModelEvent);
  620 + },
  621 +
  622 + // Internal method called every time a model in the set fires an event.
  623 + // Sets need to update their indexes when models change ids. All other
  624 + // events simply proxy through. "add" and "remove" events that originate
  625 + // in other collections are ignored.
  626 + _onModelEvent : function(ev, model, collection, options) {
  627 + if ((ev == 'add' || ev == 'remove') && collection != this) return;
  628 + if (ev == 'destroy') {
  629 + this._remove(model, options);
  630 + }
  631 + if (model && ev === 'change:' + model.idAttribute) {
  632 + delete this._byId[model.previous(model.idAttribute)];
  633 + this._byId[model.id] = model;
  634 + }
  635 + this.trigger.apply(this, arguments);
  636 + }
  637 +
  638 + });
  639 +
  640 + // Underscore methods that we want to implement on the Collection.
  641 + var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
  642 + 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
  643 + 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
  644 + 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy'];
  645 +
  646 + // Mix in each Underscore method as a proxy to `Collection#models`.
  647 + _.each(methods, function(method) {
  648 + Backbone.Collection.prototype[method] = function() {
  649 + return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
  650 + };
  651 + });
  652 +
  653 + // Backbone.Router
  654 + // -------------------
  655 +
  656 + // Routers map faux-URLs to actions, and fire events when routes are
  657 + // matched. Creating a new one sets its `routes` hash, if not set statically.
  658 + Backbone.Router = function(options) {
  659 + options || (options = {});
  660 + if (options.routes) this.routes = options.routes;
  661 + this._bindRoutes();
  662 + this.initialize.apply(this, arguments);
  663 + };
  664 +
  665 + // Cached regular expressions for matching named param parts and splatted
  666 + // parts of route strings.
  667 + var namedParam = /:([\w\d]+)/g;
  668 + var splatParam = /\*([\w\d]+)/g;
  669 + var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
  670 +
  671 + // Set up all inheritable **Backbone.Router** properties and methods.
  672 + _.extend(Backbone.Router.prototype, Backbone.Events, {
  673 +
  674 + // Initialize is an empty function by default. Override it with your own
  675 + // initialization logic.
  676 + initialize : function(){},
  677 +
  678 + // Manually bind a single named route to a callback. For example:
  679 + //
  680 + // this.route('search/:query/p:num', 'search', function(query, num) {
  681 + // ...
  682 + // });
  683 + //
  684 + route : function(route, name, callback) {
  685 + Backbone.history || (Backbone.history = new Backbone.History);
  686 + if (!_.isRegExp(route)) route = this._routeToRegExp(route);
  687 + Backbone.history.route(route, _.bind(function(fragment) {
  688 + var args = this._extractParameters(route, fragment);
  689 + callback.apply(this, args);
  690 + this.trigger.apply(this, ['route:' + name].concat(args));
  691 + }, this));
  692 + },
  693 +
  694 + // Simple proxy to `Backbone.history` to save a fragment into the history.
  695 + navigate : function(fragment, triggerRoute) {
  696 + Backbone.history.navigate(fragment, triggerRoute);
  697 + },
  698 +
  699 + // Bind all defined routes to `Backbone.history`. We have to reverse the
  700 + // order of the routes here to support behavior where the most general
  701 + // routes can be defined at the bottom of the route map.
  702 + _bindRoutes : function() {
  703 + if (!this.routes) return;
  704 + var routes = [];
  705 + for (var route in this.routes) {
  706 + routes.unshift([route, this.routes[route]]);
  707 + }
  708 + for (var i = 0, l = routes.length; i < l; i++) {
  709 + this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
  710 + }
  711 + },
  712 +
  713 + // Convert a route string into a regular expression, suitable for matching
  714 + // against the current location hash.
  715 + _routeToRegExp : function(route) {
  716 + route = route.replace(escapeRegExp, "\\$&")
  717 + .replace(namedParam, "([^\/]*)")
  718 + .replace(splatParam, "(.*?)");
  719 + return new RegExp('^' + route + '$');
  720 + },
  721 +
  722 + // Given a route, and a URL fragment that it matches, return the array of
  723 + // extracted parameters.
  724 + _extractParameters : function(route, fragment) {
  725 + return route.exec(fragment).slice(1);
  726 + }
  727 +
  728 + });
  729 +
  730 + // Backbone.History
  731 + // ----------------
  732 +
  733 + // Handles cross-browser history management, based on URL fragments. If the
  734 + // browser does not support `onhashchange`, falls back to polling.
  735 + Backbone.History = function() {
  736 + this.handlers = [];
  737 + _.bindAll(this, 'checkUrl');
  738 + };
  739 +
  740 + // Cached regex for cleaning hashes.
  741 + var hashStrip = /^#*/;
  742 +
  743 + // Cached regex for detecting MSIE.
  744 + var isExplorer = /msie [\w.]+/;
  745 +
  746 + // Has the history handling already been started?
  747 + var historyStarted = false;
  748 +
  749 + // Set up all inheritable **Backbone.History** properties and methods.
  750 + _.extend(Backbone.History.prototype, {
  751 +
  752 + // The default interval to poll for hash changes, if necessary, is
  753 + // twenty times a second.
  754 + interval: 50,
  755 +
  756 + // Get the cross-browser normalized URL fragment, either from the URL,
  757 + // the hash, or the override.
  758 + getFragment : function(fragment, forcePushState) {
  759 + if (fragment == null) {
  760 + if (this._hasPushState || forcePushState) {
  761 + fragment = window.location.pathname;
  762 + var search = window.location.search;
  763 + if (search) fragment += search;
  764 + if (fragment.indexOf(this.options.root) == 0) fragment = fragment.substr(this.options.root.length);
  765 + } else {
  766 + fragment = window.location.hash;
  767 + }
  768 + }
  769 + return decodeURIComponent(fragment.replace(hashStrip, ''));
  770 + },
  771 +
  772 + // Start the hash change handling, returning `true` if the current URL matches
  773 + // an existing route, and `false` otherwise.
  774 + start : function(options) {
  775 +
  776 + // Figure out the initial configuration. Do we need an iframe?
  777 + // Is pushState desired ... is it available?
  778 + if (historyStarted) throw new Error("Backbone.history has already been started");
  779 + this.options = _.extend({}, {root: '/'}, this.options, options);
  780 + this._wantsPushState = !!this.options.pushState;
  781 + this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
  782 + var fragment = this.getFragment();
  783 + var docMode = document.documentMode;
  784 + var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
  785 + if (oldIE) {
  786 + this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
  787 + this.navigate(fragment);
  788 + }
  789 +
  790 + // Depending on whether we're using pushState or hashes, and whether
  791 + // 'onhashchange' is supported, determine how we check the URL state.
  792 + if (this._hasPushState) {
  793 + $(window).bind('popstate', this.checkUrl);
  794 + } else if ('onhashchange' in window && !oldIE) {
  795 + $(window).bind('hashchange', this.checkUrl);
  796 + } else {
  797 + setInterval(this.checkUrl, this.interval);
  798 + }
  799 +
  800 + // Determine if we need to change the base url, for a pushState link
  801 + // opened by a non-pushState browser.
  802 + this.fragment = fragment;
  803 + historyStarted = true;
  804 + var loc = window.location;
  805 + var atRoot = loc.pathname == this.options.root;
  806 + if (this._wantsPushState && !this._hasPushState && !atRoot) {
  807 + this.fragment = this.getFragment(null, true);
  808 + window.location.replace(this.options.root + '#' + this.fragment);
  809 + // Return immediately as browser will do redirect to new url
  810 + return true;
  811 + } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
  812 + this.fragment = loc.hash.replace(hashStrip, '');
  813 + window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
  814 + }
  815 +
  816 + if (!this.options.silent) {
  817 + return this.loadUrl();
  818 + }
  819 + },
  820 +
  821 + // Add a route to be tested when the fragment changes. Routes added later may
  822 + // override previous routes.
  823 + route : function(route, callback) {
  824 + this.handlers.unshift({route : route, callback : callback});
  825 + },
  826 +
  827 + // Checks the current URL to see if it has changed, and if it has,
  828 + // calls `loadUrl`, normalizing across the hidden iframe.
  829 + checkUrl : function(e) {
  830 + var current = this.getFragment();
  831 + if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash);
  832 + if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false;
  833 + if (this.iframe) this.navigate(current);
  834 + this.loadUrl() || this.loadUrl(window.location.hash);
  835 + },
  836 +
  837 + // Attempt to load the current URL fragment. If a route succeeds with a
  838 + // match, returns `true`. If no defined routes matches the fragment,
  839 + // returns `false`.
  840 + loadUrl : function(fragmentOverride) {
  841 + var fragment = this.fragment = this.getFragment(fragmentOverride);
  842 + var matched = _.any(this.handlers, function(handler) {
  843 + if (handler.route.test(fragment)) {
  844 + handler.callback(fragment);
  845 + return true;
  846 + }
  847 + });
  848 + return matched;
  849 + },
  850 +
  851 + // Save a fragment into the hash history. You are responsible for properly
  852 + // URL-encoding the fragment in advance. This does not trigger
  853 + // a `hashchange` event.
  854 + navigate : function(fragment, triggerRoute) {
  855 + var frag = (fragment || '').replace(hashStrip, '');
  856 + if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
  857 + if (this._hasPushState) {
  858 + var loc = window.location;
  859 + if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
  860 + this.fragment = frag;
  861 + window.history.pushState({}, document.title, loc.protocol + '//' + loc.host + frag);
  862 + } else {
  863 + window.location.hash = this.fragment = frag;
  864 + if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
  865 + this.iframe.document.open().close();
  866 + this.iframe.location.hash = frag;
  867 + }
  868 + }
  869 + if (triggerRoute) this.loadUrl(fragment);
  870 + }
  871 +
  872 + });
  873 +
  874 + // Backbone.View
  875 + // -------------
  876 +
  877 + // Creating a Backbone.View creates its initial element outside of the DOM,
  878 + // if an existing element is not provided...
  879 + Backbone.View = function(options) {
  880 + this.cid = _.uniqueId('view');
  881 + this._configure(options || {});
  882 + this._ensureElement();
  883 + this.delegateEvents();
  884 + this.initialize.apply(this, arguments);
  885 + };
  886 +
  887 + // Element lookup, scoped to DOM elements within the current view.
  888 + // This should be prefered to global lookups, if you're dealing with
  889 + // a specific view.
  890 + var selectorDelegate = function(selector) {
  891 + return $(selector, this.el);
  892 + };
  893 +
  894 + // Cached regex to split keys for `delegate`.
  895 + var eventSplitter = /^(\S+)\s*(.*)$/;
  896 +
  897 + // List of view options to be merged as properties.
  898 + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
  899 +
  900 + // Set up all inheritable **Backbone.View** properties and methods.
  901 + _.extend(Backbone.View.prototype, Backbone.Events, {
  902 +
  903 + // The default `tagName` of a View's element is `"div"`.
  904 + tagName : 'div',
  905 +
  906 + // Attach the `selectorDelegate` function as the `$` property.
  907 + $ : selectorDelegate,
  908 +
  909 + // Initialize is an empty function by default. Override it with your own
  910 + // initialization logic.
  911 + initialize : function(){},
  912 +
  913 + // **render** is the core function that your view should override, in order
  914 + // to populate its element (`this.el`), with the appropriate HTML. The
  915 + // convention is for **render** to always return `this`.
  916 + render : function() {
  917 + return this;
  918 + },
  919 +
  920 + // Remove this view from the DOM. Note that the view isn't present in the
  921 + // DOM by default, so calling this method may be a no-op.
  922 + remove : function() {
  923 + $(this.el).remove();
  924 + return this;
  925 + },
  926 +
  927 + // For small amounts of DOM Elements, where a full-blown template isn't
  928 + // needed, use **make** to manufacture elements, one at a time.
  929 + //
  930 + // var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
  931 + //
  932 + make : function(tagName, attributes, content) {
  933 + var el = document.createElement(tagName);
  934 + if (attributes) $(el).attr(attributes);
  935 + if (content) $(el).html(content);
  936 + return el;
  937 + },
  938 +
  939 + // Set callbacks, where `this.callbacks` is a hash of
  940 + //
  941 + // *{"event selector": "callback"}*
  942 + //
  943 + // {
  944 + // 'mousedown .title': 'edit',
  945 + // 'click .button': 'save'
  946 + // }
  947 + //
  948 + // pairs. Callbacks will be bound to the view, with `this` set properly.
  949 + // Uses event delegation for efficiency.
  950 + // Omitting the selector binds the event to `this.el`.
  951 + // This only works for delegate-able events: not `focus`, `blur`, and
  952 + // not `change`, `submit`, and `reset` in Internet Explorer.
  953 + delegateEvents : function(events) {
  954 + if (!(events || (events = this.events))) return;
  955 + if (_.isFunction(events)) events = events.call(this);
  956 + $(this.el).unbind('.delegateEvents' + this.cid);
  957 + for (var key in events) {
  958 + var method = this[events[key]];
  959 + if (!method) throw new Error('Event "' + events[key] + '" does not exist');
  960 + var match = key.match(eventSplitter);
  961 + var eventName = match[1], selector = match[2];
  962 + method = _.bind(method, this);
  963 + eventName += '.delegateEvents' + this.cid;
  964 + if (selector === '') {
  965 + $(this.el).bind(eventName, method);
  966 + } else {
  967 + $(this.el).delegate(selector, eventName, method);
  968 + }
  969 + }
  970 + },
  971 +
  972 + // Performs the initial configuration of a View with a set of options.
  973 + // Keys with special meaning *(model, collection, id, className)*, are
  974 + // attached directly to the view.
  975 + _configure : function(options) {
  976 + if (this.options) options = _.extend({}, this.options, options);
  977 + for (var i = 0, l = viewOptions.length; i < l; i++) {
  978 + var attr = viewOptions[i];
  979 + if (options[attr]) this[attr] = options[attr];
  980 + }
  981 + this.options = options;
  982 + },
  983 +
  984 + // Ensure that the View has a DOM element to render into.
  985 + // If `this.el` is a string, pass it through `$()`, take the first
  986 + // matching element, and re-assign it to `el`. Otherwise, create
  987 + // an element from the `id`, `className` and `tagName` proeprties.
  988 + _ensureElement : function() {
  989 + if (!this.el) {
  990 + var attrs = this.attributes || {};
  991 + if (this.id) attrs.id = this.id;
  992 + if (this.className) attrs['class'] = this.className;
  993 + this.el = this.make(this.tagName, attrs);
  994 + } else if (_.isString(this.el)) {
  995 + this.el = $(this.el).get(0);
  996 + }
  997 + }
  998 +
  999 + });
  1000 +
  1001 + // The self-propagating extend function that Backbone classes use.
  1002 + var extend = function (protoProps, classProps) {
  1003 + var child = inherits(this, protoProps, classProps);
  1004 + child.extend = this.extend;
  1005 + return child;
  1006 + };
  1007 +
  1008 + // Set up inheritance for the model, collection, and view.
  1009 + Backbone.Model.extend = Backbone.Collection.extend =
  1010 + Backbone.Router.extend = Backbone.View.extend = extend;
  1011 +
  1012 + // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
  1013 + var methodMap = {
  1014 + 'create': 'POST',
  1015 + 'update': 'PUT',
  1016 + 'delete': 'DELETE',
  1017 + 'read' : 'GET'
  1018 + };
  1019 +
  1020 + // Backbone.sync
  1021 + // -------------
  1022 +
  1023 + // Override this function to change the manner in which Backbone persists
  1024 + // models to the server. You will be passed the type of request, and the
  1025 + // model in question. By default, uses makes a RESTful Ajax request
  1026 + // to the model's `url()`. Some possible customizations could be:
  1027 + //
  1028 + // * Use `setTimeout` to batch rapid-fire updates into a single request.
  1029 + // * Send up the models as XML instead of JSON.
  1030 + // * Persist models via WebSockets instead of Ajax.
  1031 + //
  1032 + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
  1033 + // as `POST`, with a `_method` parameter containing the true HTTP method,
  1034 + // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
  1035 + // `application/json` with the model in a param named `model`.
  1036 + // Useful when interfacing with server-side languages like **PHP** that make
  1037 + // it difficult to read the body of `PUT` requests.
  1038 + Backbone.sync = function(method, model, options) {
  1039