Merge branch 'master' into gh-pages

This commit is contained in:
Rufus Pollock 2013-02-08 13:21:10 +00:00
commit 6a6bb89699
34 changed files with 5892 additions and 1783 deletions

View File

@ -15,7 +15,17 @@ A simple but powerful library for building data applications in pure Javascript
## Developer Notes
Running the tests by opening `test/index.html` in your browser.
Run the tests by opening `test/index.html` in your browser.
Note that the demos and documentation utilize the [jekyll templating
system][jekyll] and to use them *locally* you will need to build them using
jekyll. Once installed, all you need to do from the command line is run jekyll:
jekyll
[jekyll]: https://github.com/mojombo/jekyll
Notes on the architecture can be found in the [documentation online](http://okfnlabs.org/recline).
### Contributing
@ -43,6 +53,16 @@ For larger changes:
* Update `/_includes/recline-deps.html` if you change required files (e.g. leaflet libraries)
* Try to build the demos in `/demos/` with jekyll and then check out the `/demos/multiview/` which utilizes most aspects of Recline
### Contributors
* [Rufus Pollock](http://rufuspollock.org/)
* [Max Ogden](http://maxogden.com/)
* [John Glover](https://github.com/johnglover)
* [James Casbon](http://casbon.me/)
* [Adrià Mercader](http://amercader.net/)
* [Dominik Moritz](https://github.com/domoritz)
* [Friedrich Lindenberg](http://pudo.org/)
* And [many more](https://github.com/okfn/recline/graphs/contributors)
## Changelog
@ -56,6 +76,8 @@ Possible breaking changes
* Added marker clustering in map view to handle a large number of markers
* 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)

View File

@ -14,6 +14,7 @@
<link rel="stylesheet" href="{{page.root}}css/grid.css">
<link rel="stylesheet" href="{{page.root}}css/slickgrid.css">
<link rel="stylesheet" href="{{page.root}}css/graph.css">
<link rel="stylesheet" href="{{page.root}}css/flot.css">
<link rel="stylesheet" href="{{page.root}}css/transform.css">
<link rel="stylesheet" href="{{page.root}}css/map.css">
<link rel="stylesheet" href="{{page.root}}css/multiview.css">
@ -22,10 +23,15 @@
<!-- 3rd party JS libraries -->
<script type="text/javascript" src="{{page.root}}vendor/jquery/1.7.1/jquery.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/underscore/1.4.2/underscore.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/underscore.deferred/0.4.0/underscore.deferred.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/backbone/0.9.2/backbone.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/mustache/0.5.0-dev/mustache.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/bootstrap/2.0.2/bootstrap.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/flotr2/flotr2.js"></script>
<!--[if lte IE 8]>
<script language="javascript" type="text/javascript" src="{{page.root}}vendor/flot/excanvas.min.js"></script>
<![endif]-->
<script type="text/javascript" src="{{page.root}}vendor/flot/jquery.flot.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/leaflet/0.4.4/leaflet.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/leaflet.markercluster/leaflet.markercluster.js"></script>
<script type="text/javascript" src="{{page.root}}vendor/slickgrid/2.0.1/jquery-ui-1.8.16.custom.min.js"></script>
@ -61,6 +67,7 @@
<script type="text/javascript" src="{{page.root}}src/view.transform.js"></script>
<script type="text/javascript" src="{{page.root}}src/data.transform.js"></script>
<script type="text/javascript" src="{{page.root}}src/view.graph.js"></script>
<script type="text/javascript" src="{{page.root}}src/view.flot.js"></script>
<script type="text/javascript" src="{{page.root}}src/view.map.js"></script>
<script type="text/javascript" src="{{page.root}}src/view.timeline.js"></script>
<script type="text/javascript" src="{{page.root}}src/widget.pager.js"></script>

26
css/flot.css Normal file
View File

@ -0,0 +1,26 @@
.recline-graph .graph {
height: 500px;
overflow: hidden;
}
.recline-graph .legend table {
width: auto;
margin-bottom: 0;
}
.recline-graph .legend td {
padding: 5px;
line-height: 13px;
}
.recline-graph .graph .alert {
width: 450px;
}
#recline-graph-tooltip {
position: absolute;
background-color: #FEE !important;
color: #000000 !important;
opacity: 0.8 !important;
border: 1px solid #fdd !important;
}

26
dist/recline.css vendored
View File

@ -1,3 +1,29 @@
.recline-graph .graph {
height: 500px;
overflow: hidden;
}
.recline-graph .legend table {
width: auto;
margin-bottom: 0;
}
.recline-graph .legend td {
padding: 5px;
line-height: 13px;
}
.recline-graph .graph .alert {
width: 450px;
}
#recline-graph-tooltip {
position: absolute;
background-color: #FEE !important;
color: #000000 !important;
opacity: 0.8 !important;
border: 1px solid #fdd !important;
}
.recline-graph .graph {
height: 500px;
}

View File

@ -2,7 +2,9 @@
this.recline = this.recline || {};
this.recline.Model = this.recline.Model || {};
(function($, my) {
(function(my) {
var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred;
// ## <a id="dataset">Dataset</a>
my.Dataset = Backbone.Model.extend({
@ -47,7 +49,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 +183,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 +247,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,13 +587,13 @@ Backbone.sync = function(method, model, options) {
return model.backend.sync(method, model, options);
};
}(jQuery, this.recline.Model));
}(this.recline.Model));
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
@ -600,42 +602,44 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
// functionality like querying, faceting, updating (by ID) and deleting (by
// ID).
//
// @param data list of hashes for each record/row in the data ({key:
// @param records list of hashes for each record/row in the data ({key:
// value, key: value})
// @param fields (optional) list of field hashes (each hash defining a field
// as per recline.Model.Field). If fields not specified they will be taken
// from the data.
my.Store = function(data, fields) {
my.Store = function(records, fields) {
var self = this;
this.data = data;
this.records = records;
// backwards compatability (in v0.5 records was named data)
this.data = this.records;
if (fields) {
this.fields = fields;
} else {
if (data) {
this.fields = _.map(data[0], function(value, key) {
if (records) {
this.fields = _.map(records[0], function(value, key) {
return {id: key, type: 'string'};
});
}
}
this.update = function(doc) {
_.each(self.data, function(internalDoc, idx) {
_.each(self.records, function(internalDoc, idx) {
if(doc.id === internalDoc.id) {
self.data[idx] = doc;
self.records[idx] = doc;
}
});
};
this.remove = function(doc) {
var newdocs = _.reject(self.data, function(internalDoc) {
var newdocs = _.reject(self.records, function(internalDoc) {
return (doc.id === internalDoc.id);
});
this.data = newdocs;
this.records = newdocs;
};
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);
@ -648,10 +652,10 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
},
this.query = function(queryObj) {
var dfd = $.Deferred();
var numRows = queryObj.size || this.data.length;
var dfd = new _.Deferred();
var numRows = queryObj.size || this.records.length;
var start = queryObj.from || 0;
var results = this.data;
var results = this.records;
results = this._applyFilters(results, queryObj);
results = this._applyFreeTextQuery(results, queryObj);
@ -816,11 +820,11 @@ 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.data = _.map(self.data, editFunc);
self.records = _.map(self.records, editFunc);
// now deal with deletes (i.e. nulls)
self.data = _.filter(self.data, function(record) {
self.records = _.filter(self.records, function(record) {
return record != null;
});
dfd.resolve();
@ -828,4 +832,4 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
};
};
}(jQuery, this.recline.Backend.Memory));
}(this.recline.Backend.Memory));

621
dist/recline.js vendored
View File

@ -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,13 +136,14 @@ this.recline.Backend.Ckan = this.recline.Backend.Ckan || {};
'float8': 'float'
};
}(jQuery, this.recline.Backend.Ckan));
}(this.recline.Backend.Ckan));
this.recline = this.recline || {};
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) {
my.__type__ = 'csv';
// ## fetch
//
@ -150,7 +151,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:
//
@ -162,7 +163,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {};
// }
// </pre>
my.fetch = function(dataset) {
var dfd = $.Deferred();
var dfd = new _.Deferred();
if (dataset.file) {
var reader = new FileReader();
var encoding = dataset.encoding || 'UTF-8';
@ -187,7 +188,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,
@ -424,12 +425,12 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {};
}
}(this.recline.Backend.CSV, jQuery));
}(this.recline.Backend.CSV));
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';
@ -448,12 +449,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);
@ -477,7 +478,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'
@ -495,7 +496,7 @@ this.recline.Backend.DataProxy = this.recline.Backend.DataProxy || {};
return dfd.promise();
}
}(jQuery, this.recline.Backend.DataProxy));
}(this.recline.Backend.DataProxy));
this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.ElasticSearch = this.recline.Backend.ElasticSearch || {};
@ -677,7 +678,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){
@ -705,7 +706,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);
@ -723,7 +724,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) {
@ -782,7 +783,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
@ -809,15 +810,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
});
@ -827,7 +828,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};
@ -941,12 +942,12 @@ this.recline.Backend.GDocs = this.recline.Backend.GDocs || {};
return urls;
};
}(jQuery, this.recline.Backend.GDocs));
}(this.recline.Backend.GDocs));
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
@ -955,42 +956,44 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
// functionality like querying, faceting, updating (by ID) and deleting (by
// ID).
//
// @param data list of hashes for each record/row in the data ({key:
// @param records list of hashes for each record/row in the data ({key:
// value, key: value})
// @param fields (optional) list of field hashes (each hash defining a field
// as per recline.Model.Field). If fields not specified they will be taken
// from the data.
my.Store = function(data, fields) {
my.Store = function(records, fields) {
var self = this;
this.data = data;
this.records = records;
// backwards compatability (in v0.5 records was named data)
this.data = this.records;
if (fields) {
this.fields = fields;
} else {
if (data) {
this.fields = _.map(data[0], function(value, key) {
if (records) {
this.fields = _.map(records[0], function(value, key) {
return {id: key, type: 'string'};
});
}
}
this.update = function(doc) {
_.each(self.data, function(internalDoc, idx) {
_.each(self.records, function(internalDoc, idx) {
if(doc.id === internalDoc.id) {
self.data[idx] = doc;
self.records[idx] = doc;
}
});
};
this.remove = function(doc) {
var newdocs = _.reject(self.data, function(internalDoc) {
var newdocs = _.reject(self.records, function(internalDoc) {
return (doc.id === internalDoc.id);
});
this.data = newdocs;
this.records = newdocs;
};
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);
@ -1003,10 +1006,10 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
},
this.query = function(queryObj) {
var dfd = $.Deferred();
var numRows = queryObj.size || this.data.length;
var dfd = new _.Deferred();
var numRows = queryObj.size || this.records.length;
var start = queryObj.from || 0;
var results = this.data;
var results = this.records;
results = this._applyFilters(results, queryObj);
results = this._applyFreeTextQuery(results, queryObj);
@ -1171,11 +1174,11 @@ 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.data = _.map(self.data, editFunc);
self.records = _.map(self.records, editFunc);
// now deal with deletes (i.e. nulls)
self.data = _.filter(self.data, function(record) {
self.records = _.filter(self.records, function(record) {
return record != null;
});
dfd.resolve();
@ -1183,7 +1186,7 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
};
};
}(jQuery, this.recline.Backend.Memory));
}(this.recline.Backend.Memory));
this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.Solr = this.recline.Backend.Solr || {};
@ -1204,7 +1207,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 = []
@ -1237,7 +1240,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,
@ -1386,7 +1389,9 @@ if (!('some' in Array.prototype)) {
this.recline = this.recline || {};
this.recline.Model = this.recline.Model || {};
(function($, my) {
(function(my) {
var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred;
// ## <a id="dataset">Dataset</a>
my.Dataset = Backbone.Model.extend({
@ -1431,7 +1436,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())
@ -1565,7 +1570,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) {
@ -1629,7 +1634,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) {
@ -1969,7 +1974,7 @@ Backbone.sync = function(method, model, options) {
return model.backend.sync(method, model, options);
};
}(jQuery, this.recline.Model));
}(this.recline.Model));
/*jshint multistr:true */
@ -1978,6 +1983,508 @@ this.recline.View = this.recline.View || {};
(function($, my) {
// ## Graph view for a Dataset using Flot graphing library.
//
// Initialization arguments (in a hash in first parameter):
//
// * model: recline.Model.Dataset
// * state: (optional) configuration hash of form:
//
// {
// group: {column name for x-axis},
// series: [{column name for series A}, {column name series B}, ... ],
// graphType: 'line',
// graphOptions: {custom [flot options]}
// }
//
// NB: should *not* provide an el argument to the view but must let the view
// generate the element itself (you can then append view.el to the DOM.
my.Flot = Backbone.View.extend({
template: ' \
<div class="recline-graph"> \
<div class="panel graph" style="display: block;"> \
<div class="js-temp-notice alert alert-block"> \
<h3 class="alert-heading">Hey there!</h3> \
<p>There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.</p> \
<p>Please tell us by <strong>using the menu on the right</strong> and a graph will automatically appear.</p> \
</div> \
</div> \
</div> \
',
initialize: function(options) {
var self = this;
this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"];
this.el = $(this.el);
_.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel');
this.needToRedraw = false;
this.model.bind('change', this.render);
this.model.fields.bind('reset', this.render);
this.model.fields.bind('add', this.render);
this.model.records.bind('add', this.redraw);
this.model.records.bind('reset', this.redraw);
var stateData = _.extend({
group: null,
// so that at least one series chooser box shows up
series: [],
graphType: 'lines-and-points'
},
options.state
);
this.state = new recline.Model.ObjectState(stateData);
this.previousTooltipPoint = {x: null, y: null};
this.editor = new my.FlotControls({
model: this.model,
state: this.state.toJSON()
});
this.editor.state.bind('change', function() {
self.state.set(self.editor.state.toJSON());
self.redraw();
});
this.elSidebar = this.editor.el;
},
render: function() {
var self = this;
var tmplData = this.model.toTemplateJSON();
var htmls = Mustache.render(this.template, tmplData);
$(this.el).html(htmls);
this.$graph = this.el.find('.panel.graph');
this.$graph.on("plothover", this._toolTip);
return this;
},
redraw: function() {
// There are issues generating a Flot graph if either:
// * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with
// Uncaught Invalid dimensions for plot, width = 0, height = 0
// * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value'
var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]);
if ((!areWeVisible || this.model.records.length === 0)) {
this.needToRedraw = true;
return;
}
// check we have something to plot
if (this.state.get('group') && this.state.get('series')) {
// faff around with width because flot draws axes *outside* of the element
// width which means graph can get push down as it hits element next to it
this.$graph.width(this.el.width() - 240);
var series = this.createSeries();
var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length);
this.plot = $.plot(this.$graph, series, options);
}
},
show: function() {
// because we cannot redraw when hidden we may need to when becoming visible
if (this.needToRedraw) {
this.redraw();
}
},
// infoboxes on mouse hover on points/bars etc
_toolTip: function (event, pos, item) {
if (item) {
if (this.previousTooltipPoint.x !== item.dataIndex ||
this.previousTooltipPoint.y !== item.seriesIndex) {
this.previousTooltipPoint.x = item.dataIndex;
this.previousTooltipPoint.y = item.seriesIndex;
$("#recline-graph-tooltip").remove();
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', {
group: this.state.attributes.group,
x: this._xaxisLabel(x),
series: item.series.label,
y: y
});
// use a different tooltip location offset for bar charts
var xLocation, yLocation;
if (this.state.attributes.graphType === 'bars') {
xLocation = item.pageX + 15;
yLocation = item.pageY;
} else {
xLocation = item.pageX + 10;
yLocation = item.pageY - 20;
}
$('<div id="recline-graph-tooltip">' + content + '</div>').css({
top: yLocation,
left: xLocation
}).appendTo("body").fadeIn(200);
}
} else {
$("#recline-graph-tooltip").remove();
this.previousTooltipPoint.x = null;
this.previousTooltipPoint.y = null;
}
},
_xaxisLabel: function (x) {
var xfield = this.model.fields.get(this.state.attributes.group);
// time series
var xtype = xfield.get('type');
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
if (this.model.records.models[parseInt(x, 10)]) {
x = this.model.records.models[parseInt(x, 10)].get(this.state.attributes.group);
if (isDateTime) {
x = new Date(x).toLocaleDateString();
}
} else if (isDateTime) {
x = new Date(parseInt(x, 10)).toLocaleDateString();
}
return x;
},
// ### getGraphOptions
//
// Get options for Flot Graph
//
// needs to be function as can depend on state
//
// @param typeId graphType id (lines, lines-and-points etc)
// @param numPoints the number of points that will be plotted
getGraphOptions: function(typeId, numPoints) {
var self = this;
var tickFormatter = function (x) {
// convert x to a string and make sure that it is not too long or the
// tick labels will overlap
// TODO: find a more accurate way of calculating the size of tick labels
var label = self._xaxisLabel(x);
if (typeof label !== 'string') {
label = label.toString();
}
if (label.length > 8) {
label = label.slice(0, 5) + "...";
}
return label;
};
var xaxis = {};
xaxis.tickFormatter = tickFormatter;
// calculate the x-axis ticks
//
// the number of ticks should be a multiple of the number of points so that
// each tick lines up with a point
if (numPoints) {
var ticks = [],
maxTicks = 10,
x = 1,
i = 0;
while (x <= maxTicks) {
if ((numPoints / x) <= maxTicks) {
break;
}
x = x + 1;
}
for (i = 0; i < numPoints; i = i + x) {
ticks.push(i);
}
xaxis.ticks = ticks;
}
var yaxis = {};
yaxis.autoscale = true;
yaxis.autoscaleMargin = 0.02;
var legend = {};
legend.position = 'ne';
var grid = {};
grid.hoverable = true;
grid.clickable = true;
grid.borderColor = "#aaaaaa";
grid.borderWidth = 1;
var optionsPerGraphType = {
lines: {
legend: legend,
colors: this.graphColors,
lines: { show: true },
xaxis: xaxis,
yaxis: yaxis,
grid: grid
},
points: {
legend: legend,
colors: this.graphColors,
points: { show: true, hitRadius: 5 },
xaxis: xaxis,
yaxis: yaxis,
grid: grid
},
'lines-and-points': {
legend: legend,
colors: this.graphColors,
points: { show: true, hitRadius: 5 },
lines: { show: true },
xaxis: xaxis,
yaxis: yaxis,
grid: grid
},
bars: {
legend: legend,
colors: this.graphColors,
lines: { show: false },
xaxis: yaxis,
yaxis: xaxis,
grid: grid,
bars: {
show: true,
horizontal: true,
shadowSize: 0,
align: 'center',
barWidth: 0.8
}
},
columns: {
legend: legend,
colors: this.graphColors,
lines: { show: false },
xaxis: xaxis,
yaxis: yaxis,
grid: grid,
bars: {
show: true,
horizontal: false,
shadowSize: 0,
align: 'center',
barWidth: 0.8
}
}
};
if (self.state.get('graphOptions')) {
return _.extend(optionsPerGraphType[typeId],
self.state.get('graphOptions'));
} else {
return optionsPerGraphType[typeId];
}
},
createSeries: function() {
var self = this;
var series = [];
_.each(this.state.attributes.series, function(field) {
var points = [];
_.each(self.model.records.models, function(doc, index) {
var xfield = self.model.fields.get(self.state.attributes.group);
var x = doc.getFieldValue(xfield);
// time series
var xtype = xfield.get('type');
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
if (isDateTime) {
if (self.state.attributes.graphType != 'bars' &&
self.state.attributes.graphType != 'columns') {
x = new Date(x).getTime();
} else {
x = index;
}
} else if (typeof x === 'string') {
x = parseFloat(x);
if (isNaN(x)) {
x = index;
}
}
var yfield = self.model.fields.get(field);
var y = doc.getFieldValue(yfield);
if (self.state.attributes.graphType == 'bars') {
points.push([y, x]);
} else {
points.push([x, y]);
}
});
series.push({
data: points,
label: field,
hoverable: true
});
});
return series;
}
});
my.FlotControls = Backbone.View.extend({
className: "editor",
template: ' \
<div class="editor"> \
<form class="form-stacked"> \
<div class="clearfix"> \
<label>Graph Type</label> \
<div class="input editor-type"> \
<select> \
<option value="lines-and-points">Lines and Points</option> \
<option value="lines">Lines</option> \
<option value="points">Points</option> \
<option value="bars">Bars</option> \
<option value="columns">Columns</option> \
</select> \
</div> \
<label>Group Column (Axis 1)</label> \
<div class="input editor-group"> \
<select> \
<option value="">Please choose ...</option> \
{{#fields}} \
<option value="{{id}}">{{label}}</option> \
{{/fields}} \
</select> \
</div> \
<div class="editor-series-group"> \
</div> \
</div> \
<div class="editor-buttons"> \
<button class="btn editor-add">Add Series</button> \
</div> \
<div class="editor-buttons editor-submit" comment="hidden temporarily" style="display: none;"> \
<button class="editor-save">Save</button> \
<input type="hidden" class="editor-id" value="chart-1" /> \
</div> \
</form> \
</div> \
',
templateSeriesEditor: ' \
<div class="editor-series js-series-{{seriesIndex}}"> \
<label>Series <span>{{seriesName}} (Axis 2)</span> \
[<a href="#remove" class="action-remove-series">Remove</a>] \
</label> \
<div class="input"> \
<select> \
{{#fields}} \
<option value="{{id}}">{{label}}</option> \
{{/fields}} \
</select> \
</div> \
</div> \
',
events: {
'change form select': 'onEditorSubmit',
'click .editor-add': '_onAddSeries',
'click .action-remove-series': 'removeSeries'
},
initialize: function(options) {
var self = this;
this.el = $(this.el);
_.bindAll(this, 'render');
this.model.fields.bind('reset', this.render);
this.model.fields.bind('add', this.render);
this.state = new recline.Model.ObjectState(options.state);
this.render();
},
render: function() {
var self = this;
var tmplData = this.model.toTemplateJSON();
var htmls = Mustache.render(this.template, tmplData);
this.el.html(htmls);
// set up editor from state
if (this.state.get('graphType')) {
this._selectOption('.editor-type', this.state.get('graphType'));
}
if (this.state.get('group')) {
this._selectOption('.editor-group', this.state.get('group'));
}
// ensure at least one series box shows up
var tmpSeries = [""];
if (this.state.get('series').length > 0) {
tmpSeries = this.state.get('series');
}
_.each(tmpSeries, function(series, idx) {
self.addSeries(idx);
self._selectOption('.editor-series.js-series-' + idx, series);
});
return this;
},
// Private: Helper function to select an option from a select list
//
_selectOption: function(id,value){
var options = this.el.find(id + ' select > option');
if (options) {
options.each(function(opt){
if (this.value == value) {
$(this).attr('selected','selected');
return false;
}
});
}
},
onEditorSubmit: function(e) {
var select = this.el.find('.editor-group select');
var $editor = this;
var $series = this.el.find('.editor-series select');
var series = $series.map(function () {
return $(this).val();
});
var updatedState = {
series: $.makeArray(series),
group: this.el.find('.editor-group select').val(),
graphType: this.el.find('.editor-type select').val()
};
this.state.set(updatedState);
},
// Public: Adds a new empty series select box to the editor.
//
// @param [int] idx index of this series in the list of series
//
// Returns itself.
addSeries: function (idx) {
var data = _.extend({
seriesIndex: idx,
seriesName: String.fromCharCode(idx + 64 + 1)
}, this.model.toTemplateJSON());
var htmls = Mustache.render(this.templateSeriesEditor, data);
this.el.find('.editor-series-group').append(htmls);
return this;
},
_onAddSeries: function(e) {
e.preventDefault();
this.addSeries(this.state.get('series').length);
},
// Public: Removes a series list item from the editor.
//
// Also updates the labels of the remaining series elements.
removeSeries: function (e) {
e.preventDefault();
var $el = $(e.target);
$el.parent().parent().remove();
this.onEditorSubmit();
}
});
})(jQuery, recline.View);
/*jshint multistr:true */
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
// ## Graph view for a Dataset using Flot graphing library.
//
// Initialization arguments (in a hash in first parameter):
@ -2293,7 +2800,7 @@ my.GraphControls = Backbone.View.extend({
<option value="columns">Columns</option> \
</select> \
</div> \
<label>Group Column (x-axis)</label> \
<label>Group Column (Axis 1)</label> \
<div class="input editor-group"> \
<select> \
<option value="">Please choose ...</option> \
@ -2317,7 +2824,7 @@ my.GraphControls = Backbone.View.extend({
',
templateSeriesEditor: ' \
<div class="editor-series js-series-{{seriesIndex}}"> \
<label>Series <span>{{seriesName}} (y-axis)</span> \
<label>Series <span>{{seriesName}} (Axis 2)</span> \
[<a href="#remove" class="action-remove-series">Remove</a>] \
</label> \
<div class="input"> \
@ -4414,11 +4921,6 @@ my.Timeline = Backbone.View.extend({
if (out.toDate() == 'Invalid Date') {
return null;
} else {
// fix for moment weirdness around date parsing and time zones
// moment('1914-08-01').toDate() => 1914-08-01 00:00 +01:00
// which in iso format (with 0 time offset) is 31 July 1914 23:00
// meanwhile native new Date('1914-08-01') => 1914-08-01 01:00 +01:00
out = out.subtract('minutes', out.zone());
return out.toDate();
}
},
@ -4921,7 +5423,7 @@ my.FilterEditor = Backbone.View.extend({
var $input = $(input);
var filterType = $input.attr('data-filter-type');
var fieldId = $input.attr('data-filter-field');
var filterIndex = parseInt($input.attr('data-filter-id'));
var filterIndex = parseInt($input.attr('data-filter-id'), 10);
var name = $input.attr('name');
var value = $input.val();
@ -4942,7 +5444,7 @@ my.FilterEditor = Backbone.View.extend({
break;
}
});
self.model.queryState.set({filters: filters});
self.model.queryState.set({filters: filters, from: 0});
self.model.queryState.trigger('change');
}
});
@ -4984,6 +5486,8 @@ my.Pager = Backbone.View.extend({
e.preventDefault();
var newFrom = parseInt(this.el.find('input[name="from"]').val());
var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom;
newFrom = Math.max(newFrom, 0);
newSize = Math.max(newSize, 1);
this.model.set({size: newSize, from: newFrom});
},
onPaginationUpdate: function(e) {
@ -4995,6 +5499,7 @@ my.Pager = Backbone.View.extend({
} else {
newFrom = this.model.get('from') + this.model.get('size');
}
newFrom = Math.max(newFrom, 0);
this.model.set({from: newFrom});
},
render: function() {

View File

@ -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/) &gt;= 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/) &gt;= 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/) &gt;= 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/) &gt;= 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

2
make
View File

@ -37,7 +37,7 @@ def minify():
if __name__ == '__main__':
if not len(sys.argv) > 1:
print 'make cat | docs | all'
print 'make cat | docs | minify | all'
sys.exit(1)
action = sys.argv[1]
if action == 'cat':

View File

@ -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));

