Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

updating to use patched Backbone and Underscore builds, adding Jasmin…

…e, updating to latest r.js so its no longer using edge, rewriting readme
  • Loading branch information...
commit 7fc3701f60207d070b8809ad8289992ac5195ce4 1 parent 8fff2ea
@addyosmani authored
View
BIN  .DS_Store
Binary file not shown
View
64 Readme.rdoc
@@ -1,26 +1,60 @@
-Template project for backbone with require.js:
-==========================================
-This project demonstrates how to use require.js with backbone.js and then replace require.js with almond.js in production.
-See the app.build.js file in public/js to see how the project is built. Note that the included require.js is currently an edge version.
+##Backback
-To build the javascript files you can do:
+Backpack is a boilerplate project for personal Backbone.js projects which includes some common items I use in my setup. It is based on a fork of jbasdf's Backbone.js template.
+
+Included in the backpack are:
+=============================
+
+* Backbone.js (AMD patched)
+* Underscore.js (AMD patched)
+* jQuery.js
+* Require.js (latest)
+* i18n.js plugin for RequireJS
+* text plugin for RequireJS
+* jQuery Cookies plugin
+* Almond
+* r.js with instructions to build the project
+* Jasmine for BDD testing
+
+
+Summary
+===========
+
+The build process will run the application through r.js, replacing require.js with almond.js in production. Information about how the project is built can be found in the app.build.js file in ```public\js```.
+
+Building
+===========
+
+If you have node installed, the project can be built by running:
node public/js/app.build.js
-However I'm lazy and like to type less so there's also a rake task:
+If you would prefer a rake task that completes this task, try:
rake build
-
-Usage:
-=============
-This project comes with a basic sinatra app for serving assets. If you have ruby and bundler installed just do:
- bundle install
+Using the project
+===================
+
+The backpack comes with a Sinatra application for serving up assets. You'll need both [ruby](http://www.ruby-lang.org/en/downloads/) and bundler installed in order to run this. To get bundler, simply run:
-To run the project
+ gem install bundler
+
+Follow the instructions at the ruby link above to download and install that dependency. The project can then be run using:
ruby app.rb
-
-Then visit the following 3 urls:
+
+ This will give you the ability to access three URLs:
+
http://localhost:4567
http://localhost:4567/dev
-http://localhost:4567/jquery <-- this file demonstrates how currently require.js will prefer the jquery loaded in the page to the one explicitly defined in the project.
+http://localhost:4567/jquery <-- this file demonstrates how currently require.js will prefer the jquery loaded in the page to the one explicitly defined in the project.
+
+Patched AMD Backbone and Underscore builds
+===========================================
+
+Backpack already contains patched AMD-compatible versions of Backbone.js and Underscore.js but if wish to grab the latest patched versions of these libraries they can be accessed from:
+
+* https://github.com/amdjs/underscore
+* https://github.com/amdjs/backbone
+
+
View
3  app.rb
@@ -15,4 +15,5 @@
get '/bookmarklet' do
File.read('bookmarklet.html')
-end
+end
+
View
5 bookmarklet.html
@@ -3,11 +3,6 @@
<head>
<title></title>
<meta http-equiv="content-type" content="text/xhtml; charset=utf-8" />
- <meta http-equiv="imagetoolbar" content="no" />
- <meta name="distribution" content="all" />
- <meta name="robots" content="all" />
- <meta name="resource-type" content="document" />
- <meta name="MSSmartTagsPreventParsing" content="true" />
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.js"></script>
</head>
<body>
View
5 dev.html
@@ -3,11 +3,6 @@
<head>
<title></title>
<meta http-equiv="content-type" content="text/xhtml; charset=utf-8" />
- <meta http-equiv="imagetoolbar" content="no" />
- <meta name="distribution" content="all" />
- <meta name="robots" content="all" />
- <meta name="resource-type" content="document" />
- <meta name="MSSmartTagsPreventParsing" content="true" />
</head>
<body>
<div id="container">This file doesn't attempt to compress or merge the javascript</div>
View
4 index.html
@@ -4,10 +4,6 @@
<title></title>
<meta http-equiv="content-type" content="text/xhtml; charset=utf-8" />
<meta http-equiv="imagetoolbar" content="no" />
- <meta name="distribution" content="all" />
- <meta name="robots" content="all" />
- <meta name="resource-type" content="document" />
- <meta name="MSSmartTagsPreventParsing" content="true" />
</head>
<body>
<div id="container">This shows all files built with almond</div>
View
5 jquery.html
@@ -3,11 +3,6 @@
<head>
<title></title>
<meta http-equiv="content-type" content="text/xhtml; charset=utf-8" />
- <meta http-equiv="imagetoolbar" content="no" />
- <meta name="distribution" content="all" />
- <meta name="robots" content="all" />
- <meta name="resource-type" content="document" />
- <meta name="MSSmartTagsPreventParsing" content="true" />
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.js"></script>
</head>
<body>
View
0  npm-debug.log
No changes.
View
BIN  public/.DS_Store
Binary file not shown
View
BIN  public/js/.DS_Store
Binary file not shown
View
14 public/js/app.build.js
@@ -12,17 +12,17 @@ var requirejs = require('./r.js');
//Set up basic config, include config that is
//common to all the requirejs.optimize() calls.
var basConfig = {
- baseUrl: "public/js",
- locale: "en-us",
- optimize: "uglify",
+ baseUrl: "public/js",
+ locale: "en-us",
+ optimize: "uglify",
//optimize: "none", // If you need to debug the compiled script
//namespace: "test", // If using Almond then no need to namespace
wrap: true,
paths: {
- 'jquery': 'libs/jquery/jquery',
- 'underscore': 'libs/underscore/underscore',
- 'backbone': 'libs/backbone/backbone',
- 'templates': '../templates'
+ 'jquery': 'libs/jquery/jquery',
+ 'underscore': 'libs/underscore/underscore',
+ 'backbone': 'libs/backbone/backbone',
+ 'templates': '../templates'
},
//All the built layers will use almond.
View
6 public/js/app_main.js
@@ -1,7 +1,7 @@
define(['jquery',
- 'underscore',
- 'backbone',
- './routers/main_router'], function($, _, Backbone, MainRouter){
+ 'underscore',
+ 'backbone',
+ './routers/main_router'], function($, _, Backbone, MainRouter){
return {
init: function(){
new MainRouter();
View
364 public/js/libs/backbone/backbone.js
@@ -4,7 +4,6 @@
// For all details and documentation:
// http://documentcloud.github.com/backbone
-
(function(root, factory) {
// Set up Backbone appropriately for the environment.
if (typeof exports !== 'undefined') {
@@ -13,7 +12,9 @@
} else if (typeof define === 'function' && define.amd) {
// AMD
define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
- factory(root, exports, _, $);
+ // Export global even in AMD case in case this script is loaded with
+ // others that may still expect a global Backbone.
+ root.Backbone = factory(root, exports, _, $);
});
} else {
// Browser globals
@@ -27,8 +28,9 @@
// Save the previous value of the `Backbone` variable.
var previousBackbone = root.Backbone;
- // Create a local reference to slice.
+ // Create a local reference to slice/splice.
var slice = Array.prototype.slice;
+ var splice = Array.prototype.splice;
// Current version of the library. Keep in sync with `package.json`.
Backbone.VERSION = '0.5.3';
@@ -40,9 +42,9 @@
return Backbone;
};
- // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will
- // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
- // `X-Http-Method-Override` header.
+ // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+ // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+ // set a `X-Http-Method-Override` header.
Backbone.emulateHTTP = false;
// Turn on `emulateJSON` to support legacy servers that can't deal with direct
@@ -55,44 +57,53 @@
// -----------------
// A module that can be mixed in to *any object* in order to provide it with
- // custom events. You may `bind` or `unbind` a callback function to an event;
- // `trigger`-ing an event fires all callbacks in succession.
+ // custom events. You may bind with `on` or remove with `off` callback functions
+ // to an event; trigger`-ing an event fires all callbacks in succession.
//
// var object = {};
// _.extend(object, Backbone.Events);
- // object.bind('expand', function(){ alert('expanded'); });
+ // object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
Backbone.Events = {
- // Bind an event, specified by a string name, `ev`, to a `callback` function.
- // Passing `"all"` will bind the callback to all events fired.
- bind : function(ev, callback, context) {
+ // Bind an event, specified by a string name, `ev`, to a `callback`
+ // function. Passing `"all"` will bind the callback to all events fired.
+ on : function(events, callback, context) {
+ var ev;
+ events = events.split(/\s+/);
var calls = this._callbacks || (this._callbacks = {});
- var list = calls[ev] || (calls[ev] = {});
- var tail = list.tail || (list.tail = list.next = {});
- tail.callback = callback;
- tail.context = context;
- list.tail = tail.next = {};
+ while (ev = events.shift()) {
+ // Create an immutable callback list, allowing traversal during
+ // modification. The tail is an empty object that will always be used
+ // as the next node.
+ var list = calls[ev] || (calls[ev] = {});
+ var tail = list.tail || (list.tail = list.next = {});
+ tail.callback = callback;
+ tail.context = context;
+ list.tail = tail.next = {};
+ }
return this;
},
- // Remove one or many callbacks. If `callback` is null, removes all
- // callbacks for the event. If `ev` is null, removes all bound callbacks
- // for all events.
- unbind : function(ev, callback) {
- var calls, node, prev;
- if (!ev) {
- this._callbacks = null;
+ // Remove one or many callbacks. If `context` is null, removes all callbacks
+ // with that function. If `callback` is null, removes all callbacks for the
+ // event. If `ev` is null, removes all bound callbacks for all events.
+ off : function(events, callback, context) {
+ var ev, calls, node;
+ if (!events) {
+ delete this._callbacks;
} else if (calls = this._callbacks) {
- if (!callback) {
- calls[ev] = {};
- } else if (node = calls[ev]) {
- while ((prev = node) && (node = node.next)) {
- if (node.callback !== callback) continue;
- prev.next = node.next;
- node.context = node.callback = null;
- break;
+ events = events.split(/\s+/);
+ while (ev = events.shift()) {
+ node = calls[ev];
+ delete calls[ev];
+ if (!callback || !node) continue;
+ // Create a new list, omitting the indicated event/context pairs.
+ while ((node = node.next) && node.next) {
+ if (node.callback === callback &&
+ (!context || node.context === context)) continue;
+ this.on(ev, node.callback, node.context);
}
}
}
@@ -102,19 +113,35 @@
// Trigger an event, firing all bound callbacks. Callbacks are passed the
// same arguments as `trigger` is, apart from the event name.
// Listening for `"all"` passes the true event name as the first argument.
- trigger : function(eventName) {
- var node, calls, callback, args, ev, events = ['all', eventName];
+ trigger : function(events) {
+ var event, node, calls, tail, args, all, rest;
if (!(calls = this._callbacks)) return this;
- while (ev = events.pop()) {
- if (!(node = calls[ev])) continue;
- args = ev == 'all' ? arguments : slice.call(arguments, 1);
- while (node = node.next) if (callback = node.callback) callback.apply(node.context || this, args);
+ all = calls['all'];
+ (events = events.split(/\s+/)).push(null);
+ // Save references to the current heads & tails.
+ while (event = events.shift()) {
+ if (all) events.push({next: all.next, tail: all.tail, event: event});
+ if (!(node = calls[event])) continue;
+ events.push({next: node.next, tail: node.tail});
+ }
+ // Traverse each list, stopping when the saved tail is reached.
+ rest = slice.call(arguments, 1);
+ while (node = events.pop()) {
+ tail = node.tail;
+ args = node.event ? [node.event].concat(rest) : rest;
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, args);
+ }
}
return this;
}
};
+ // Aliases for backwards compatibility.
+ Backbone.Events.bind = Backbone.Events.on;
+ Backbone.Events.unbind = Backbone.Events.off;
+
// Backbone.Model
// --------------
@@ -127,14 +154,14 @@
if (defaults = getValue(this, 'defaults')) {
attributes = _.extend({}, defaults, attributes);
}
+ if (options && options.collection) this.collection = options.collection;
this.attributes = {};
this._escapedAttributes = {};
this.cid = _.uniqueId('c');
this.set(attributes, {silent : true});
this._changed = false;
this._previousAttributes = _.clone(this.attributes);
- if (options && options.collection) this.collection = options.collection;
- this.initialize(attributes, options);
+ this.initialize.apply(this, arguments);
};
// Attach all inheritable methods to the Model prototype.
@@ -175,10 +202,10 @@
return this.attributes[attr] != null;
},
- // Set a hash of model attributes on the object, firing `"change"` unless you
- // choose to silence it.
+ // Set a hash of model attributes on the object, firing `"change"` unless
+ // you choose to silence it.
set : function(key, value, options) {
- var attrs;
+ var attrs, attr, val;
if (_.isObject(key) || key == null) {
attrs = key;
options = value;
@@ -205,16 +232,22 @@
this._changing = true;
// Update attributes.
- for (var attr in attrs) {
- var val = attrs[attr];
+ var changes = {};
+ for (attr in attrs) {
+ val = attrs[attr];
if (!_.isEqual(now[attr], val) || (options.unset && (attr in now))) {
options.unset ? delete now[attr] : now[attr] = val;
delete escaped[attr];
this._changed = true;
- if (!options.silent) this.trigger('change:' + attr, this, val, options);
+ changes[attr] = val;
}
}
+ // Fire `change:attribute` events.
+ for (var attr in changes) {
+ if (!options.silent) this.trigger('change:' + attr, this, changes[attr], options);
+ }
+
// Fire the `"change"` event, if the model has been changed.
if (!alreadyChanging) {
if (!options.silent && this._changed) this.change(options);
@@ -241,14 +274,14 @@
// model differs from its current attributes, they will be overriden,
// triggering a `"change"` event.
fetch : function(options) {
- options || (options = {});
+ options = options ? _.clone(options) : {};
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp);
};
- options.error = wrapError(options.error, model, options);
+ options.error = Backbone.wrapError(options.error, model, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
@@ -256,31 +289,39 @@
// If the server returns an attributes hash that differs, the model's
// state will be `set` again.
save : function(attrs, options) {
- options || (options = {});
+ options = options ? _.clone(options) : {};
if (attrs && !this.set(attrs, options)) return false;
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
if (!model.set(model.parse(resp, xhr), options)) return false;
- if (success) success(model, resp, xhr);
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
};
- options.error = wrapError(options.error, model, options);
+ options.error = Backbone.wrapError(options.error, model, options);
var method = this.isNew() ? 'create' : 'update';
return (this.sync || Backbone.sync).call(this, method, this, options);
},
- // Destroy this model on the server if it was already persisted. Upon success, the model is removed
- // from its collection, if it has one.
+ // Destroy this model on the server if it was already persisted.
+ // Upon success, the model is removed from its collection, if it has one.
destroy : function(options) {
- options || (options = {});
+ options = options ? _.clone(options) : {};
if (this.isNew()) return this.trigger('destroy', this, this.collection, options);
var model = this;
var success = options.success;
options.success = function(resp) {
model.trigger('destroy', model, model.collection, options);
- if (success) success(model, resp);
+ if (success) {
+ success(model, resp);
+ } else {
+ model.trigger('sync', model, resp, options);
+ }
};
- options.error = wrapError(options.error, model, options);
+ options.error = Backbone.wrapError(options.error, model, options);
return (this.sync || Backbone.sync).call(this, 'delete', this, options);
},
@@ -288,7 +329,7 @@
// using Backbone's restful methods, override this to change the endpoint
// that will be called.
url : function() {
- var base = getValue(this.collection, 'url') || this.urlRoot || urlError();
+ var base = getValue(this.collection, 'url') || getValue(this, 'urlRoot') || urlError();
if (this.isNew()) return base;
return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
},
@@ -320,14 +361,14 @@
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
hasChanged : function(attr) {
- if (attr) return this._previousAttributes[attr] != this.attributes[attr];
+ if (attr) return !_.isEqual(this._previousAttributes[attr], this.attributes[attr]);
return this._changed;
},
- // 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.
+ // 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.
changedAttributes : function(now) {
if (!this._changed) return false;
now || (now = this.attributes);
@@ -382,10 +423,9 @@
Backbone.Collection = function(models, options) {
options || (options = {});
if (options.comparator) this.comparator = options.comparator;
- _.bindAll(this, '_onModelEvent', '_removeReference');
this._reset();
- if (models) this.reset(models, {silent: true});
this.initialize.apply(this, arguments);
+ if (models) this.reset(models, {silent: true});
};
// Define the Collection's inheritable methods.
@@ -408,13 +448,28 @@
// Add a model, or list of models to the set. Pass **silent** to avoid
// firing the `added` event for every new model.
add : function(models, options) {
- if (_.isArray(models)) {
- for (var i = 0, l = models.length; i < l; i++) {
- if (options && (options.at == +options.at) && i) options.at += 1;
- this._add(models[i], options);
+ var i, index, length;
+ options || (options = {});
+ if (!_.isArray(models)) models = [models];
+ models = slice.call(models);
+ for (i = 0, length = models.length; i < length; i++) {
+ var model = models[i] = this._prepareModel(models[i], options);
+ var hasId = model.id != null;
+ if (this._byCid[model.cid] || (hasId && this._byId[model.id])) {
+ throw new Error("Can't add the same model to a set twice");
}
- } else {
- this._add(models, options);
+ this._byCid[model.cid] = model;
+ if (hasId) this._byId[model.id] = model;
+ model.on('all', this._onModelEvent, this);
+ }
+ this.length += length;
+ index = options.at != null ? options.at : this.models.length;
+ splice.apply(this.models, [index, 0].concat(models));
+ if (this.comparator) this.sort({silent: true});
+ if (options.silent) return this;
+ for (i = 0; i < length; i++) {
+ options.index = index + i;
+ models[i].trigger('add', models[i], this, options);
}
return this;
},
@@ -422,12 +477,22 @@
// Remove a model, or a list of models from the set. Pass silent to avoid
// firing the `removed` event for every model removed.
remove : function(models, options) {
- if (_.isArray(models)) {
- for (var i = 0, l = models.length; i < l; i++) {
- this._remove(models[i], options);
+ var i, index, model;
+ options || (options = {});
+ if (!_.isArray(models)) models = [models];
+ for (i = 0, l = models.length; i < l; i++) {
+ model = this.getByCid(models[i]) || this.get(models[i]);
+ if (!model) continue;
+ delete this._byId[model.id];
+ delete this._byCid[model.cid];
+ index = this.indexOf(model);
+ this.models.splice(index, 1);
+ this.length--;
+ if (!options.silent) {
+ options.index = index;
+ model.trigger('remove', model, this, options);
}
- } else {
- this._remove(models, options);
+ this._removeReference(model);
}
return this;
},
@@ -448,12 +513,18 @@
return this.models[index];
},
- // 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.
+ // 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.
sort : function(options) {
options || (options = {});
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
- this.models = this.sortBy(this.comparator);
+ var boundComparator = _.bind(this.comparator, this);
+ if (this.comparator.length == 1) {
+ this.models = this.sortBy(boundComparator);
+ } else {
+ this.models.sort(boundComparator);
+ }
if (!options.silent) this.trigger('reset', this, options);
return this;
},
@@ -469,7 +540,9 @@
reset : function(models, options) {
models || (models = []);
options || (options = {});
- this.each(this._removeReference);
+ for (var i = 0, l = this.models.length; i < l; i++) {
+ this._removeReference(this.models[i]);
+ }
this._reset();
this.add(models, {silent: true, parse: options.parse});
if (!options.silent) this.trigger('reset', this, options);
@@ -480,7 +553,7 @@
// collection when they arrive. If `add: true` is passed, appends the
// models to the collection instead of resetting.
fetch : function(options) {
- options || (options = {});
+ options = options ? _.clone(options) : {};
if (options.parse === undefined) options.parse = true;
var collection = this;
var success = options.success;
@@ -488,7 +561,7 @@
collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
if (success) success(collection, resp);
};
- options.error = wrapError(options.error, collection, options);
+ options.error = Backbone.wrapError(options.error, collection, options);
return (this.sync || Backbone.sync).call(this, 'read', this, options);
},
@@ -497,13 +570,17 @@
// Returns the model, or 'false' if validation on a new model fails.
create : function(model, options) {
var coll = this;
- options || (options = {});
+ options = options ? _.clone(options) : {};
model = this._prepareModel(model, options);
if (!model) return false;
var success = options.success;
options.success = function(nextModel, resp, xhr) {
coll.add(nextModel, options);
- if (success) success(nextModel, resp, xhr);
+ if (success) {
+ success(nextModel, resp);
+ } else {
+ nextModel.trigger('sync', model, resp, options);
+ }
};
model.save(null, options);
return model;
@@ -534,7 +611,8 @@
_prepareModel : function(model, options) {
if (!(model instanceof Backbone.Model)) {
var attrs = model;
- model = new this.model(attrs, {collection: this, parse: options.parse});
+ options.collection = this;
+ model = new this.model(attrs, options);
if (model.validate && !model._performValidation(model.attributes, options)) model = false;
} else if (!model.collection) {
model.collection = this;
@@ -542,51 +620,12 @@
return model;
},
- // Internal implementation of adding a single model to the set, updating
- // hash indexes for `id` and `cid` lookups.
- // Returns the model, or 'false' if validation on a new model fails.
- _add : function(model, options) {
- options || (options = {});
- model = this._prepareModel(model, options);
- if (!model) return false;
- var already = this.getByCid(model);
- if (already) throw new Error(["Can't add the same model to a set twice", already.id]);
- this._byId[model.id] = model;
- this._byCid[model.cid] = model;
- var index = options.at != null ? options.at :
- this.comparator ? this.sortedIndex(model, this.comparator) :
- this.length;
- this.models.splice(index, 0, model);
- model.bind('all', this._onModelEvent);
- this.length++;
- options.index = index;
- if (!options.silent) model.trigger('add', model, this, options);
- return model;
- },
-
- // Internal implementation of removing a single model from the set, updating
- // hash indexes for `id` and `cid` lookups.
- _remove : function(model, options) {
- options || (options = {});
- model = this.getByCid(model) || this.get(model);
- if (!model) return null;
- delete this._byId[model.id];
- delete this._byCid[model.cid];
- var index = this.indexOf(model);
- this.models.splice(index, 1);
- this.length--;
- options.index = index;
- if (!options.silent) model.trigger('remove', model, this, options);
- this._removeReference(model);
- return model;
- },
-
// Internal method to remove a model's ties to a collection.
_removeReference : function(model) {
if (this == model.collection) {
delete model.collection;
}
- model.unbind('all', this._onModelEvent);
+ model.off('all', this._onModelEvent, this);
},
// Internal method called every time a model in the set fires an event.
@@ -596,7 +635,7 @@
_onModelEvent : function(ev, model, collection, options) {
if ((ev == 'add' || ev == 'remove') && collection != this) return;
if (ev == 'destroy') {
- this._remove(model, options);
+ this.remove(model, options);
}
if (model && ev === 'change:' + model.idAttribute) {
delete this._byId[model.previous(model.idAttribute)];
@@ -608,10 +647,11 @@
});
// Underscore methods that we want to implement on the Collection.
- var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', 'detect',
- 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
- 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', 'toArray', 'size',
- 'first', 'rest', 'last', 'without', 'indexOf', 'lastIndexOf', 'isEmpty', 'groupBy'];
+ var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+ 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+ 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+ 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+ 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
@@ -658,6 +698,7 @@
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
+ Backbone.history.trigger('route', this, name, args);
}, this));
},
@@ -707,8 +748,8 @@
_.bindAll(this, 'checkUrl');
};
- // Cached regex for cleaning hashes.
- var hashStrip = /^#/;
+ // Cached regex for cleaning leading hashes and slashes .
+ var routeStripper = /^[#\/]/;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
@@ -717,7 +758,7 @@
var historyStarted = false;
// Set up all inheritable **Backbone.History** properties and methods.
- _.extend(Backbone.History.prototype, {
+ _.extend(Backbone.History.prototype, Backbone.Events, {
// The default interval to poll for hash changes, if necessary, is
// twenty times a second.
@@ -735,7 +776,7 @@
fragment = window.location.hash;
}
}
- fragment = decodeURIComponent(fragment.replace(hashStrip, ''));
+ fragment = decodeURIComponent(fragment.replace(routeStripper, ''));
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
return fragment;
},
@@ -748,6 +789,7 @@
// Is pushState desired ... is it available?
if (historyStarted) throw new Error("Backbone.history has already been started");
this.options = _.extend({}, {root: '/'}, this.options, options);
+ this._wantsHashChange = this.options.hashChange !== false;
this._wantsPushState = !!this.options.pushState;
this._hasPushState = !!(this.options.pushState && window.history && window.history.pushState);
var fragment = this.getFragment();
@@ -762,9 +804,9 @@
// 'onhashchange' is supported, determine how we check the URL state.
if (this._hasPushState) {
$(window).bind('popstate', this.checkUrl);
- } else if ('onhashchange' in window && !oldIE) {
+ } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
$(window).bind('hashchange', this.checkUrl);
- } else {
+ } else if (this._wantsHashChange) {
setInterval(this.checkUrl, this.interval);
}
@@ -774,13 +816,13 @@
historyStarted = true;
var loc = window.location;
var atRoot = loc.pathname == this.options.root;
- if (this._wantsPushState && !this._hasPushState && !atRoot) {
+ if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
this.fragment = this.getFragment(null, true);
window.location.replace(this.options.root + '#' + this.fragment);
// Return immediately as browser will do redirect to new url
return true;
} else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
- this.fragment = loc.hash.replace(hashStrip, '');
+ this.fragment = loc.hash.replace(routeStripper, '');
window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
}
@@ -789,8 +831,8 @@
}
},
- // Add a route to be tested when the fragment changes. Routes added later may
- // override previous routes.
+ // Add a route to be tested when the fragment changes. Routes added later
+ // may override previous routes.
route : function(route, callback) {
this.handlers.unshift({route : route, callback : callback});
},
@@ -828,13 +870,13 @@
// you which to modify the current URL without adding an entry to the history.
navigate : function(fragment, options) {
if (!options || options === true) options = {trigger: options};
- var frag = (fragment || '').replace(hashStrip, '');
+ var frag = (fragment || '').replace(routeStripper, '');
if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return;
if (this._hasPushState) {
if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
this.fragment = frag;
window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
- } else {
+ } else if (this._wantsHashChange) {
this.fragment = frag;
this._updateHash(window.location, frag, options.replace);
if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) {
@@ -843,6 +885,8 @@
if(!options.replace) this.iframe.document.open().close();
this._updateHash(this.iframe.location, frag, options.replace);
}
+ } else {
+ window.location.assign(this.options.root + fragment);
}
if (options.trigger) this.loadUrl(fragment);
},
@@ -867,8 +911,8 @@
this.cid = _.uniqueId('view');
this._configure(options || {});
this._ensureElement();
- this.delegateEvents();
this.initialize.apply(this, arguments);
+ this.delegateEvents();
};
// Cached regex to split keys for `delegate`.
@@ -926,6 +970,7 @@
// {
// 'mousedown .title': 'edit',
// 'click .button': 'save'
+ // 'click .open': function(e) { ... }
// }
//
// pairs. Callbacks will be bound to the view, with `this` set properly.
@@ -937,7 +982,8 @@
if (!(events || (events = getValue(this, 'events')))) return;
this.undelegateEvents();
for (var key in events) {
- var method = this[events[key]];
+ var method = events[key];
+ if (!_.isFunction(method)) method = this[events[key]];
if (!method) throw new Error('Event "' + events[key] + '" does not exist');
var match = key.match(eventSplitter);
var eventName = match[1], selector = match[2];
@@ -974,7 +1020,7 @@
// an element from the `id`, `className` and `tagName` properties.
_ensureElement : function() {
if (!this.el) {
- var attrs = this.attributes || {};
+ var attrs = getValue(this, 'attributes') || {};
if (this.id) attrs.id = this.id;
if (this.className) attrs['class'] = this.className;
this.el = this.make(this.tagName, attrs);
@@ -1018,8 +1064,8 @@
//
// Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
// as `POST`, with a `_method` parameter containing the true HTTP method,
- // as well as all requests with the body as `application/x-www-form-urlencoded` instead of
- // `application/json` with the model in a param named `model`.
+ // as well as all requests with the body as `application/x-www-form-urlencoded`
+ // instead of `application/json` with the model in a param named `model`.
// Useful when interfacing with server-side languages like **PHP** that make
// it difficult to read the body of `PUT` requests.
Backbone.sync = function(method, model, options) {
@@ -1066,6 +1112,18 @@
return $.ajax(_.extend(params, options));
};
+ // Wrap an optional error callback with a fallback error event.
+ Backbone.wrapError = function(onError, originalModel, options) {
+ return function(model, resp) {
+ var resp = model === originalModel ? resp : model;
+ if (onError) {
+ onError(model, resp, options);
+ } else {
+ originalModel.trigger('error', model, resp, options);
+ }
+ };
+ };
+
// Helpers
// -------
@@ -1080,7 +1138,7 @@
// The constructor function for the new subclass is either defined by you
// (the "constructor" property in your `extend` definition), or defaulted
- // by us to simply call `super()`.
+ // by us to simply call the parent's constructor.
if (protoProps && protoProps.hasOwnProperty('constructor')) {
child = protoProps.constructor;
} else {
@@ -1123,17 +1181,5 @@
throw new Error('A "url" property or function must be specified');
};
- // Wrap an optional error callback with a fallback error event.
- var wrapError = function(onError, originalModel, options) {
- return function(model, resp) {
- var resp = model === originalModel ? resp : model;
- if (onError) {
- onError(model, resp, options);
- } else {
- originalModel.trigger('error', model, resp, options);
- }
- };
- };
-
return Backbone;
}));
View
88 public/js/libs/underscore/underscore.js
@@ -1,5 +1,5 @@
-// Underscore.js 1.2.3
-// (c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore.js 1.3.1
+// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
@@ -25,7 +25,6 @@
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
- concat = ArrayProto.concat,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
@@ -51,24 +50,25 @@
// Export the Underscore object for **Node.js** and **"CommonJS"**, with
// backwards-compatibility for the old `require()` API. If we're not in
- // CommonJS, add `_` to the global object.
+ // CommonJS, add `_` to the global object via a string identifier for
+ // the Closure Compiler "advanced" mode, and optionally register as an
+ // AMD module via define().
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
- } else if (typeof define === 'function' && define.amd) {
- // Register as a named module with AMD.
- define('underscore', function() {
- return _;
- });
} else {
- // Exported as a string, for Closure Compiler "advanced" mode.
+ if (typeof define === 'function' && define.amd) {
+ define('underscore', function() {
+ return _;
+ });
+ }
root['_'] = _;
}
// Current version.
- _.VERSION = '1.2.3';
+ _.VERSION = '1.3.1';
// Collection Functions
// --------------------
@@ -86,7 +86,7 @@
}
} else {
for (var key in obj) {
- if (hasOwnProperty.call(obj, key)) {
+ if (_.has(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
@@ -95,13 +95,14 @@
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
- _.map = function(obj, iterator, context) {
+ _.map = _.collect = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
+ if (obj.length === +obj.length) results.length = obj.length;
return results;
};
@@ -218,7 +219,7 @@
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
- return (method.call ? method || value : value[method]).apply(value, args);
+ return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
@@ -511,7 +512,7 @@
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
- return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
+ return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
@@ -583,7 +584,7 @@
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
- var args = concat.apply([func], arguments);
+ var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
@@ -617,7 +618,7 @@
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
- for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
+ for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
return keys;
};
@@ -640,7 +641,7 @@
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
- if (source[prop] !== void 0) obj[prop] = source[prop];
+ obj[prop] = source[prop];
}
});
return obj;
@@ -738,17 +739,17 @@
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
- if (hasOwnProperty.call(a, key)) {
+ if (_.has(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
- if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
+ if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
- if (hasOwnProperty.call(b, key) && !(size--)) break;
+ if (_.has(b, key) && !(size--)) break;
}
result = !size;
}
@@ -767,7 +768,7 @@
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
- for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
+ for (var key in obj) if (_.has(obj, key)) return false;
return true;
};
@@ -793,7 +794,7 @@
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
- return !!(obj && hasOwnProperty.call(obj, 'callee'));
+ return !!(obj && _.has(obj, 'callee'));
};
}
@@ -843,6 +844,11 @@
return obj === void 0;
};
+ // Has own property?
+ _.has = function(obj, key) {
+ return hasOwnProperty.call(obj, key);
+ };
+
// Utility Functions
// -----------------
@@ -892,6 +898,17 @@
escape : /<%-([\s\S]+?)%>/g
};
+ // When customizing `templateSettings`, if you don't want to define an
+ // interpolation, evaluation or escaping regex, we need one that is
+ // guaranteed not to match.
+ var noMatch = /.^/;
+
+ // Within an interpolation, evaluation, or escaping, remove HTML escaping
+ // that had been previously added.
+ var unescape = function(code) {
+ return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
+ };
+
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
@@ -901,15 +918,14 @@
'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
- .replace(c.escape, function(match, code) {
- return "',_.escape(" + code.replace(/\\'/g, "'") + "),'";
+ .replace(c.escape || noMatch, function(match, code) {
+ return "',_.escape(" + unescape(code) + "),'";
})
- .replace(c.interpolate, function(match, code) {
- return "'," + code.replace(/\\'/g, "'") + ",'";
+ .replace(c.interpolate || noMatch, function(match, code) {
+ return "'," + unescape(code) + ",'";
})
- .replace(c.evaluate || null, function(match, code) {
- return "');" + code.replace(/\\'/g, "'")
- .replace(/[\r\n\t]/g, ' ') + ";__p.push('";
+ .replace(c.evaluate || noMatch, function(match, code) {
+ return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
@@ -922,6 +938,11 @@
};
};
+ // Add a "chain" function, which will delegate to the wrapper.
+ _.chain = function(obj) {
+ return _(obj).chain();
+ };
+
// The OOP Wrapper
// ---------------
@@ -954,8 +975,11 @@
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
- method.apply(this._wrapped, arguments);
- return result(this._wrapped, this._chain);
+ var wrapped = this._wrapped;
+ method.apply(wrapped, arguments);
+ var length = wrapped.length;
+ if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
+ return result(wrapped, this._chain);
};
});
View
12,099 public/js/min-main.js
1 addition, 12,098 deletions not shown
View
12,100 public/js/min-main_too.js
1 addition, 12,099 deletions not shown
View
4 public/js/models/asset.js
@@ -1,6 +1,6 @@
define(['jquery',
- 'underscore',
- 'backbone'], function($, _, Backbone){
+ 'underscore',
+ 'backbone'], function($, _, Backbone){
return Backbone.Model.extend({
View
76 public/js/r.js
@@ -1,5 +1,5 @@
/**
- * @license r.js 1.0.3 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
+ * @license r.js 1.0.4 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
@@ -20,7 +20,7 @@ var requirejs, require, define;
var fileName, env, fs, vm, path, exec, rhinoContext, dir, nodeRequire,
nodeDefine, exists, reqMain, loadedOptimizedLib,
- version = '1.0.3',
+ version = '1.0.4',
jsSuffixRegExp = /\.js$/,
commandOption = '',
//Used by jslib/rhino/args.js
@@ -101,7 +101,7 @@ var requirejs, require, define;
}
/** vim: et:ts=4:sw=4:sts=4
- * @license RequireJS 1.0.3 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
+ * @license RequireJS 1.0.4 Copyright (c) 2010-2011, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
@@ -113,7 +113,7 @@ var requirejs, require, define;
(function () {
//Change this version number for each release.
- var version = "1.0.3",
+ var version = "1.0.4",
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /require\(\s*["']([^'"\s]+)["']\s*\)/g,
currDirRegExp = /^\.\//,
@@ -425,7 +425,15 @@ var requirejs, require, define;
url = urlMap[normalizedName];
if (!url) {
//Calculate url for the module, if it has a name.
- url = context.nameToUrl(normalizedName, null, parentModuleMap);
+ //Use name here since nameToUrl also calls normalize,
+ //and for relative names that are outside the baseUrl
+ //this causes havoc. Was thinking of just removing
+ //parentModuleMap to avoid extra normalization, but
+ //normalize() still does a dot removal because of
+ //issue #142, so just pass in name here and redo
+ //the normalization. Paths outside baseUrl are just
+ //messy to support.
+ url = context.nameToUrl(name, null, parentModuleMap);
//Store the URL mapping for later.
urlMap[normalizedName] = url;
@@ -730,7 +738,7 @@ var requirejs, require, define;
prefix = map.prefix,
plugin = prefix ? plugins[prefix] ||
(plugins[prefix] = defined[prefix]) : null,
- manager, created, pluginManager;
+ manager, created, pluginManager, prefixMap;
if (fullName) {
manager = managerCallbacks[fullName];
@@ -768,7 +776,18 @@ var requirejs, require, define;
//If there is a plugin needed, but it is not loaded,
//first load the plugin, then continue on.
if (prefix && !plugin) {
- pluginManager = getManager(makeModuleMap(prefix), true);
+ prefixMap = makeModuleMap(prefix);
+
+ //Clear out defined and urlFetched if the plugin was previously
+ //loaded/defined, but not as full module (as in a build
+ //situation). However, only do this work if the plugin is in
+ //defined but does not have a module export value.
+ if (prefix in defined && !defined[prefix]) {
+ delete defined[prefix];
+ delete urlFetched[prefixMap.url];
+ }
+
+ pluginManager = getManager(prefixMap, true);
pluginManager.add(function (plugin) {
//Create a new manager for the normalized
//resource ID and have it call this manager when
@@ -1839,7 +1858,8 @@ var requirejs, require, define;
node = context && context.config && context.config.xhtml ?
document.createElementNS("http://www.w3.org/1999/xhtml", "html:script") :
document.createElement("script");
- node.type = type || "text/javascript";
+ node.type = type || (context && context.config.scriptType) ||
+ "text/javascript";
node.charset = "utf-8";
//Use async so Gecko does not block on executing the script if something
//like a long-polling comet tag is being run first. Gecko likes
@@ -3055,7 +3075,7 @@ define('uglifyjs/parse-js', ["require", "exports", "module"], function(require,
disclaimer in the documentation and/or other materials
provided with the distribution.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
@@ -4471,7 +4491,7 @@ exports.ast_squeeze_more = ast_squeeze_more;
disclaimer in the documentation and/or other materials
provided with the distribution.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
@@ -7669,7 +7689,7 @@ function (lang, logger, envOptimize, file, parse,
logger.trace("Uglifying file: " + fileName);
try {
- ast = parser.parse(fileContents, config);
+ ast = parser.parse(fileContents, config.strict_semicolons);
ast = processor.ast_mangle(ast, config);
ast = processor.ast_squeeze(ast, config);
@@ -7852,8 +7872,13 @@ function (file, pragma, parse) {
//and to make sure this file is first, so that define calls work.
//This situation mainly occurs when the build is done on top of the output
//of another build, where the first build may include require somewhere in it.
- if (!layer.existingRequireUrl && parse.definesRequire(url, contents)) {
- layer.existingRequireUrl = url;
+ try {
+ if (!layer.existingRequireUrl && parse.definesRequire(url, contents)) {
+ layer.existingRequireUrl = url;
+ }
+ } catch (e1) {
+ throw new Error('Parse error using UglifyJS ' +
+ 'for file: ' + url + '\n' + e1);
}
if (moduleName in context.plugins) {
@@ -7870,12 +7895,17 @@ function (file, pragma, parse) {
//Parse out the require and define calls.
//Do this even for plugins in case they have their own
//dependencies that may be separate to how the pluginBuilder works.
- if (!context.needFullExec[moduleName]) {
- contents = parse(moduleName, url, contents, {
- insertNeedsDefine: true,
- has: context.config.has,
- findNestedDependencies: context.config.findNestedDependencies
- });
+ try {
+ if (!context.needFullExec[moduleName]) {
+ contents = parse(moduleName, url, contents, {
+ insertNeedsDefine: true,
+ has: context.config.has,
+ findNestedDependencies: context.config.findNestedDependencies
+ });
+ }
+ } catch (e2) {
+ throw new Error('Parse error using UglifyJS ' +
+ 'for file: ' + url + '\n' + e2);
}
require._cachedFileContents[url] = contents;
@@ -7939,8 +7969,8 @@ function (file, pragma, parse) {
} else if (map.url && require._isSupportedBuildUrl(map.url)) {
//If the url has not been added to the layer yet, and it
//is from an actual file that was loaded, add it now.
+ url = normalizeUrlWithBase(context, map.fullName, map.url);
if (!layer.pathAdded[url] && layer.buildPathMap[fullName]) {
- url = normalizeUrlWithBase(context, map.fullName, map.url);
//Remember the list of dependencies for this layer.
layer.buildFilePaths.push(url);
layer.pathAdded[url] = true;
@@ -8697,6 +8727,10 @@ function (lang, logger, file, parse, optimize, pragma,
throw new Error("ERROR: 'baseUrl' option missing.");
}
+ if (!config.out && !config.dir) {
+ throw new Error('Missing either an "out" or "dir" config value.');
+ }
+
if (config.out && !config.cssIn) {
//Just one file to optimize.
@@ -9223,4 +9257,4 @@ function (args, build) {
}((typeof console !== 'undefined' ? console : undefined),
(typeof Packages !== 'undefined' ? Array.prototype.slice.call(arguments, 0) : []),
- (typeof readFile !== 'undefined' ? readFile : undefined)));
+ (typeof readFile !== 'undefined' ? readFile : undefined)));
View
12 public/js/routers/main_router.js
@@ -1,13 +1,13 @@
define(['jquery',
- 'underscore',
- 'backbone',
- "../models/asset",
- "../views/main/main_view",
- "../views/global/error"], function($, _, Backbone, Asset, MainView, ErrorMessage){
+ 'underscore',
+ 'backbone',
+ "../models/asset",
+ "../views/main/main_view",
+ "../views/global/error"], function($, _, Backbone, Asset, MainView, ErrorMessage){
return Backbone.Router.extend({
routes: {
- "": "index"
+ "": "index"
},
initialize: function(){
View
20 public/tests/jasmine/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
190 public/tests/jasmine/jasmine-html.js
@@ -0,0 +1,190 @@
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
+ this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) { el.appendChild(child); }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var showPassed, showSkipped;
+
+ this.outerDiv = this.createDom('div', { className: 'jasmine_reporter' },
+ this.createDom('div', { className: 'banner' },
+ this.createDom('div', { className: 'logo' },
+ this.createDom('span', { className: 'title' }, "Jasmine"),
+ this.createDom('span', { className: 'version' }, runner.env.versionString())),
+ this.createDom('div', { className: 'options' },
+ "Show ",
+ showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+ showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+ )
+ ),
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' },
+ this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+ this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+ this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+ );
+
+ this.document.body.appendChild(this.outerDiv);
+
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+ this.suiteDivs[suite.id] = suiteDiv;
+ var parentDiv = this.outerDiv;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.id];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+
+ this.startedAt = new Date();
+
+ var self = this;
+ showPassed.onclick = function(evt) {
+ if (showPassed.checked) {
+ self.outerDiv.className += ' show-passed';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+ }
+ };
+
+ showSkipped.onclick = function(evt) {
+ if (showSkipped.checked) {
+ self.outerDiv.className += ' show-skipped';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+ }
+ };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+ var results = runner.results();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ //do it twice for IE
+ this.runnerDiv.setAttribute("className", className);
+ var specs = runner.specs();
+ var specCount = 0;
+ for (var i = 0; i < specs.length; i++) {
+ if (this.specFilter(specs[i])) {
+ specCount++;
+ }
+ }
+ var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+ this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+ this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+ var results = suite.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount === 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+ if (this.logRunningSpecs) {
+ this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+ var results = spec.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(spec.getFullName()),
+ title: spec.getFullName()
+ }, spec.description));
+
+
+ var resultItems = results.getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ specDiv.appendChild(messagesDiv);
+ }
+
+ this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap.spec) {
+ return true;
+ }
+ return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
View
166 public/tests/jasmine/jasmine.css
@@ -0,0 +1,166 @@
+body {
+ font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif;
+}
+
+
+.jasmine_reporter a:visited, .jasmine_reporter a {
+ color: #303;
+}
+
+.jasmine_reporter a:hover, .jasmine_reporter a:active {
+ color: blue;
+}
+
+.run_spec {
+ float:right;
+ padding-right: 5px;
+ font-size: .8em;
+ text-decoration: none;
+}
+
+.jasmine_reporter {
+ margin: 0 5px;
+}
+
+.banner {
+ color: #303;
+ background-color: #fef;
+ padding: 5px;
+}
+
+.logo {
+ float: left;
+ font-size: 1.1em;
+ padding-left: 5px;
+}
+
+.logo .version {
+ font-size: .6em;
+ padding-left: 1em;
+}
+
+.runner.running {
+ background-color: yellow;
+}
+
+
+.options {
+ text-align: right;
+ font-size: .8em;
+}
+
+
+
+
+.suite {
+ border: 1px outset gray;
+ margin: 5px 0;
+ padding-left: 1em;
+}
+
+.suite .suite {
+ margin: 5px;
+}
+
+.suite.passed {
+ background-color: #dfd;
+}
+
+.suite.failed {
+ background-color: #fdd;
+}
+
+.spec {
+ margin: 5px;
+ padding-left: 1em;
+ clear: both;
+}
+
+.spec.failed, .spec.passed, .spec.skipped {
+ padding-bottom: 5px;
+ border: 1px solid gray;
+}
+
+.spec.failed {
+ background-color: #fbb;
+ border-color: red;
+}
+
+.spec.passed {
+ background-color: #bfb;
+ border-color: green;
+}
+
+.spec.skipped {
+ background-color: #bbb;
+}
+
+.messages {
+ border-left: 1px dashed gray;
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+.passed {
+ background-color: #cfc;
+ display: none;
+}
+
+.failed {
+ background-color: #fbb;
+}
+
+.skipped {
+ color: #777;
+ background-color: #eee;
+ display: none;
+}
+
+
+/*.resultMessage {*/
+ /*white-space: pre;*/
+/*}*/
+
+.resultMessage span.result {
+ display: block;
+ line-height: 2em;
+ color: black;
+}
+
+.resultMessage .mismatch {
+ color: black;
+}
+
+.stackTrace {
+ white-space: pre;
+ font-size: .8em;
+ margin-left: 10px;
+ max-height: 5em;
+ overflow: auto;
+ border: 1px inset red;
+ padding: 1em;
+ background: #eef;
+}
+
+.finished-at {
+ padding-left: 1em;
+ font-size: .6em;
+}
+
+.show-passed .passed,
+.show-skipped .skipped {
+ display: block;
+}
+
+
+#jasmine_content {
+ position:fixed;
+ right: 100%;
+}
+
+.runner {
+ border: 1px solid gray;
+ display: block;
+ margin: 5px 0;
+ padding: 2px 0 2px 10px;
+}
View
2,476 public/tests/jasmine/jasmine.js
@@ -0,0 +1,2476 @@
+var isCommonJS = typeof window == "undefined";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use <code>jasmine.undefined</code> instead of <code>undefined</code>, since <code>undefined</code> is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for (var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+ this.message = this.passed_ ? 'Passed.' : params.message;
+
+ var trace = (params.trace || new Error(this.message));
+ this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+ return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do <b>not</b> call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.