From 9eeb4ab97a424078a986b19047426ad1cf6e6c68 Mon Sep 17 00:00:00 2001 From: James Casbon Date: Sat, 28 Jan 2012 19:44:07 +0000 Subject: [PATCH] google spreadsheet backend --- src/model.js | 107 +++++++++++++++++++++++ test/model.test.js | 205 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 312 insertions(+) diff --git a/src/model.js b/src/model.js index 30054f4b..5472aa8e 100644 --- a/src/model.js +++ b/src/model.js @@ -313,7 +313,114 @@ my.BackendDataProxy = Backbone.Model.extend({ } }); + +// Google spreadsheet backend +my.BackendGDoc = Backbone.Model.extend({ + getDataset: function(id) { + var dataset = new my.Dataset({ + id: id + }); + dataset.backend = this; + return dataset; + }, + sync: function(method, model, options) { + if (method === "read") { + console.log('fetching data from url', model.backend.get('url')); + var dfd = $.Deferred(); + var dataset = this; + + $.getJSON(model.backend.get('url'), function(d) { + result = model.backend.gdocsToJavascript(d); + model.set({'headers': result.header}); + model.backend.set({'data': result.data, 'headers': result.header}); + dfd.resolve(model); + }) + + return dfd.promise(); } + }, + + getDocuments: function(datasetId, start, numRows) { + var dfd = $.Deferred(); + var fields = this.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(this.get('data'), function (d) { + var obj = {}; + _.each(_.zip(fields, d), function (x) { obj[x[0]] = x[1]; }) + return obj; + }); + dfd.resolve(objs); + return dfd; + }, + gdocsToJavascript: function(gdocsSpreadsheet) { +/* + :options: (optional) optional argument dictionary: + columnsToUse: list of columns to use (specified by header names) + colTypes: dictionary (with column names as keys) specifying types (e.g. range, percent for use in conversion). + :return: tabular data object (hash with keys: header and data). + + Issues: seems google docs return columns in rows in random order and not even sure whether consistent across rows. + */ + var options = {}; + if (arguments.length > 1) { + options = arguments[1]; + } + var results = { + 'header': [], + 'data': [] + }; + // 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) { + for (var k in gdocsSpreadsheet.feed.entry[0]) { + if (k.substr(0, 3) == 'gsx') { + var col = k.substr(4) + results.header.push(col); + } + } + } + } + + // 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') { + if (rep.test(value)) { + var value2 = rep.exec(value); + var value3 = parseFloat(value2); + value = value3 / 100; + } + } + row.push(value); + } + results.data.push(row); + }); + return results; +} + +}); + return my; }(jQuery); + + + + diff --git a/test/model.test.js b/test/model.test.js index 4e3e3c2e..87f88227 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -264,4 +264,209 @@ test('DataProxy Backend', function() { $.ajax.restore(); }); + +var sample_gdocs_spreadsheet_data = { + "feed": { + "category": [ + { + "term": "http://schemas.google.com/spreadsheets/2006#list", + "scheme": "http://schemas.google.com/spreadsheets/2006" + } + ], + "updated": { + "$t": "2010-07-12T18:32:16.200Z" + }, + "xmlns": "http://www.w3.org/2005/Atom", + "xmlns$gsx": "http://schemas.google.com/spreadsheets/2006/extended", + "title": { + "$t": "Sheet1", + "type": "text" + }, + "author": [ + { + "name": { + "$t": "okfn.rufus.pollock" + }, + "email": { + "$t": "okfn.rufus.pollock@gmail.com" + } + } + ], + "openSearch$startIndex": { + "$t": "1" + }, + "link": [ + { + "href": "http://spreadsheets.google.com/pub?key=0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc", + "type": "text/html", + "rel": "alternate" + }, + { + "href": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values", + "type": "application/atom+xml", + "rel": "http://schemas.google.com/g/2005#feed" + }, + { + "href": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json-in-script", + "type": "application/atom+xml", + "rel": "self" + } + ], + "xmlns$openSearch": "http://a9.com/-/spec/opensearchrss/1.0/", + "entry": [ + { + "category": [ + { + "term": "http://schemas.google.com/spreadsheets/2006#list", + "scheme": "http://schemas.google.com/spreadsheets/2006" + } + ], + "updated": { + "$t": "2010-07-12T18:32:16.200Z" + }, + "gsx$column-2": { + "$t": "1" + }, + "gsx$column-1": { + "$t": "A" + }, + "title": { + "$t": "A", + "type": "text" + }, + "content": { + "$t": "column-2: 1", + "type": "text" + }, + "link": [ + { + "href": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cokwr", + "type": "application/atom+xml", + "rel": "self" + } + ], + "id": { + "$t": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cokwr" + } + }, + { + "category": [ + { + "term": "http://schemas.google.com/spreadsheets/2006#list", + "scheme": "http://schemas.google.com/spreadsheets/2006" + } + ], + "updated": { + "$t": "2010-07-12T18:32:16.200Z" + }, + "gsx$column-2": { + "$t": "2" + }, + "gsx$column-1": { + "$t": "b" + }, + "title": { + "$t": "b", + "type": "text" + }, + "content": { + "$t": "column-2: 2", + "type": "text" + }, + "link": [ + { + "href": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cpzh4", + "type": "application/atom+xml", + "rel": "self" + } + ], + "id": { + "$t": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cpzh4" + } + }, + { + "category": [ + { + "term": "http://schemas.google.com/spreadsheets/2006#list", + "scheme": "http://schemas.google.com/spreadsheets/2006" + } + ], + "updated": { + "$t": "2010-07-12T18:32:16.200Z" + }, + "gsx$column-2": { + "$t": "3" + }, + "gsx$column-1": { + "$t": "c" + }, + "title": { + "$t": "c", + "type": "text" + }, + "content": { + "$t": "column-2: 3", + "type": "text" + }, + "link": [ + { + "href": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cre1l", + "type": "application/atom+xml", + "rel": "self" + } + ], + "id": { + "$t": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values/cre1l" + } + } + ], + "openSearch$totalResults": { + "$t": "3" + }, + "id": { + "$t": "http://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values" + } + }, + "version": "1.0", + "encoding": "UTF-8" +} + + + + +test("GDoc Backend", function() { + var backend = new recline.Model.BackendGDoc({url: 'https://spreadsheets.google.com/feeds/list/0Aon3JiuouxLUdDQwZE1JdV94cUd6NWtuZ0IyWTBjLWc/od6/public/values?alt=json' +}); + recline.Model.setBackend(backend); + dataset = backend.getDataset(); + + console.log('got gdoc dataset', dataset); + + var stub = sinon.stub($, 'getJSON', function(options, cb) { + console.log('options are', options, cb); + var partialUrl = 'spreadsheets.google.com'; + if (options.indexOf(partialUrl) != -1) { + cb(sample_gdocs_spreadsheet_data) + + } + }); + + dataset.fetch().then(function(dataset) { + console.log('inside dataset:', dataset, dataset.get('headers'), dataset.get('data')); + deepEqual(['column-2', 'column-1'], dataset.get('headers')); + //equal(null, dataset.docCount) + dataset.getDocuments().then(function(docList) { + equal(3, docList.length); + console.log(docList.models[0]); + equal("A", docList.models[0].get('column-1')); + // needed only if not stubbing + start(); + }); + }); + $.getJSON.restore(); + + +}); + + })(this.jQuery);