View File

@ -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));
}(jQuery, this.recline.Backend.CouchDB));

View File

@ -3,7 +3,8 @@ 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) {
my.__type__ = 'csv';
// ## fetch
//
@ -11,7 +12,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 +24,7 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {};
// }
// </pre>
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 +49,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 +286,4 @@ this.recline.Backend.CSV = this.recline.Backend.CSV || {};
}
}(this.recline.Backend.CSV, jQuery));
}(this.recline.Backend.CSV));

View File

@ -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));

View File

@ -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) {

View File

@ -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));

View File

@ -2,51 +2,56 @@ 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';
// private data - use either jQuery or Underscore Deferred depending on what is available
var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred;
// ## Data Wrapper
//
// Turn a simple array of JS objects into a mini data-store with
// functionality like querying, faceting, updating (by ID) and deleting (by
// ID).
//
// @param data list of hashes for each record/row in the data ({key:
// @param records list of hashes for each record/row in the data ({key:
// value, key: value})
// @param fields (optional) list of field hashes (each hash defining a field
// as per recline.Model.Field). If fields not specified they will be taken
// from the data.
my.Store = function(data, fields) {
my.Store = function(records, fields) {
var self = this;
this.data = data;
this.records = records;
// backwards compatability (in v0.5 records was named data)
this.data = this.records;
if (fields) {
this.fields = fields;
} else {
if (data) {
this.fields = _.map(data[0], function(value, key) {
if (records) {
this.fields = _.map(records[0], function(value, key) {
return {id: key, type: 'string'};
});
}
}
this.update = function(doc) {
_.each(self.data, function(internalDoc, idx) {
_.each(self.records, function(internalDoc, idx) {
if(doc.id === internalDoc.id) {
self.data[idx] = doc;
self.records[idx] = doc;
}
});
};
this.remove = function(doc) {
var newdocs = _.reject(self.data, function(internalDoc) {
var newdocs = _.reject(self.records, function(internalDoc) {
return (doc.id === internalDoc.id);
});
this.data = newdocs;
this.records = newdocs;
};
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);
@ -59,10 +64,10 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
},
this.query = function(queryObj) {
var dfd = $.Deferred();
var numRows = queryObj.size || this.data.length;
var dfd = new Deferred();
var numRows = queryObj.size || this.records.length;
var start = queryObj.from || 0;
var results = this.data;
var results = this.records;
results = this._applyFilters(results, queryObj);
results = this._applyFreeTextQuery(results, queryObj);
@ -227,11 +232,11 @@ 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.data = _.map(self.data, editFunc);
self.records = _.map(self.records, editFunc);
// now deal with deletes (i.e. nulls)
self.data = _.filter(self.data, function(record) {
self.records = _.filter(self.records, function(record) {
return record != null;
});
dfd.resolve();
@ -239,4 +244,4 @@ this.recline.Backend.Memory = this.recline.Backend.Memory || {};
};
};
}(jQuery, this.recline.Backend.Memory));
}(this.recline.Backend.Memory));

View File

@ -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,

View File

@ -2,7 +2,10 @@
this.recline = this.recline || {};
this.recline.Model = this.recline.Model || {};
(function($, my) {
(function(my) {
// private - use either jQuery or Underscore Deferred depending on what is available
var Deferred = _.isUndefined(this.jQuery) ? _.Deferred : jQuery.Deferred;
// ## <a id="dataset">Dataset</a>
my.Dataset = Backbone.Model.extend({
@ -47,7 +50,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 +184,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 +248,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 +588,5 @@ Backbone.sync = function(method, model, options) {
return model.backend.sync(method, model, options);
};
}(jQuery, this.recline.Model));
}(this.recline.Model));

502
src/view.flot.js Normal file
View File

@ -0,0 +1,502 @@
/*jshint multistr:true */
this.recline = this.recline || {};
this.recline.View = this.recline.View || {};
(function($, my) {
// ## Graph view for a Dataset using Flot graphing library.
//
// Initialization arguments (in a hash in first parameter):
//
// * model: recline.Model.Dataset
// * state: (optional) configuration hash of form:
//
// {
// group: {column name for x-axis},
// series: [{column name for series A}, {column name series B}, ... ],
// graphType: 'line',
// graphOptions: {custom [flot options]}
// }
//
// NB: should *not* provide an el argument to the view but must let the view
// generate the element itself (you can then append view.el to the DOM.
my.Flot = Backbone.View.extend({
template: ' \
<div class="recline-graph"> \
<div class="panel graph" style="display: block;"> \
<div class="js-temp-notice alert alert-block"> \
<h3 class="alert-heading">Hey there!</h3> \
<p>There\'s no graph here yet because we don\'t know what fields you\'d like to see plotted.</p> \
<p>Please tell us by <strong>using the menu on the right</strong> and a graph will automatically appear.</p> \
</div> \
</div> \
</div> \
',
initialize: function(options) {
var self = this;
this.graphColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"];
this.el = $(this.el);
_.bindAll(this, 'render', 'redraw', '_toolTip', '_xaxisLabel');
this.needToRedraw = false;
this.model.bind('change', this.render);
this.model.fields.bind('reset', this.render);
this.model.fields.bind('add', this.render);
this.model.records.bind('add', this.redraw);
this.model.records.bind('reset', this.redraw);
var stateData = _.extend({
group: null,
// so that at least one series chooser box shows up
series: [],
graphType: 'lines-and-points'
},
options.state
);
this.state = new recline.Model.ObjectState(stateData);
this.previousTooltipPoint = {x: null, y: null};
this.editor = new my.FlotControls({
model: this.model,
state: this.state.toJSON()
});
this.editor.state.bind('change', function() {
self.state.set(self.editor.state.toJSON());
self.redraw();
});
this.elSidebar = this.editor.el;
},
render: function() {
var self = this;
var tmplData = this.model.toTemplateJSON();
var htmls = Mustache.render(this.template, tmplData);
$(this.el).html(htmls);
this.$graph = this.el.find('.panel.graph');
this.$graph.on("plothover", this._toolTip);
return this;
},
redraw: function() {
// There are issues generating a Flot graph if either:
// * The relevant div that graph attaches to his hidden at the moment of creating the plot -- Flot will complain with
// Uncaught Invalid dimensions for plot, width = 0, height = 0
// * There is no data for the plot -- either same error or may have issues later with errors like 'non-existent node-value'
var areWeVisible = !jQuery.expr.filters.hidden(this.el[0]);
if ((!areWeVisible || this.model.records.length === 0)) {
this.needToRedraw = true;
return;
}
// check we have something to plot
if (this.state.get('group') && this.state.get('series')) {
// faff around with width because flot draws axes *outside* of the element
// width which means graph can get push down as it hits element next to it
this.$graph.width(this.el.width() - 240);
var series = this.createSeries();
var options = this.getGraphOptions(this.state.attributes.graphType, series[0].data.length);
this.plot = $.plot(this.$graph, series, options);
}
},
show: function() {
// because we cannot redraw when hidden we may need to when becoming visible
if (this.needToRedraw) {
this.redraw();
}
},
// infoboxes on mouse hover on points/bars etc
_toolTip: function (event, pos, item) {
if (item) {
if (this.previousTooltipPoint.x !== item.dataIndex ||
this.previousTooltipPoint.y !== item.seriesIndex) {
this.previousTooltipPoint.x = item.dataIndex;
this.previousTooltipPoint.y = item.seriesIndex;
$("#recline-graph-tooltip").remove();
var x = item.datapoint[0].toFixed(2),
y = item.datapoint[1].toFixed(2);
var content = _.template('<%= group %> = <%= x %>, <%= series %> = <%= y %>', {
group: this.state.attributes.group,
x: this._xaxisLabel(x),
series: item.series.label,
y: y
});
// use a different tooltip location offset for bar charts
var xLocation, yLocation;
if (this.state.attributes.graphType === 'bars') {
xLocation = item.pageX + 15;
yLocation = item.pageY;
} else {
xLocation = item.pageX + 10;
yLocation = item.pageY - 20;
}
$('<div id="recline-graph-tooltip">' + content + '</div>').css({
top: yLocation,
left: xLocation
}).appendTo("body").fadeIn(200);
}
} else {
$("#recline-graph-tooltip").remove();
this.previousTooltipPoint.x = null;
this.previousTooltipPoint.y = null;
}
},
_xaxisLabel: function (x) {
var xfield = this.model.fields.get(this.state.attributes.group);
// time series
var xtype = xfield.get('type');
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
if (this.model.records.models[parseInt(x, 10)]) {
x = this.model.records.models[parseInt(x, 10)].get(this.state.attributes.group);
if (isDateTime) {
x = new Date(x).toLocaleDateString();
}
} else if (isDateTime) {
x = new Date(parseInt(x, 10)).toLocaleDateString();
}
return x;
},
// ### getGraphOptions
//
// Get options for Flot Graph
//
// needs to be function as can depend on state
//
// @param typeId graphType id (lines, lines-and-points etc)
// @param numPoints the number of points that will be plotted
getGraphOptions: function(typeId, numPoints) {
var self = this;
var tickFormatter = function (x) {
// convert x to a string and make sure that it is not too long or the
// tick labels will overlap
// TODO: find a more accurate way of calculating the size of tick labels
var label = self._xaxisLabel(x);
if (typeof label !== 'string') {
label = label.toString();
}
if (label.length > 8) {
label = label.slice(0, 5) + "...";
}
return label;
};
var xaxis = {};
xaxis.tickFormatter = tickFormatter;
// calculate the x-axis ticks
//
// the number of ticks should be a multiple of the number of points so that
// each tick lines up with a point
if (numPoints) {
var ticks = [],
maxTicks = 10,
x = 1,
i = 0;
while (x <= maxTicks) {
if ((numPoints / x) <= maxTicks) {
break;
}
x = x + 1;
}
for (i = 0; i < numPoints; i = i + x) {
ticks.push(i);
}
xaxis.ticks = ticks;
}
var yaxis = {};
yaxis.autoscale = true;
yaxis.autoscaleMargin = 0.02;
var legend = {};
legend.position = 'ne';
var grid = {};
grid.hoverable = true;
grid.clickable = true;
grid.borderColor = "#aaaaaa";
grid.borderWidth = 1;
var optionsPerGraphType = {
lines: {
legend: legend,
colors: this.graphColors,
lines: { show: true },
xaxis: xaxis,
yaxis: yaxis,
grid: grid
},
points: {
legend: legend,
colors: this.graphColors,
points: { show: true, hitRadius: 5 },
xaxis: xaxis,
yaxis: yaxis,
grid: grid
},
'lines-and-points': {
legend: legend,
colors: this.graphColors,
points: { show: true, hitRadius: 5 },
lines: { show: true },
xaxis: xaxis,
yaxis: yaxis,
grid: grid
},
bars: {
legend: legend,
colors: this.graphColors,
lines: { show: false },
xaxis: yaxis,
yaxis: xaxis,
grid: grid,
bars: {
show: true,
horizontal: true,
shadowSize: 0,
align: 'center',
barWidth: 0.8
}
},
columns: {
legend: legend,
colors: this.graphColors,
lines: { show: false },
xaxis: xaxis,
yaxis: yaxis,
grid: grid,
bars: {
show: true,
horizontal: false,
shadowSize: 0,
align: 'center',
barWidth: 0.8
}
}
};
if (self.state.get('graphOptions')) {
return _.extend(optionsPerGraphType[typeId],
self.state.get('graphOptions'));
} else {
return optionsPerGraphType[typeId];
}
},
createSeries: function() {
var self = this;
var series = [];
_.each(this.state.attributes.series, function(field) {
var points = [];
_.each(self.model.records.models, function(doc, index) {
var xfield = self.model.fields.get(self.state.attributes.group);
var x = doc.getFieldValue(xfield);
// time series
var xtype = xfield.get('type');
var isDateTime = (xtype === 'date' || xtype === 'date-time' || xtype === 'time');
if (isDateTime) {
if (self.state.attributes.graphType != 'bars' &&
self.state.attributes.graphType != 'columns') {
x = new Date(x).getTime();
} else {
x = index;
}
} else if (typeof x === 'string') {
x = parseFloat(x);
if (isNaN(x)) {
x = index;
}
}
var yfield = self.model.fields.get(field);
var y = doc.getFieldValue(yfield);
if (self.state.attributes.graphType == 'bars') {
points.push([y, x]);
} else {
points.push([x, y]);
}
});
series.push({
data: points,
label: field,
hoverable: true
});
});
return series;
}
});
my.FlotControls = Backbone.View.extend({
className: "editor",
template: ' \
<div class="editor"> \
<form class="form-stacked"> \
<div class="clearfix"> \
<label>Graph Type</label> \
<div class="input editor-type"> \
<select> \
<option value="lines-and-points">Lines and Points</option> \
<option value="lines">Lines</option> \
<option value="points">Points</option> \
<option value="bars">Bars</option> \
<option value="columns">Columns</option> \
</select> \
</div> \
<label>Group Column (Axis 1)</label> \
<div class="input editor-group"> \
<select> \
<option value="">Please choose ...</option> \
{{#fields}} \
<option value="{{id}}">{{label}}</option> \
{{/fields}} \
</select> \
</div> \
<div class="editor-series-group"> \
</div> \
</div> \
<div class="editor-buttons"> \
<button class="btn editor-add">Add Series</button> \
</div> \
<div class="editor-buttons editor-submit" comment="hidden temporarily" style="display: none;"> \
<button class="editor-save">Save</button> \
<input type="hidden" class="editor-id" value="chart-1" /> \
</div> \
</form> \
</div> \
',
templateSeriesEditor: ' \
<div class="editor-series js-series-{{seriesIndex}}"> \
<label>Series <span>{{seriesName}} (Axis 2)</span> \
[<a href="#remove" class="action-remove-series">Remove</a>] \
</label> \
<div class="input"> \
<select> \
{{#fields}} \
<option value="{{id}}">{{label}}</option> \
{{/fields}} \
</select> \
</div> \
</div> \
',
events: {
'change form select': 'onEditorSubmit',
'click .editor-add': '_onAddSeries',
'click .action-remove-series': 'removeSeries'
},
initialize: function(options) {
var self = this;
this.el = $(this.el);
_.bindAll(this, 'render');
this.model.fields.bind('reset', this.render);
this.model.fields.bind('add', this.render);
this.state = new recline.Model.ObjectState(options.state);
this.render();
},
render: function() {
var self = this;
var tmplData = this.model.toTemplateJSON();
var htmls = Mustache.render(this.template, tmplData);
this.el.html(htmls);
// set up editor from state
if (this.state.get('graphType')) {
this._selectOption('.editor-type', this.state.get('graphType'));
}
if (this.state.get('group')) {
this._selectOption('.editor-group', this.state.get('group'));
}
// ensure at least one series box shows up
var tmpSeries = [""];
if (this.state.get('series').length > 0) {
tmpSeries = this.state.get('series');
}
_.each(tmpSeries, function(series, idx) {
self.addSeries(idx);
self._selectOption('.editor-series.js-series-' + idx, series);
});
return this;
},
// Private: Helper function to select an option from a select list
//
_selectOption: function(id,value){
var options = this.el.find(id + ' select > option');
if (options) {
options.each(function(opt){
if (this.value == value) {
$(this).attr('selected','selected');
return false;
}
});
}
},
onEditorSubmit: function(e) {
var select = this.el.find('.editor-group select');
var $editor = this;
var $series = this.el.find('.editor-series select');
var series = $series.map(function () {
return $(this).val();
});
var updatedState = {
series: $.makeArray(series),
group: this.el.find('.editor-group select').val(),
graphType: this.el.find('.editor-type select').val()
};
this.state.set(updatedState);
},
// Public: Adds a new empty series select box to the editor.
//
// @param [int] idx index of this series in the list of series
//
// Returns itself.
addSeries: function (idx) {
var data = _.extend({
seriesIndex: idx,
seriesName: String.fromCharCode(idx + 64 + 1)
}, this.model.toTemplateJSON());
var htmls = Mustache.render(this.templateSeriesEditor, data);
this.el.find('.editor-series-group').append(htmls);
return this;
},
_onAddSeries: function(e) {
e.preventDefault();
this.addSeries(this.state.get('series').length);
},
// Public: Removes a series list item from the editor.
//
// Also updates the labels of the remaining series elements.
removeSeries: function (e) {
e.preventDefault();
var $el = $(e.target);
$el.parent().parent().remove();
this.onEditorSubmit();
}
});
})(jQuery, recline.View);

View File

@ -320,7 +320,7 @@ my.GraphControls = Backbone.View.extend({
<option value="columns">Columns</option> \
</select> \
</div> \
<label>Group Column (x-axis)</label> \
<label>Group Column (Axis 1)</label> \
<div class="input editor-group"> \
<select> \
<option value="">Please choose ...</option> \
@ -344,7 +344,7 @@ my.GraphControls = Backbone.View.extend({
',
templateSeriesEditor: ' \
<div class="editor-series js-series-{{seriesIndex}}"> \
<label>Series <span>{{seriesName}} (y-axis)</span> \
<label>Series <span>{{seriesName}} (Axis 2)</span> \
[<a href="#remove" class="action-remove-series">Remove</a>] \
</label> \
<div class="input"> \

View File

@ -146,11 +146,6 @@ my.Timeline = Backbone.View.extend({
if (out.toDate() == 'Invalid Date') {
return null;
} else {
// fix for moment weirdness around date parsing and time zones
// moment('1914-08-01').toDate() => 1914-08-01 00:00 +01:00
// which in iso format (with 0 time offset) is 31 July 1914 23:00
// meanwhile native new Date('1914-08-01') => 1914-08-01 01:00 +01:00
out = out.subtract('minutes', out.zone());
return out.toDate();
}
},

View File

@ -141,7 +141,7 @@ my.FilterEditor = Backbone.View.extend({
var $input = $(input);
var filterType = $input.attr('data-filter-type');
var fieldId = $input.attr('data-filter-field');
var filterIndex = parseInt($input.attr('data-filter-id'));
var filterIndex = parseInt($input.attr('data-filter-id'), 10);
var name = $input.attr('name');
var value = $input.val();
@ -162,7 +162,7 @@ my.FilterEditor = Backbone.View.extend({
break;
}
});
self.model.queryState.set({filters: filters});
self.model.queryState.set({filters: filters, from: 0});
self.model.queryState.trigger('change');
}
});

View File

@ -32,6 +32,8 @@ my.Pager = Backbone.View.extend({
e.preventDefault();
var newFrom = parseInt(this.el.find('input[name="from"]').val());
var newSize = parseInt(this.el.find('input[name="to"]').val()) - newFrom;
newFrom = Math.max(newFrom, 0);
newSize = Math.max(newSize, 1);
this.model.set({size: newSize, from: newFrom});
},
onPaginationUpdate: function(e) {
@ -43,6 +45,7 @@ my.Pager = Backbone.View.extend({
} else {
newFrom = this.model.get('from') + this.model.get('size');
}
newFrom = Math.max(newFrom, 0);
this.model.set({from: newFrom});
},
render: function() {

View File

@ -22,16 +22,16 @@ var memoryFields = [
];
var _wrapData = function() {
var dataCopy = $.extend(true, [], memoryData);
var recordsCopy = $.extend(true, [], memoryData);
// return new recline.Backend.Memory.Store(dataCopy, fields);
return new recline.Backend.Memory.Store(dataCopy, memoryFields);
return new recline.Backend.Memory.Store(recordsCopy, memoryFields);
}
test('basics', function () {
var data = _wrapData();
equal(data.fields.length, 7);
deepEqual(['id', 'date', 'x', 'y', 'z', 'country', 'label'], _.pluck(data.fields, 'id'));
equal(memoryData.length, data.data.length);
equal(memoryData.length, data.records.length);
});
test('query', function () {
@ -136,7 +136,7 @@ test('filters with nulls', function () {
equal(out.total, 4);
});
data.data[5].country = '';
data.records[5].country = '';
query = new recline.Model.Query();
query.addFilter({type: 'range', field: 'country', start: '', stop: 'Z'});
data.query(query.toJSON()).then(function(out) {
@ -154,7 +154,7 @@ test('facet', function () {
var data = _wrapData();
var query = new recline.Model.Query();
query.addFacet('country');
var out = data.computeFacets(data.data, query.toJSON());
var out = data.computeFacets(data.records, query.toJSON());
var exp = [
{
term: 'UK',
@ -179,12 +179,12 @@ test('update and delete', function () {
doc1 = $.extend(true, {}, memoryData[0]);
doc1.x = newVal;
data.update(doc1);
equal(data.data[0].x, newVal);
equal(data.records[0].x, newVal);
// Test Delete
data.remove(doc1);
equal(data.data.length, 5);
equal(data.data[0].x, memoryData[1].x);
equal(data.records.length, 5);
equal(data.records[0].x, memoryData[1].x);
});
test('transform', function () {
@ -198,8 +198,8 @@ test('transform', function () {
d.a = d.a * 10;
return d;
})
equal(store.data[0].a, 10);
equal(store.data[1].a, 20);
equal(store.records[0].a, 10);
equal(store.records[1].a, 20);
});
test('transform deletes', function () {
@ -209,7 +209,7 @@ test('transform deletes', function () {
if (d.a == '1') return null;
else return d;
})
equal(store.data.length, 2);
equal(store.records.length, 2);
});
})(this.jQuery);
@ -281,7 +281,7 @@ test('basics', function () {
test('query', function () {
var dataset = makeBackendDataset();
// convenience for tests - get the data that should get changed
var data = dataset._store.data;
var data = dataset._store.records;
var dataset = makeBackendDataset();
var queryObj = {
size: 4
@ -295,7 +295,7 @@ test('query', function () {
test('query sort', function () {
var dataset = makeBackendDataset();
// convenience for tests - get the data that should get changed
var data = dataset._store.data;
var data = dataset._store.records;
var queryObj = {
sort: [
{field: 'y', order: 'desc'}
@ -372,9 +372,9 @@ test('update and delete', function () {
// convenience for tests - get the data that should get changed
var data = dataset._store;
dataset.query().then(function(docList) {
equal(docList.length, Math.min(100, data.data.length));
equal(docList.length, Math.min(100, data.records.length));
var doc1 = docList.models[0];
deepEqual(doc1.toJSON(), data.data[0]);
deepEqual(doc1.toJSON(), data.records[0]);
// Test UPDATE
var newVal = 10;
@ -386,8 +386,8 @@ test('update and delete', function () {
deepEqual(dataset._changes.deletes[0], doc1.toJSON());
dataset.save().then(function() {
equal(data.data.length, 5);
equal(data.data[0].x, memoryData.records[1].x);
equal(data.records.length, 5);
equal(data.records[0].x, memoryData.records[1].x);
});
});
});

View File

@ -6,6 +6,7 @@
<link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen" />
<!-- need this stylesheet because flot will complain if canvas does not have a height -->
<link rel="stylesheet" href="../css/graph.css" type="text/css" media="screen" />
<link rel="stylesheet" href="../css/flot.css" type="text/css" media="screen" />
<script type="text/javascript" src="../vendor/jquery/1.7.1/jquery.js"></script>
<script type="text/javascript" src="../vendor/underscore/1.4.2/underscore.js"></script>

View File

@ -10,6 +10,7 @@
<script type="text/javascript" src="../vendor/jquery/1.7.1/jquery.js"></script>
<script type="text/javascript" src="../vendor/underscore/1.4.2/underscore.js"></script>
<script type="text/javascript" src="../vendor/underscore.deferred/0.4.0/underscore.deferred.js"></script>
<script type="text/javascript" src="../vendor/backbone/0.9.2/backbone.js"></script>
<script type="text/javascript" src="../vendor/moment/1.6.2/moment.js"></script>
<script type="text/javascript" src="../vendor/mustache/0.5.0-dev/mustache.js"></script>
@ -54,6 +55,7 @@
<script type="text/javascript" src="../src/view.slickgrid.js"></script>
<script type="text/javascript" src="../src/view.transform.js"></script>
<script type="text/javascript" src="../src/view.graph.js"></script>
<script type="text/javascript" src="../src/view.flot.js"></script>
<script type="text/javascript" src="../src/view.map.js"></script>
<script type="text/javascript" src="../src/view.timeline.js"></script>
<script type="text/javascript" src="../src/widget.pager.js"></script>
@ -65,6 +67,7 @@
<script type="text/javascript" src="view.grid.test.js"></script>
<script type="text/javascript" src="view.slickgrid.test.js"></script>
<script type="text/javascript" src="view.graph.test.js"></script>
<script type="text/javascript" src="view.flot.test.js"></script>
<script type="text/javascript" src="view.map.test.js"></script>
<script type="text/javascript" src="view.timeline.test.js"></script>
<script type="text/javascript" src="view.multiview.test.js"></script>

71
test/view.flot.test.js Normal file
View File

@ -0,0 +1,71 @@
module("View - Flot");
test('basics', function () {
var dataset = Fixture.getDataset();
var view = new recline.View.Flot({
model: dataset
});
$('.fixtures').append(view.el);
equal(view.state.get('graphType'), 'lines-and-points');
// view will auto render ...
assertPresent('.editor', view.elSidebar);
view.remove();
});
test('initialize', function () {
var dataset = Fixture.getDataset();
var view = new recline.View.Flot({
model: dataset,
state: {
'graphType': 'lines',
'group': 'x',
'series': ['y', 'z']
}
});
$('.fixtures').append(view.el);
equal(view.state.get('graphType'), 'lines');
deepEqual(view.state.get('series'), ['y', 'z']);
// check we have updated editor with state info
equal(view.elSidebar.find('.editor-type select').val(), 'lines');
equal(view.elSidebar.find('.editor-group select').val(), 'x');
var out = _.map(view.elSidebar.find('.editor-series select'), function($el) {
return $($el).val();
});
deepEqual(out, ['y', 'z']);
view.remove();
});
test('dates in graph view', function () {
expect(0);
var dataset = Fixture.getDataset();
var view = new recline.View.Flot({
model: dataset,
state: {
'graphType': 'lines',
'group': 'date',
'series': ['y', 'z']
}
});
$('.fixtures').append(view.el);
view.remove();
});
test('FlotControls basics', function () {
var dataset = Fixture.getDataset();
var view = new recline.View.FlotControls({
model: dataset,
state: {
graphType: 'bars',
series: []
}
});
$('.fixtures').append(view.el);
equal(view.state.get('graphType'), 'bars');
// view will auto render ...
assertPresent('.editor', view.el);
view.remove();
});

View File

@ -47,7 +47,7 @@ test('render etc', function () {
assertPresent('.vmm-timeline', view.el);
assertPresent('.timenav', view.el);
assertPresent('.timenav', view.el);
equal('2011', view.el.find('.marker.active h4').text());
equal(view.el.find('.marker.active h4').text(), '2011');
view.remove();
});

1428
vendor/flot/excanvas.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
vendor/flot/excanvas.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2664
vendor/flot/jquery.flot.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -501,845 +501,7 @@
return bean
});
// Underscore.js 1.1.7
// (c) 2011 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.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **CommonJS**, with backwards-compatibility
// for the old `require()` API. If we're not in CommonJS, add `_` to the
// global object.
if (typeof module !== 'undefined' && module.exports) {
module.exports = _;
_._ = _;
} else {
// Exported as a string, for Closure Compiler "advanced" mode.
root['_'] = _;
}
// Current version.
_.VERSION = '1.1.7';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = 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);
});
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = memo !== void 0;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError("Reduce of empty array with no initial value");
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return memo !== void 0 ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = (_.isArray(obj) ? obj.slice() : _.toArray(obj)).reverse();
return _.reduce(reversed, iterator, memo, context);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator = iterator || _.identity;
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result |= iterator.call(context, value, index, list)) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
any(obj, function(value) {
if (found = value === target) return true;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.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);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion produced by an iterator
_.groupBy = function(obj, iterator) {
var result = {};
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.toArray(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head`. The **guard** check allows it to work
// with `_.map`.
_.first = _.head = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Get the last element of an array.
_.last = function(array) {
return array[array.length - 1];
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(_.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted) {
return _.reduce(array, function(memo, el, i) {
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo[memo.length] = el;
return memo;
}, []);
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and another.
// Only the elements present in just the first array will remain.
_.difference = function(array, other) {
return _.filter(array, function(value){ return !_.include(other, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function(func, obj) {
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
var args = slice.call(arguments, 2);
return function() {
return func.apply(obj, args.concat(slice.call(arguments)));
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(func, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Internal function used to implement `_.throttle` and `_.debounce`.
var limit = function(func, wait, debounce) {
var timeout;
return function() {
var context = this, args = arguments;
var throttler = function() {
timeout = null;
func.apply(context, args);
};
if (debounce) clearTimeout(timeout);
if (debounce || !timeout) timeout = setTimeout(throttler, wait);
};
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
return limit(func, wait, false);
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
_.debounce = function(func, wait) {
return limit(func, wait, true);
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = slice.call(arguments);
return function() {
var args = slice.call(arguments);
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.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;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (source[prop] !== void 0) obj[prop] = source[prop];
}
});
return obj;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
// Check object identity.
if (a === b) return true;
// Different types?
var atype = typeof(a), btype = typeof(b);
if (atype != btype) return false;
// Basic equality test (watch out for coercions).
if (a == b) return true;
// One is falsy and the other truthy.
if ((!a && b) || (a && !b)) return false;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// One of them implements an isEqual()?
if (a.isEqual) return a.isEqual(b);
if (b.isEqual) return b.isEqual(a);
// Check dates' integer values.
if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
// Both are NaN?
if (_.isNaN(a) && _.isNaN(b)) return false;
// Compare regular expressions.
if (_.isRegExp(a) && _.isRegExp(b))
return a.source === b.source &&
a.global === b.global &&
a.ignoreCase === b.ignoreCase &&
a.multiline === b.multiline;
// If a is not an object by this point, we can't handle it.
if (atype !== 'object') return false;
// Check for different array lengths before comparing contents.
if (a.length && (a.length !== b.length)) return false;
// Nothing else worked, deep compare the contents.
var aKeys = _.keys(a), bKeys = _.keys(b);
// Different object sizes?
if (aKeys.length != bKeys.length) return false;
// Recursive comparison of contents.
for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
return true;
};
// Is a given array or object empty?
_.isEmpty = function(obj) {
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) === '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return !!(obj && hasOwnProperty.call(obj, 'callee'));
};
// Is a given value a function?
_.isFunction = function(obj) {
return !!(obj && obj.constructor && obj.call && obj.apply);
};
// Is a given value a string?
_.isString = function(obj) {
return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
};
// Is a given value a number?
_.isNumber = function(obj) {
return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed));
};
// Is the given value `NaN`? `NaN` happens to be the only value in JavaScript
// that does not equal itself.
_.isNaN = function(obj) {
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false;
};
// Is a given value a date?
_.isDate = function(obj) {
return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g
};
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(str, data) {
var c = _.templateSettings;
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(c.interpolate, function(match, code) {
return "'," + code.replace(/\\'/g, "'") + ",'";
})
.replace(c.evaluate || null, function(match, code) {
return "');" + code.replace(/\\'/g, "'")
.replace(/[\r\n\t]/g, ' ') + "__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
+ "');}return __p.join('');";
var func = new Function('obj', tmpl);
return data ? func(data) : func;
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
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);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
})();
/**
* Flotr2 (c) 2012 Carl Sutherland
* MIT License

View File

@ -1,99 +0,0 @@
/*
* jQuery Hotkeys Plugin
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
*
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
*
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
*/
(function(jQuery){
jQuery.hotkeys = {
version: "0.8",
specialKeys: {
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
},
shiftNums: {
"`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
"8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
".": ">", "/": "?", "\\": "|"
}
};
function keyHandler( handleObj ) {
// Only care when a possible input has been specified
if ( typeof handleObj.data !== "string" ) {
return;
}
var origHandler = handleObj.handler,
keys = handleObj.data.toLowerCase().split(" ");
handleObj.handler = function( event ) {
// Don't fire in text-accepting inputs that we didn't directly bind to
if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) ||
event.target.type === "text") ) {
return;
}
// Keypress represents characters, not special keys
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ],
character = String.fromCharCode( event.which ).toLowerCase(),
key, modif = "", possible = {};
// check combinations (alt|ctrl|shift+anything)
if ( event.altKey && special !== "alt" ) {
modif += "alt+";
}
if ( event.ctrlKey && special !== "ctrl" ) {
modif += "ctrl+";
}
// TODO: Need to make sure this works consistently across platforms
if ( event.metaKey && !event.ctrlKey && special !== "meta" ) {
modif += "meta+";
}
if ( event.shiftKey && special !== "shift" ) {
modif += "shift+";
}
if ( special ) {
possible[ modif + special ] = true;
} else {
possible[ modif + character ] = true;
possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true;
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
if ( modif === "shift+" ) {
possible[ jQuery.hotkeys.shiftNums[ character ] ] = true;
}
}
for ( var i = 0, l = keys.length; i < l; i++ ) {
if ( possible[ keys[i] ] ) {
return origHandler.apply( this, arguments );
}
}
};
}
jQuery.each([ "keydown", "keyup", "keypress" ], function() {
jQuery.event.special[ this ] = { add: keyHandler };
});
})( jQuery );

675
vendor/traverse.js vendored
View File

@ -1,675 +0,0 @@
var require = function (file, cwd) {
var resolved = require.resolve(file, cwd || '/');
var mod = require.modules[resolved];
if (!mod) throw new Error(
'Failed to resolve module ' + file + ', tried ' + resolved
);
var res = mod._cached ? mod._cached : mod();
return res;
}
var __require = require;
require.paths = [];
require.modules = {};
require.extensions = [".js",".coffee"];
require.resolve = (function () {
var core = {
'assert': true,
'events': true,
'fs': true,
'path': true,
'vm': true
};
return function (x, cwd) {
if (!cwd) cwd = '/';
if (core[x]) return x;
var path = require.modules.path();
var y = cwd || '.';
if (x.match(/^(?:\.\.?\/|\/)/)) {
var m = loadAsFileSync(path.resolve(y, x))
|| loadAsDirectorySync(path.resolve(y, x));
if (m) return m;
}
var n = loadNodeModulesSync(x, y);
if (n) return n;
throw new Error("Cannot find module '" + x + "'");
function loadAsFileSync (x) {
if (require.modules[x]) {
return x;
}
for (var i = 0; i < require.extensions.length; i++) {
var ext = require.extensions[i];
if (require.modules[x + ext]) return x + ext;
}
}
function loadAsDirectorySync (x) {
x = x.replace(/\/+$/, '');
var pkgfile = x + '/package.json';
if (require.modules[pkgfile]) {
var pkg = require.modules[pkgfile]();
var b = pkg.browserify;
if (typeof b === 'object' && b.main) {
var m = loadAsFileSync(path.resolve(x, b.main));
if (m) return m;
}
else if (typeof b === 'string') {
var m = loadAsFileSync(path.resolve(x, b));
if (m) return m;
}
else if (pkg.main) {
var m = loadAsFileSync(path.resolve(x, pkg.main));
if (m) return m;
}
}
return loadAsFileSync(x + '/index');
}
function loadNodeModulesSync (x, start) {
var dirs = nodeModulesPathsSync(start);
for (var i = 0; i < dirs.length; i++) {
var dir = dirs[i];
var m = loadAsFileSync(dir + '/' + x);
if (m) return m;
var n = loadAsDirectorySync(dir + '/' + x);
if (n) return n;
}
var m = loadAsFileSync(x);
if (m) return m;
}
function nodeModulesPathsSync (start) {
var parts;
if (start === '/') parts = [ '' ];
else parts = path.normalize(start).split('/');
var dirs = [];
for (var i = parts.length - 1; i >= 0; i--) {
if (parts[i] === 'node_modules') continue;
var dir = parts.slice(0, i + 1).join('/') + '/node_modules';
dirs.push(dir);
}
return dirs;
}
};
})();
require.alias = function (from, to) {
var path = require.modules.path();
var res = null;
try {
res = require.resolve(from + '/package.json', '/');
}
catch (err) {
res = require.resolve(from, '/');
}
var basedir = path.dirname(res);
Object.keys(require.modules)
.forEach(function (x) {
if (x.slice(0, basedir.length + 1) === basedir + '/') {
var f = x.slice(basedir.length);
require.modules[to + f] = require.modules[basedir + f];
}
else if (x === basedir) {
require.modules[to] = require.modules[basedir];
}
})
;
};
if (typeof process === 'undefined') process = {};
if (!process.nextTick) process.nextTick = function (fn) {
setTimeout(fn, 0);
};
if (!process.title) process.title = 'browser';
if (!process.binding) process.binding = function (name) {
if (name === 'evals') return require('vm')
else throw new Error('No such module')
};
if (!process.cwd) process.cwd = function () { return '.' };
require.modules["path"] = function () {
var module = { exports : {} };
var exports = module.exports;
var __dirname = ".";
var __filename = "path";
var require = function (file) {
return __require(file, ".");
};
require.resolve = function (file) {
return __require.resolve(name, ".");
};
require.modules = __require.modules;
__require.modules["path"]._cached = module.exports;
(function () {
// resolves . and .. elements in a path array with directory names there
// must be no slashes, empty elements, or device names (c:\) in the array
// (so also no leading and trailing slashes - it does not distinguish
// relative and absolute paths)
function normalizeArray(parts, allowAboveRoot) {
// if the path tries to go above the root, `up` ends up > 0
var up = 0;
for (var i = parts.length; i >= 0; i--) {
var last = parts[i];
if (last == '.') {
parts.splice(i, 1);
} else if (last === '..') {
parts.splice(i, 1);
up++;
} else if (up) {
parts.splice(i, 1);
up--;
}
}
// if the path is allowed to go above the root, restore leading ..s
if (allowAboveRoot) {
for (; up--; up) {
parts.unshift('..');
}
}
return parts;
}
// Regex to split a filename into [*, dir, basename, ext]
// posix version
var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;
// path.resolve([from ...], to)
// posix version
exports.resolve = function() {
var resolvedPath = '',
resolvedAbsolute = false;
for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) {
var path = (i >= 0)
? arguments[i]
: process.cwd();
// Skip empty and invalid entries
if (typeof path !== 'string' || !path) {
continue;
}
resolvedPath = path + '/' + resolvedPath;
resolvedAbsolute = path.charAt(0) === '/';
}
// At this point the path should be resolved to a full absolute path, but
// handle relative paths to be safe (might happen when process.cwd() fails)
// Normalize the path
resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
return !!p;
}), !resolvedAbsolute).join('/');
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
};
// path.normalize(path)
// posix version
exports.normalize = function(path) {
var isAbsolute = path.charAt(0) === '/',
trailingSlash = path.slice(-1) === '/';
// Normalize the path
path = normalizeArray(path.split('/').filter(function(p) {
return !!p;
}), !isAbsolute).join('/');
if (!path && !isAbsolute) {
path = '.';
}
if (path && trailingSlash) {
path += '/';
}
return (isAbsolute ? '/' : '') + path;
};
// posix version
exports.join = function() {
var paths = Array.prototype.slice.call(arguments, 0);
return exports.normalize(paths.filter(function(p, index) {
return p && typeof p === 'string';
}).join('/'));
};
exports.dirname = function(path) {
var dir = splitPathRe.exec(path)[1] || '';
var isWindows = false;
if (!dir) {
// No dirname
return '.';
} else if (dir.length === 1 ||
(isWindows && dir.length <= 3 && dir.charAt(1) === ':')) {
// It is just a slash or a drive letter with a slash
return dir;
} else {
// It is a full dirname, strip trailing slash
return dir.substring(0, dir.length - 1);
}
};
exports.basename = function(path, ext) {
var f = splitPathRe.exec(path)[2] || '';
// TODO: make this comparison case-insensitive on windows?
if (ext && f.substr(-1 * ext.length) === ext) {
f = f.substr(0, f.length - ext.length);
}
return f;
};
exports.extname = function(path) {
return splitPathRe.exec(path)[3] || '';
};
;
}).call(module.exports);
__require.modules["path"]._cached = module.exports;
return module.exports;
};
require.modules["/node_modules/traverse/package.json"] = function () {
var module = { exports : {} };
var exports = module.exports;
var __dirname = "/node_modules/traverse";
var __filename = "/node_modules/traverse/package.json";
var require = function (file) {
return __require(file, "/node_modules/traverse");
};
require.resolve = function (file) {
return __require.resolve(name, "/node_modules/traverse");
};
require.modules = __require.modules;
__require.modules["/node_modules/traverse/package.json"]._cached = module.exports;
(function () {
module.exports = {"name":"traverse","version":"0.4.4","description":"Traverse and transform objects by visiting every node on a recursive walk","author":"James Halliday","license":"MIT/X11","main":"./index","repository":{"type":"git","url":"http://github.com/substack/js-traverse.git"},"devDependencies":{"expresso":"0.7.x"},"scripts":{"test":"expresso"}};
}).call(module.exports);
__require.modules["/node_modules/traverse/package.json"]._cached = module.exports;
return module.exports;
};
require.modules["/node_modules/traverse/index.js"] = function () {
var module = { exports : {} };
var exports = module.exports;
var __dirname = "/node_modules/traverse";
var __filename = "/node_modules/traverse/index.js";
var require = function (file) {
return __require(file, "/node_modules/traverse");
};
require.resolve = function (file) {
return __require.resolve(name, "/node_modules/traverse");
};
require.modules = __require.modules;
__require.modules["/node_modules/traverse/index.js"]._cached = module.exports;
(function () {
module.exports = Traverse;
function Traverse (obj) {
if (!(this instanceof Traverse)) return new Traverse(obj);
this.value = obj;
}
Traverse.prototype.get = function (ps) {
var node = this.value;
for (var i = 0; i < ps.length; i ++) {
var key = ps[i];
if (!Object.hasOwnProperty.call(node, key)) {
node = undefined;
break;
}
node = node[key];
}
return node;
};
Traverse.prototype.set = function (ps, value) {
var node = this.value;
for (var i = 0; i < ps.length - 1; i ++) {
var key = ps[i];
if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
node = node[key];
}
node[ps[i]] = value;
return value;
};
Traverse.prototype.map = function (cb) {
return walk(this.value, cb, true);
};
Traverse.prototype.forEach = function (cb) {
this.value = walk(this.value, cb, false);
return this.value;
};
Traverse.prototype.reduce = function (cb, init) {
var skip = arguments.length === 1;
var acc = skip ? this.value : init;
this.forEach(function (x) {
if (!this.isRoot || !skip) {
acc = cb.call(this, acc, x);
}
});
return acc;
};
Traverse.prototype.deepEqual = function (obj) {
if (arguments.length !== 1) {
throw new Error(
'deepEqual requires exactly one object to compare against'
);
}
var equal = true;
var node = obj;
this.forEach(function (y) {
var notEqual = (function () {
equal = false;
//this.stop();
return undefined;
}).bind(this);
//if (node === undefined || node === null) return notEqual();
if (!this.isRoot) {
/*
if (!Object.hasOwnProperty.call(node, this.key)) {
return notEqual();
}
*/
if (typeof node !== 'object') return notEqual();
node = node[this.key];
}
var x = node;
this.post(function () {
node = x;
});
var toS = function (o) {
return Object.prototype.toString.call(o);
};
if (this.circular) {
if (Traverse(obj).get(this.circular.path) !== x) notEqual();
}
else if (typeof x !== typeof y) {
notEqual();
}
else if (x === null || y === null || x === undefined || y === undefined) {
if (x !== y) notEqual();
}
else if (x.__proto__ !== y.__proto__) {
notEqual();
}
else if (x === y) {
// nop
}
else if (typeof x === 'function') {
if (x instanceof RegExp) {
// both regexps on account of the __proto__ check
if (x.toString() != y.toString()) notEqual();
}
else if (x !== y) notEqual();
}
else if (typeof x === 'object') {
if (toS(y) === '[object Arguments]'
|| toS(x) === '[object Arguments]') {
if (toS(x) !== toS(y)) {
notEqual();
}
}
else if (x instanceof Date || y instanceof Date) {
if (!(x instanceof Date) || !(y instanceof Date)
|| x.getTime() !== y.getTime()) {
notEqual();
}
}
else {
var kx = Object.keys(x);
var ky = Object.keys(y);
if (kx.length !== ky.length) return notEqual();
for (var i = 0; i < kx.length; i++) {
var k = kx[i];
if (!Object.hasOwnProperty.call(y, k)) {
notEqual();
}
}
}
}
});
return equal;
};
Traverse.prototype.paths = function () {
var acc = [];
this.forEach(function (x) {
acc.push(this.path);
});
return acc;
};
Traverse.prototype.nodes = function () {
var acc = [];
this.forEach(function (x) {
acc.push(this.node);
});
return acc;
};
Traverse.prototype.clone = function () {
var parents = [], nodes = [];
return (function clone (src) {
for (var i = 0; i < parents.length; i++) {
if (parents[i] === src) {
return nodes[i];
}
}
if (typeof src === 'object' && src !== null) {
var dst = copy(src);
parents.push(src);
nodes.push(dst);
Object.keys(src).forEach(function (key) {
dst[key] = clone(src[key]);
});
parents.pop();
nodes.pop();
return dst;
}
else {
return src;
}
})(this.value);
};
function walk (root, cb, immutable) {
var path = [];
var parents = [];
var alive = true;
return (function walker (node_) {
var node = immutable ? copy(node_) : node_;
var modifiers = {};
var keepGoing = true;
var state = {
node : node,
node_ : node_,
path : [].concat(path),
parent : parents[parents.length - 1],
key : path.slice(-1)[0],
isRoot : path.length === 0,
level : path.length,
circular : null,
update : function (x, stopHere) {
if (!state.isRoot) {
state.parent.node[state.key] = x;
}
state.node = x;
if (stopHere) keepGoing = false;
},
'delete' : function () {
delete state.parent.node[state.key];
},
remove : function () {
if (Array.isArray(state.parent.node)) {
state.parent.node.splice(state.key, 1);
}
else {
delete state.parent.node[state.key];
}
},
before : function (f) { modifiers.before = f },
after : function (f) { modifiers.after = f },
pre : function (f) { modifiers.pre = f },
post : function (f) { modifiers.post = f },
stop : function () { alive = false },
block : function () { keepGoing = false }
};
if (!alive) return state;
if (typeof node === 'object' && node !== null) {
state.isLeaf = Object.keys(node).length == 0;
for (var i = 0; i < parents.length; i++) {
if (parents[i].node_ === node_) {
state.circular = parents[i];
break;
}
}
}
else {
state.isLeaf = true;
}
state.notLeaf = !state.isLeaf;
state.notRoot = !state.isRoot;
// use return values to update if defined
var ret = cb.call(state, state.node);
if (ret !== undefined && state.update) state.update(ret);
state.keys = null;
if (modifiers.before) modifiers.before.call(state, state.node);
if (!keepGoing) return state;
if (typeof state.node == 'object'
&& state.node !== null && !state.circular) {
parents.push(state);
var keys = state.keys || Object.keys(state.node);
keys.forEach(function (key, i) {
path.push(key);
if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
var child = walker(state.node[key]);
if (immutable && Object.hasOwnProperty.call(state.node, key)) {
state.node[key] = child.node;
}
child.isLast = i == keys.length - 1;
child.isFirst = i == 0;
if (modifiers.post) modifiers.post.call(state, child);
path.pop();
});
parents.pop();
}
if (modifiers.after) modifiers.after.call(state, state.node);
return state;
})(root).node;
}
Object.keys(Traverse.prototype).forEach(function (key) {
Traverse[key] = function (obj) {
var args = [].slice.call(arguments, 1);
var t = Traverse(obj);
return t[key].apply(t, args);
};
});
function copy (src) {
if (typeof src === 'object' && src !== null) {
var dst;
if (Array.isArray(src)) {
dst = [];
}
else if (src instanceof Date) {
dst = new Date(src);
}
else if (src instanceof Boolean) {
dst = new Boolean(src);
}
else if (src instanceof Number) {
dst = new Number(src);
}
else if (src instanceof String) {
dst = new String(src);
}
else {
dst = Object.create(Object.getPrototypeOf(src));
}
Object.keys(src).forEach(function (key) {
dst[key] = src[key];
});
return dst;
}
else return src;
}
;
}).call(module.exports);
__require.modules["/node_modules/traverse/index.js"]._cached = module.exports;
return module.exports;
};

View File

@ -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);