Merge branch 'master' into gh-pages
This commit is contained in:
commit
6a6bb89699
24
README.md
24
README.md
@ -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)
|
||||
|
||||
|
||||
@ -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
26
css/flot.css
Normal 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
26
dist/recline.css
vendored
@ -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;
|
||||
}
|
||||
|
||||
50
dist/recline.dataset.js
vendored
50
dist/recline.dataset.js
vendored
@ -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
621
dist/recline.js
vendored
@ -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() {
|
||||
|
||||
@ -68,15 +68,23 @@ title: Download
|
||||
|
||||
### Dependencies
|
||||
|
||||
Recline has dependencies on some third-party libraries, notably JQuery and Backbone:
|
||||
Recline has dependencies on some third-party libraries. Specifically, recline.dataset.js depends on:
|
||||
|
||||
* [Underscore](http://documentcloud.github.com/underscore/) >= 1.0
|
||||
* [Underscore Deferred](https://github.com/wookiehangover/underscore.deferred) v0.4.0
|
||||
* [Backbone](http://backbonejs.org/) >= 0.5.1
|
||||
|
||||
Those backends which utilize jquery's ajax method depend on jQuery:
|
||||
|
||||
* [JQuery](http://jquery.com/) >= 1.6
|
||||
* [Backbone](http://backbonejs.org/) >= 0.5.1
|
||||
* [Underscore](http://documentcloud.github.com/underscore/) >= 1.0
|
||||
|
||||
Optional dependencies:
|
||||
All the views require, in addition to those needed for recline.dataset.js:
|
||||
|
||||
* [JQuery](http://jquery.com/) >= 1.6
|
||||
* [Mustache.js](https://github.com/janl/mustache.js/) >= 0.5.0-dev (required for all views)
|
||||
|
||||
Individual views have additional dependencies such as:
|
||||
|
||||
* [JQuery Flot](http://code.google.com/p/flot/) >= 0.7 (required for for graph view)
|
||||
* [Leaflet](http://leaflet.cloudmade.com/) >= 0.4.4 (required for map view)
|
||||
* [Leaflet.markercluster](https://github.com/danzel/Leaflet.markercluster) as of 2012-09-12 (required for marker clustering)
|
||||
@ -84,7 +92,8 @@ Optional dependencies:
|
||||
* [Bootstrap](http://twitter.github.com/bootstrap/) >= v2.0 (default option for CSS and UI JS but you can use your own)
|
||||
|
||||
If you grab the full zipball for Recline this will include all of the relevant
|
||||
dependencies in the vendor directory.
|
||||
dependencies in the vendor directory and you can also find them at in the
|
||||
[github repo here](https://github.com/okfn/recline/tree/master/vendor).
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
2
make
2
make
@ -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':
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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,
|
||||
|
||||
13
src/model.js
13
src/model.js
@ -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
502
src/view.flot.js
Normal 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);
|
||||
@ -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"> \
|
||||
|
||||
@ -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();
|
||||
}
|
||||
},
|
||||
|
||||
@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
71
test/view.flot.test.js
Normal 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();
|
||||
});
|
||||
|
||||
@ -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
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
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
2664
vendor/flot/jquery.flot.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
838
vendor/flotr2/flotr2.js
vendored
838
vendor/flotr2/flotr2.js
vendored
@ -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
|
||||
|
||||
99
vendor/jquery.hotkeys.js
vendored
99
vendor/jquery.hotkeys.js
vendored
@ -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
675
vendor/traverse.js
vendored
@ -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;
|
||||
};
|
||||
445
vendor/underscore.deferred/0.4.0/underscore.deferred.js
vendored
Normal file
445
vendor/underscore.deferred/0.4.0/underscore.deferred.js
vendored
Normal 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);
|
||||
Loading…
x
Reference in New Issue
Block a user