From fa3bb6676e8918fcf6a0cd855dfcc7ccbf5ccb16 Mon Sep 17 00:00:00 2001
From: Rufus Pollock CSS: the demo utilizes bootstrap but you can integrate with your own HTML and CSS. Data Explorer specific CSS can be found here in the repo: https://github.com/okfn/recline/tree/master/css. More details and examples: see docs below and the Demo (hit view source). The javascript you want for
- actual integration is in: app.js. Recline has a simple structure layered on top of the basic Model/View
distinction inherent in Backbone. There are the following three domain objects (all Backbone Models): More details and examples: see docs below and the Demo -- just hit view source (NB: the javascript for the
+ demo is in: app.js).
+
@@ -98,35 +101,6 @@
Using It
-
-// Note: you should have included the relevant JS libraries (and CSS)
-// See above for dependencies
-
-// Dataset is a Backbone model
-var dataset = recline.Model.Dataset({
- id: 'my-id'
- backend: {
- // backend ID so we can look backend up in the registry (see below)
- type: 'memory'
- // other backend config (e.g. API url with which to communicate)
- // this will usually be backend specific
- ...
- }
-});
-// DataExplorer is a Backbone View
-var explorer = recline.View.DataExplorer({
- model: dataset,
- // you can specify any element to bind to in the dom
- el: $('.data-explorer-here')
-});
-// Start Backbone routing (if you want routing support)
-Backbone.history.start();
-
- Documentation
Source
+ Using It
+
+// Note: you should have included the relevant JS libraries (and CSS)
+// See above for dependencies
+
+// Dataset is a Backbone model so the first hash become model attributes
+var dataset = recline.Model.Dataset({
+ id: 'my-id'
+ },
+ // Either a backend instance or string id for a backend in the registry
+ backend
+);
+// DataExplorer is a Backbone View
+var explorer = recline.View.DataExplorer({
+ model: dataset,
+ // you can specify any element to bind to in the dom
+ el: $('.data-explorer-here')
+});
+// Start Backbone routing (if you want routing support)
+Backbone.history.start();
+
+ Source Docs (via Docco)
this.recline = this.recline || {};
this.recline.Model = this.recline.Model || {};
-(function($, my) {
- my.backends = {};
+(function($, my) {Override Backbone.sync to hand off to sync function in relevant backend
Backbone.sync = function(method, model, options) {
+ return model.backend.sync(method, model, options);
+ }To use you should:
+Crude way to catch backend errors +Many of backends use JSONP and so will not get error messages and this is +a crude way to catch those errors.
function wrapInTimeout(ourFunction) {
+ var dfd = $.Deferred();
+ var timeout = 5000;
+ var timer = setTimeout(function() {
+ dfd.reject({
+ message: 'Request Error: Backend did not respond after ' + (timeout / 1000) + ' seconds'
+ });
+ }, timeout);
+ ourFunction.done(function(arguments) {
+ clearTimeout(timer);
+ dfd.resolve(arguments);
+ })
+ .fail(function(arguments) {
+ clearTimeout(timer);
+ dfd.reject(arguments);
+ })
+ ;
+ return dfd.promise();
+ }A. provide metadata as model data to the Dataset
+This is very artificial and is really only designed for testing +purposes.
-B. Set backendConfig on your dataset with attributes:
+To use it you should provide in your constructor data:
data: hash with 2 keys:
+documents: list of hashes, each hash being one doc. A doc must have an id attribute which is unique.
-Example of data:
+Example:
- {
- headers: ['x', 'y', 'z']
- , rows: [
- {id: 0, x: 1, y: 2, z: 3}
- , {id: 1, x: 2, y: 4, z: 6}
- ]
- };
+// Backend setup
+var backend = Backend();
+backend.addDataset({
+metadata: {
+ id: 'my-id',
+ title: 'My Title',
+ headers: ['x', 'y', 'z'],
+},
+documents: [
+ {id: 0, x: 1, y: 2, z: 3},
+ {id: 1, x: 2, y: 4, z: 6}
+ ]
+});
+// later ...
+var dataset = Dataset({id: 'my-id'});
+dataset.fetch();
+etc ...
my.BackendMemory = Backbone.Model.extend({
- sync: function(method, model, options) {
- var self = this;
- if (method === "read") {
- var dfd = $.Deferred();
- if (model.__type__ == 'Dataset') {
- var dataset = model;
- dataset.set({
- headers: dataset.backendConfig.data.headers
- });
- dataset.docCount = dataset.backendConfig.data.rows.length;
- dfd.resolve(dataset);
- }
- return dfd.promise();
- } else if (method === 'update') {
- var dfd = $.Deferred();
- if (model.__type__ == 'Document') {
- _.each(model.backendConfig.data.rows, function(row, idx) {
- if(row.id === model.id) {
- model.backendConfig.data.rows[idx] = model.toJSON();
- }
- });
- dfd.resolve(model);
- }
- return dfd.promise();
- } else if (method === 'delete') {
- var dfd = $.Deferred();
- if (model.__type__ == 'Document') {
- model.backendConfig.data.rows = _.reject(model.backendConfig.data.rows, function(row) {
- return (row.id === model.id);
- });
- dfd.resolve(model);
- }
- return dfd.promise();
- } else {
- alert('Not supported: sync on BackendMemory with method ' + method + ' and model ' + model);
- }
- },
- getDocuments: function(model, numRows, start) {
- if (start === undefined) {
- start = 0;
- }
- if (numRows === undefined) {
- numRows = 10;
- }
+ initialize: function() {
+ this.datasets = {};
+ },
+ addDataset: function(data) {
+ this.datasets[data.metadata.id] = $.extend(true, {}, data);
+ },
+ sync: function(method, model, options) {
+ var self = this;
+ if (method === "read") {
var dfd = $.Deferred();
- rows = model.backendConfig.data.rows;
- var results = rows.slice(start, start+numRows);
- dfd.resolve(results);
+ if (model.__type__ == 'Dataset') {
+ var rawDataset = this.datasets[model.id];
+ model.set(rawDataset.metadata);
+ model.docCount = rawDataset.documents.length;
+ dfd.resolve(model);
+ }
return dfd.promise();
+ } else if (method === 'update') {
+ var dfd = $.Deferred();
+ if (model.__type__ == 'Document') {
+ _.each(self.datasets[model.dataset.id].documents, function(doc, idx) {
+ if(doc.id === model.id) {
+ self.datasets[model.dataset.id].documents[idx] = model.toJSON();
+ }
+ });
+ dfd.resolve(model);
+ }
+ return dfd.promise();
+ } else if (method === 'delete') {
+ var dfd = $.Deferred();
+ if (model.__type__ == 'Document') {
+ var rawDataset = self.datasets[model.dataset.id];
+ var newdocs = _.reject(rawDataset.documents, function(doc) {
+ return (doc.id === model.id);
+ });
+ rawDataset.documents = newdocs;
+ dfd.resolve(model);
+ }
+ return dfd.promise();
+ } else {
+ alert('Not supported: sync on BackendMemory with method ' + method + ' and model ' + model);
}
+ },
+ query: function(model, queryObj) {
+ var numRows = queryObj.size;
+ var start = queryObj.offset;
+ var dfd = $.Deferred();
+ results = this.datasets[model.id].documents;not complete sorting!
_.each(queryObj.sort, function(item) {
+ results = _.sortBy(results, function(doc) {
+ var _out = doc[item[0]];
+ return (item[1] == 'asc') ? _out : -1*_out;
+ });
+ });
+ var results = results.slice(start, start+numRows);
+ dfd.resolve(results);
+ return dfd.promise();
+ }
});
- my.backends['memory'] = new my.BackendMemory();Connecting to Webstores
-To use this backend set backendConfig on your Dataset as:
- -
-{
- 'type': 'webstore',
- 'url': url to relevant Webstore table
-}
- my.BackendWebstore = Backbone.Model.extend({
+To use this backend ensure your Dataset has a webstore_url in its attributes.
my.BackendWebstore = Backbone.Model.extend({
sync: function(method, model, options) {
if (method === "read") {
if (model.__type__ == 'Dataset') {
- var dataset = model;
- var base = dataset.backendConfig.url;
+ var base = model.get('webstore_url');
var schemaUrl = base + '/schema.json';
var jqxhr = $.ajax({
url: schemaUrl,
@@ -114,72 +140,72 @@ source) For connecting to DataProxy-s.
-Set a Dataset to use this backend:
- -dataset.backendConfig = {
- // required
- url: {url-of-data-to-proxy},
- format: csv | xls,
-}
-
-
When initializing the DataProxy backend you can set the following attributes:
Datasets using using this backend should set the following attributes:
+ +Note that this is a read-only backend.
my.BackendDataProxy = Backbone.Model.extend({
defaults: {
- dataproxy: 'http://jsonpdataproxy.appspot.com'
+ dataproxy_url: 'http://jsonpdataproxy.appspot.com'
},
sync: function(method, model, options) {
+ var self = this;
if (method === "read") {
if (model.__type__ == 'Dataset') {
- var dataset = model;
- var base = my.backends['dataproxy'].get('dataproxy');TODO: should we cache for extra efficiency
var data = {
- url: dataset.backendConfig.url
+ var base = self.get('dataproxy_url');TODO: should we cache for extra efficiency
var data = {
+ url: model.get('url')
, 'max-results': 1
- , type: dataset.backendConfig.format
+ , type: model.get('format') || 'csv'
};
var jqxhr = $.ajax({
url: base
@@ -187,11 +213,14 @@ source) Connect to Google Docs spreadsheet. For write operations
my.BackendGDoc = Backbone.Model.extend({
+Connect to Google Docs spreadsheet.
+
+Dataset must have a url attribute pointing to the Gdocs
+spreadsheet's JSON feed e.g.
+
+
+var dataset = new recline.Model.Dataset({
+ url: 'https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json'
+ },
+ 'gdocs'
+);
+ my.BackendGDoc = Backbone.Model.extend({
sync: function(method, model, options) {
+ var self = this;
if (method === "read") {
var dfd = $.Deferred();
var dataset = model;
- $.getJSON(model.backendConfig.url, function(d) {
- result = my.backends['gdocs'].gdocsToJavascript(d);
- model.set({'headers': result.header});cache data onto dataset (we have loaded whole gdoc it seems!)
model._dataCache = result.data;
+ $.getJSON(model.get('url'), function(d) {
+ result = self.gdocsToJavascript(d);
+ model.set({'headers': result.header});cache data onto dataset (we have loaded whole gdoc it seems!)
model._dataCache = result.data;
dfd.resolve(model);
})
return dfd.promise(); }
},
- getDocuments: function(dataset, start, numRows) {
+ query: function(dataset, queryObj) {
var dfd = $.Deferred();
- var fields = dataset.get('headers');zip the field headers with the data rows to produce js objs + var fields = dataset.get('headers');
zip the field headers with the data rows to produce js objs TODO: factor this out as a common method with other backends
var objs = _.map(dataset._dataCache, function (d) {
var obj = {};
_.each(_.zip(fields, d), function (x) { obj[x[0]] = x[1]; })
@@ -274,11 +309,11 @@ TODO: factor this out as a common method with other backends var results = {
'header': [],
'data': []
- };default is no special info on type of columns
var colTypes = {};
+ };default is no special info on type of columns
var colTypes = {};
if (options.colTypes) {
colTypes = options.colTypes;
- }either extract column headings from spreadsheet directly, or used supplied ones
if (options.columnsToUse) {columns set to subset supplied
results.header = options.columnsToUse;
- } else {set columns to use to be all available
if (gdocsSpreadsheet.feed.entry.length > 0) {
+ }either extract column headings from spreadsheet directly, or used supplied ones
if (options.columnsToUse) {columns set to subset supplied
results.header = options.columnsToUse;
+ } else {set columns to use to be all available
if (gdocsSpreadsheet.feed.entry.length > 0) {
for (var k in gdocsSpreadsheet.feed.entry[0]) {
if (k.substr(0, 3) == 'gsx') {
var col = k.substr(4)
@@ -286,13 +321,13 @@ TODO: factor this out as a common method with other backends }
}
}
- }converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])
var rep = /^([\d\.\-]+)\%$/;
+ }converts non numberical values that should be numerical (22.3%[string] -> 0.223[float])
var rep = /^([\d\.\-]+)\%$/;
$.each(gdocsSpreadsheet.feed.entry, function (i, entry) {
var row = [];
for (var k in results.header) {
var col = results.header[k];
var _keyname = 'gsx$' + col;
- var value = entry[_keyname]['$t'];if labelled as % and value contains %, convert
if (colTypes[col] == 'percent') {
+ var value = entry[_keyname]['$t'];if labelled as % and value contains %, convert
if (colTypes[col] == 'percent') {
if (rep.test(value)) {
var value2 = rep.exec(value);
var value3 = parseFloat(value2);
diff --git a/docs/model.html b/docs/model.html
index 74e34674..f6fee7df 100644
--- a/docs/model.html
+++ b/docs/model.html
@@ -10,11 +10,17 @@
my.Dataset = Backbone.Model.extend({
__type__: 'Dataset',
- initialize: function(options) {
+ initialize: function(model, backend) {
+ this.backend = backend;
+ if (backend && backend.constructor == String) {
+ this.backend = my.backends[backend];
+ }
this.currentDocuments = new my.DocumentList();
this.docCount = null;
- this.backend = null;
- },this.queryState = {};
},AJAX method with promise API to get rows (documents) from the backend.
@@ -25,19 +31,23 @@ also returned. :param start: passed onto backend getDocuments.this does not fit very well with Backbone setup. Backbone really expects you to know the ids of objects your are fetching (which you do in classic RESTful ajax-y world). But this paradigm does not fill well with data set up we have here. -This also illustrates the limitations of separating the Dataset and the Backend
getDocuments: function(numRows, start) {
+This also illustrates the limitations of separating the Dataset and the Backend query: function(queryObj) {
var self = this;
- var backend = my.backends[this.backendConfig.type];
+ this.queryState = queryObj || this.defaultQuery;
+ this.queryState = _.extend({size: 100, offset: 0}, this.queryState);
var dfd = $.Deferred();
- backend.getDocuments(this, numRows, start).then(function(rows) {
+ this.backend.query(this, this.queryState).done(function(rows) {
var docs = _.map(rows, function(row) {
var _doc = new my.Document(row);
- _doc.backendConfig = self.backendConfig;
- _doc.backend = backend;
+ _doc.backend = self.backend;
+ _doc.dataset = self;
return _doc;
});
self.currentDocuments.reset(docs);
dfd.resolve(self.currentDocuments);
+ })
+ .fail(function(arguments) {
+ dfd.reject(arguments);
});
return dfd.promise();
},
@@ -47,14 +57,17 @@ This also illustrates the limitations of separating the Dataset and the Backend<
data.docCount = this.docCount;
return data;
}
- });A single entry or row in the dataset
my.Document = Backbone.Model.extend({
__type__: 'Document'
- }); my.DocumentList = Backbone.Collection.extend({
+ }); my.DocumentList = Backbone.Collection.extend({
__type__: 'DocumentList',
model: my.Document
- });
+ });Backends will register themselves by id into this registry
my.backends = {};
+
}(jQuery, this.recline.Model));