diff --git a/README.md b/README.md
index 6c70c5a1..61e1a7e1 100755
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ Possible breaking changes
* Dataset.restore method removed (not used internally except from Multiview.restore)
* Views no longer call render in initialize but must be called client code
* Backend.Memory.Store attribute for holding 'records' renamed to `records` from `data`
+* Require new underscore.deferred vendor library for all use (jQuery no longer required if just using recline.dataset.js)
### v0.5 - July 5th 2012 (first public release)
diff --git a/_includes/recline-deps.html b/_includes/recline-deps.html
index af8baeeb..3abbb84d 100644
--- a/_includes/recline-deps.html
+++ b/_includes/recline-deps.html
@@ -22,6 +22,7 @@
+
diff --git a/download.markdown b/download.markdown
index db8a7e67..8ad29dde 100644
--- a/download.markdown
+++ b/download.markdown
@@ -68,15 +68,23 @@ title: Download
### Dependencies
-Recline has dependencies on some third-party libraries, notably JQuery and Backbone:
+Recline has dependencies on some third-party libraries. Specifically, recline.dataset.js depends on:
+
+* [Underscore](http://documentcloud.github.com/underscore/) >= 1.0
+* [Underscore Deferred](https://github.com/wookiehangover/underscore.deferred) v0.4.0
+* [Backbone](http://backbonejs.org/) >= 0.5.1
+
+Those backends which utilize jquery's ajax method depend on jQuery:
* [JQuery](http://jquery.com/) >= 1.6
-* [Backbone](http://backbonejs.org/) >= 0.5.1
-* [Underscore](http://documentcloud.github.com/underscore/) >= 1.0
-Optional dependencies:
+All the views require, in addition to those needed for recline.dataset.js:
+* [JQuery](http://jquery.com/) >= 1.6
* [Mustache.js](https://github.com/janl/mustache.js/) >= 0.5.0-dev (required for all views)
+
+Individual views have additional dependencies such as:
+
* [JQuery Flot](http://code.google.com/p/flot/) >= 0.7 (required for for graph view)
* [Leaflet](http://leaflet.cloudmade.com/) >= 0.4.4 (required for map view)
* [Leaflet.markercluster](https://github.com/danzel/Leaflet.markercluster) as of 2012-09-12 (required for marker clustering)
@@ -84,7 +92,8 @@ Optional dependencies:
* [Bootstrap](http://twitter.github.com/bootstrap/) >= v2.0 (default option for CSS and UI JS but you can use your own)
If you grab the full zipball for Recline this will include all of the relevant
-dependencies in the vendor directory.
+dependencies in the vendor directory and you can also find them at in the
+[github repo here](https://github.com/okfn/recline/tree/master/vendor).
### Example
diff --git a/src/backend.ckan.js b/src/backend.ckan.js
index 89a65a2c..dafe6ecd 100644
--- a/src/backend.ckan.js
+++ b/src/backend.ckan.js
@@ -2,7 +2,7 @@ this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.Ckan = this.recline.Backend.Ckan || {};
-(function($, my) {
+(function(my) {
// ## CKAN Backend
//
// This provides connection to the CKAN DataStore (v2)
@@ -41,7 +41,7 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {};
dataset.id = out.resource_id;
var wrapper = my.DataStore(out.endpoint);
}
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0});
jqxhr.done(function(results) {
// map ckan types to our usual types ...
@@ -84,7 +84,7 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {};
var wrapper = my.DataStore(out.endpoint);
}
var actualQuery = my._normalizeQuery(queryObj, dataset);
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var jqxhr = wrapper.search(actualQuery);
jqxhr.done(function(results) {
var out = {
@@ -107,7 +107,7 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {};
};
that.search = function(data) {
var searchUrl = that.endpoint + '/3/action/datastore_search';
- var jqxhr = $.ajax({
+ var jqxhr = jQuery.ajax({
url: searchUrl,
data: data,
dataType: 'json'
@@ -136,4 +136,4 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {};
'float8': 'float'
};
-}(jQuery, this.recline.Backend.Ckan));
+}(this.recline.Backend.Ckan));
diff --git a/src/backend.couchdb.js b/src/backend.couchdb.js
index 0ef3f625..8c2c9ad9 100755
--- a/src/backend.couchdb.js
+++ b/src/backend.couchdb.js
@@ -197,7 +197,7 @@ my.__type__ = 'couchdb';
var db_url = dataset.db_url;
var view_url = dataset.view_url;
var cdb = new my.CouchDBWrapper(db_url, view_url);
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
// if 'doc' attribute is present, return schema of that
// else return schema of 'value' attribute which contains
@@ -239,7 +239,7 @@ my.__type__ = 'couchdb';
//
//
my.save = function (changes, dataset) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var total = changes.creates.length + changes.updates.length + changes.deletes.length;
var results = {'done': [], 'fail': [] };
@@ -280,7 +280,7 @@ my.save = function (changes, dataset) {
// @param {Object} recline.Dataset instance
// @param {Object} recline.Query instance.
my.query = function(queryObj, dataset) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var db_url = dataset.db_url;
var view_url = dataset.view_url;
var query_options = dataset.query_options;
@@ -475,7 +475,7 @@ function randomId(length, chars) {
}
_createDocument = function (new_doc, dataset) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var db_url = dataset.db_url;
var view_url = dataset.view_url;
var _id = new_doc['id'];
@@ -497,7 +497,7 @@ _createDocument = function (new_doc, dataset) {
};
_updateDocument = function (new_doc, dataset) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var db_url = dataset.db_url;
var view_url = dataset.view_url;
var _id = new_doc['id'];
@@ -527,7 +527,7 @@ _updateDocument = function (new_doc, dataset) {
};
_deleteDocument = function (del_doc, dataset) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var db_url = dataset.db_url;
var view_url = dataset.view_url;
var _id = del_doc['id'];
@@ -557,4 +557,4 @@ _deleteDocument = function (del_doc, dataset) {
return dfd.promise();
}
};
-}(jQuery, this.recline.Backend.CouchDB));
\ No newline at end of file
+}(jQuery, this.recline.Backend.CouchDB));
diff --git a/src/backend.csv.js b/src/backend.csv.js
index 81b9771b..83e71d9c 100644
--- a/src/backend.csv.js
+++ b/src/backend.csv.js
@@ -3,7 +3,7 @@ this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.CSV = this.recline.Backend.CSV || {};
// Note that provision of jQuery is optional (it is **only** needed if you use fetch on a remote file)
-(function(my, $) {
+(function(my) {
// ## fetch
//
@@ -11,7 +11,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {};
//
// 1. `dataset.file`: `file` is an HTML5 file object. This is opened and parsed with the CSV parser.
// 2. `dataset.data`: `data` is a string in CSV format. This is passed directly to the CSV parser
- // 3. `dataset.url`: a url to an online CSV file that is ajax accessible (note this usually requires either local or on a server that is CORS enabled). The file is then loaded using $.ajax and parsed using the CSV parser (NB: this requires jQuery)
+ // 3. `dataset.url`: a url to an online CSV file that is ajax accessible (note this usually requires either local or on a server that is CORS enabled). The file is then loaded using jQuery.ajax and parsed using the CSV parser (NB: this requires jQuery)
//
// All options generates similar data and use the memory store outcome, that is they return something like:
//
@@ -23,7 +23,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {};
// }
//
my.fetch = function(dataset) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
if (dataset.file) {
var reader = new FileReader();
var encoding = dataset.encoding || 'UTF-8';
@@ -48,7 +48,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {};
useMemoryStore: true
});
} else if (dataset.url) {
- $.get(dataset.url).done(function(data) {
+ jQuery.get(dataset.url).done(function(data) {
var rows = my.parseCSV(data, dataset);
dfd.resolve({
records: rows,
@@ -285,4 +285,4 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {};
}
-}(this.recline.Backend.CSV, jQuery));
+}(this.recline.Backend.CSV));
diff --git a/src/backend.dataproxy.js b/src/backend.dataproxy.js
index 58f537d7..b8f17826 100644
--- a/src/backend.dataproxy.js
+++ b/src/backend.dataproxy.js
@@ -2,7 +2,7 @@ this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {};
-(function($, my) {
+(function(my) {
my.__type__ = 'dataproxy';
// URL for the dataproxy
my.dataproxy_url = 'http://jsonpdataproxy.appspot.com';
@@ -21,12 +21,12 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {};
'max-results': dataset.size || dataset.rows || 1000,
type: dataset.format || ''
};
- var jqxhr = $.ajax({
+ var jqxhr = jQuery.ajax({
url: my.dataproxy_url,
data: data,
dataType: 'jsonp'
});
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
_wrapInTimeout(jqxhr).done(function(results) {
if (results.error) {
dfd.reject(results.error);
@@ -50,7 +50,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {};
// Many of backends use JSONP and so will not get error messages and this is
// a crude way to catch those errors.
var _wrapInTimeout = function(ourFunction) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var timer = setTimeout(function() {
dfd.reject({
message: 'Request Error: Backend did not respond after ' + (my.timeout / 1000) + ' seconds'
@@ -68,4 +68,4 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {};
return dfd.promise();
}
-}(jQuery, this.recline.Backend.DataProxy));
+}(this.recline.Backend.DataProxy));
diff --git a/src/backend.elasticsearch.js b/src/backend.elasticsearch.js
index 9037917f..56075a3c 100644
--- a/src/backend.elasticsearch.js
+++ b/src/backend.elasticsearch.js
@@ -179,7 +179,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
// ### fetch
my.fetch = function(dataset) {
var es = new my.Wrapper(dataset.url, my.esOptions);
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
es.mapping().done(function(schema) {
if (!schema){
@@ -207,7 +207,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
my.save = function(changes, dataset) {
var es = new my.Wrapper(dataset.url, my.esOptions);
if (changes.creates.length + changes.updates.length + changes.deletes.length > 1) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
msg = 'Saving more than one item at a time not yet supported';
alert(msg);
dfd.reject(msg);
@@ -225,7 +225,7 @@ this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
// ### query
my.query = function(queryObj, dataset) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var es = new my.Wrapper(dataset.url, my.esOptions);
var jqxhr = es.query(queryObj);
jqxhr.done(function(results) {
diff --git a/src/backend.gdocs.js b/src/backend.gdocs.js
index 4a36c302..3f1812a8 100644
--- a/src/backend.gdocs.js
+++ b/src/backend.gdocs.js
@@ -2,7 +2,7 @@ this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
-(function($, my) {
+(function(my) {
my.__type__ = 'gdocs';
// ## Google spreadsheet backend
@@ -29,15 +29,15 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
// * fields: array of Field objects
// * records: array of objects for each row
my.fetch = function(dataset) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var urls = my.getGDocsAPIUrls(dataset.url);
// TODO cover it with tests
// get the spreadsheet title
(function () {
- var titleDfd = $.Deferred();
+ var titleDfd = new _.Deferred();
- $.getJSON(urls.spreadsheet, function (d) {
+ jQuery.getJSON(urls.spreadsheet, function (d) {
titleDfd.resolve({
spreadsheetTitle: d.feed.title.$t
});
@@ -47,7 +47,7 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
}()).then(function (response) {
// get the actual worksheet data
- $.getJSON(urls.worksheet, function(d) {
+ jQuery.getJSON(urls.worksheet, function(d) {
var result = my.parseData(d);
var fields = _.map(result.fields, function(fieldId) {
return {id: fieldId};
@@ -161,4 +161,4 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
return urls;
};
-}(jQuery, this.recline.Backend.GDocs));
+}(this.recline.Backend.GDocs));
diff --git a/src/backend.memory.js b/src/backend.memory.js
index 6dedae38..07773e77 100644
--- a/src/backend.memory.js
+++ b/src/backend.memory.js
@@ -2,7 +2,7 @@ this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.Memory = this.recline.Backend.Memory || {};
-(function($, my) {
+(function(my) {
my.__type__ = 'memory';
// ## Data Wrapper
@@ -48,7 +48,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
this.save = function(changes, dataset) {
var self = this;
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
// TODO _.each(changes.creates) { ... }
_.each(changes.updates, function(record) {
self.update(record);
@@ -61,7 +61,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
},
this.query = function(queryObj) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
var numRows = queryObj.size || this.records.length;
var start = queryObj.from || 0;
var results = this.records;
@@ -229,7 +229,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
};
this.transform = function(editFunc) {
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
// TODO: should we clone before mapping? Do not see the point atm.
self.records = _.map(self.records, editFunc);
// now deal with deletes (i.e. nulls)
@@ -241,4 +241,4 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
};
};
-}(jQuery, this.recline.Backend.Memory));
+}(this.recline.Backend.Memory));
diff --git a/src/backend.solr.js b/src/backend.solr.js
index fae6d353..51b4ab71 100644
--- a/src/backend.solr.js
+++ b/src/backend.solr.js
@@ -18,7 +18,7 @@ this.recline.Backend.Solr = this.recline.Backend.Solr || {};
dataType: 'jsonp',
jsonp: 'json.wrf'
});
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
jqxhr.done(function(results) {
// if we get 0 results we cannot get fields
var fields = []
@@ -51,7 +51,7 @@ this.recline.Backend.Solr = this.recline.Backend.Solr || {};
dataType: 'jsonp',
jsonp: 'json.wrf'
});
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
jqxhr.done(function(results) {
var out = {
total: results.response.numFound,
diff --git a/src/model.js b/src/model.js
index e912d8e5..e2217134 100644
--- a/src/model.js
+++ b/src/model.js
@@ -2,7 +2,7 @@
this.recline = this.recline || {};
this.recline.Model = this.recline.Model || {};
-(function($, my) {
+(function(my) {
// ## Dataset
my.Dataset = Backbone.Model.extend({
@@ -47,7 +47,7 @@ my.Dataset = Backbone.Model.extend({
// Retrieve dataset and (some) records from the backend.
fetch: function() {
var self = this;
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
if (this.backend !== recline.Backend.Memory) {
this.backend.fetch(this.toJSON())
@@ -181,7 +181,7 @@ my.Dataset = Backbone.Model.extend({
// also returned.
query: function(queryObj) {
var self = this;
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
this.trigger('query:start');
if (queryObj) {
@@ -245,7 +245,7 @@ my.Dataset = Backbone.Model.extend({
this.fields.each(function(field) {
query.addFacet(field.id);
});
- var dfd = $.Deferred();
+ var dfd = new _.Deferred();
this._store.query(query.toJSON(), this.toJSON()).done(function(queryResult) {
if (queryResult.facets) {
_.each(queryResult.facets, function(facetResult, facetId) {
@@ -585,5 +585,5 @@ Backbone.sync = function(method, model, options) {
return model.backend.sync(method, model, options);
};
-}(jQuery, this.recline.Model));
+}(this.recline.Model));
diff --git a/test/index.html b/test/index.html
index 23900b7f..5d4831dd 100644
--- a/test/index.html
+++ b/test/index.html
@@ -10,6 +10,7 @@
+
diff --git a/vendor/underscore.deferred/0.4.0/underscore.deferred.js b/vendor/underscore.deferred/0.4.0/underscore.deferred.js
new file mode 100644
index 00000000..abb8bb0b
--- /dev/null
+++ b/vendor/underscore.deferred/0.4.0/underscore.deferred.js
@@ -0,0 +1,445 @@
+(function(root){
+
+ // Let's borrow a couple of things from Underscore that we'll need
+
+ // _.each
+ var breaker = {},
+ AP = Array.prototype,
+ OP = Object.prototype,
+
+ hasOwn = OP.hasOwnProperty,
+ toString = OP.toString,
+ forEach = AP.forEach,
+ indexOf = AP.indexOf,
+ slice = AP.slice;
+
+ var _each = function( obj, iterator, context ) {
+ var key, i, l;
+
+ if ( !obj ) {
+ return;
+ }
+ if ( forEach && obj.forEach === forEach ) {
+ obj.forEach( iterator, context );
+ } else if ( obj.length === +obj.length ) {
+ for ( i = 0, l = obj.length; i < l; i++ ) {
+ if ( i in obj && iterator.call( context, obj[i], i, obj ) === breaker ) {
+ return;
+ }
+ }
+ } else {
+ for ( key in obj ) {
+ if ( hasOwn.call( obj, key ) ) {
+ if ( iterator.call( context, obj[key], key, obj) === breaker ) {
+ return;
+ }
+ }
+ }
+ }
+ };
+
+ // _.isFunction
+ var _isFunction = function( obj ) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ };
+
+ // _.extend
+ var _extend = function( obj ) {
+
+ _each( slice.call( arguments, 1), function( source ) {
+ var prop;
+
+ for ( prop in source ) {
+ if ( source[prop] !== void 0 ) {
+ obj[ prop ] = source[ prop ];
+ }
+ }
+ });
+ return obj;
+ };
+
+ // $.inArray
+ var _inArray = function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( indexOf ) {
+ return indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ };
+
+ // And some jQuery specific helpers
+
+ var class2type = {};
+
+ // Populate the class2type map
+ _each("Boolean Number String Function Array Date RegExp Object".split(" "), function(name, i) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+ });
+
+ var _type = function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ };
+
+ // Now start the jQuery-cum-Underscore implementation. Some very
+ // minor changes to the jQuery source to get this working.
+
+ // Internal Deferred namespace
+ var _d = {};
+ // String to Object options format cache
+ var optionsCache = {};
+
+ // Convert String-formatted options into Object-formatted ones and store in cache
+ function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ _each( options.split( /\s+/ ), function( flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+ }
+
+ _d.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ _extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ _each( args, function( arg ) {
+ var type = _type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ _each( arguments, function( arg ) {
+ var index;
+ while( ( index = _inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ return _inArray( fn, list ) > -1;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+ };
+
+ _d.Deferred = function( func ) {
+
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", _d.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", _d.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", _d.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+
+ return _d.Deferred(function( newDefer ) {
+
+ _each( tuples, function( tuple, i ) {
+ var action = tuple[ 0 ],
+ fn = fns[ i ];
+
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ]( _isFunction( fn ) ?
+
+ function() {
+ var returned;
+ try { returned = fn.apply( this, arguments ); } catch(e){
+ newDefer.reject(e);
+ return;
+ }
+
+ if ( returned && _isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action !== "notify" ? 'resolveWith' : action + 'With']( this === deferred ? newDefer : this, [ returned ] );
+ }
+ } :
+
+ newDefer[ action ]
+ );
+ });
+
+ fns = null;
+
+ }).promise();
+
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? _extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ _each( tuples, function( tuple, i ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ] = list.fire
+ deferred[ tuple[0] ] = list.fire;
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ };
+
+ // Deferred helper
+ _d.when = function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = _type(subordinate) === 'array' && arguments.length === 1 ?
+ subordinate : slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && _isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : _d.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && _isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ };
+
+ // Try exporting as a Common.js Module
+ if ( typeof module !== "undefined" && module.exports ) {
+ module.exports = _d;
+
+ // Or mixin to Underscore.js
+ } else if ( typeof root._ !== "undefined" ) {
+ root._.mixin(_d);
+
+ // Or assign it to window._
+ } else {
+ root._ = _d;
+ }
+
+})(this);