From 60b7f19f71daddd82ecfdaee021a6130810c0517 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Tue, 17 Apr 2012 12:53:11 +0100 Subject: [PATCH 1/6] [vendor,refactor][xs]: lay vendor directory in consistent way ({lib}/{version}/{js-file}). --- app/index.html | 8 ++++---- test/index.html | 11 ++++++----- .../{backbone-0.5.1.js => backbone/0.5.1/backbone.js} | 0 .../0.7/jquery.flot.js} | 0 vendor/{jquery-1.7.1.js => jquery/1.7.1/jquery.js} | 0 .../1.1.6/underscore.js} | 0 6 files changed, 10 insertions(+), 9 deletions(-) rename vendor/{backbone-0.5.1.js => backbone/0.5.1/backbone.js} (100%) rename vendor/{jquery.flot-0.7.js => jquery.flot/0.7/jquery.flot.js} (100%) rename vendor/{jquery-1.7.1.js => jquery/1.7.1/jquery.js} (100%) rename vendor/{underscore-1.1.6.js => underscore/1.1.6/underscore.js} (100%) diff --git a/app/index.html b/app/index.html index e6367736..63e1d9ef 100644 --- a/app/index.html +++ b/app/index.html @@ -29,11 +29,11 @@ - - - + + + - + diff --git a/test/index.html b/test/index.html index da0e90b8..8bc78f41 100644 --- a/test/index.html +++ b/test/index.html @@ -7,13 +7,14 @@ - - - + + + - - + + + diff --git a/vendor/backbone-0.5.1.js b/vendor/backbone/0.5.1/backbone.js similarity index 100% rename from vendor/backbone-0.5.1.js rename to vendor/backbone/0.5.1/backbone.js diff --git a/vendor/jquery.flot-0.7.js b/vendor/jquery.flot/0.7/jquery.flot.js similarity index 100% rename from vendor/jquery.flot-0.7.js rename to vendor/jquery.flot/0.7/jquery.flot.js diff --git a/vendor/jquery-1.7.1.js b/vendor/jquery/1.7.1/jquery.js similarity index 100% rename from vendor/jquery-1.7.1.js rename to vendor/jquery/1.7.1/jquery.js diff --git a/vendor/underscore-1.1.6.js b/vendor/underscore/1.1.6/underscore.js similarity index 100% rename from vendor/underscore-1.1.6.js rename to vendor/underscore/1.1.6/underscore.js From 92ec8d5b3ed04a6ba8c3c5a7ede268769a442806 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 21 Apr 2012 22:48:33 +0100 Subject: [PATCH 2/6] [backend/memory,bugfix][xs]: fix error on memory filtering when field value is null (cannot call toString). --- src/backend/memory.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/memory.js b/src/backend/memory.js index f79991e8..adf06bab 100644 --- a/src/backend/memory.js +++ b/src/backend/memory.js @@ -158,7 +158,8 @@ this.recline.Backend = this.recline.Backend || {}; _.each(terms, function(term) { var foundmatch = false; dataset.fields.each(function(field) { - var value = rawdoc[field.id].toString(); + var value = rawdoc[field.id]; + if (value !== null) { value = value.toString(); } // TODO regexes? foundmatch = foundmatch || (value === term); // TODO: early out (once we are true should break to spare unnecessary testing) From 793fde461753bdaf5af660a46be1976b6662a7a3 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 21 Apr 2012 23:47:52 +0100 Subject: [PATCH 3/6] [bugfix,view-map][s]: 30m to track down and fix a bug whereby map view was ignoring config passed to it. * setupGeometryFields was running (and overwriting) even if fields set in state passed in (which I don't think we want). --- src/view-map.js | 14 +++++++++----- src/view.js | 2 +- test/view.test.js | 11 ++++++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/view-map.js b/src/view-map.js index 9ceb07b1..a6361d75 100644 --- a/src/view-map.js +++ b/src/view-map.js @@ -335,12 +335,16 @@ my.Map = Backbone.View.extend({ // If not found, the user can define them via the UI form. _setupGeometryField: function(){ var geomField, latField, lonField; - this.state.set({ - geomField: this._checkField(this.geometryFieldNames), - latField: this._checkField(this.latitudeFieldNames), - lonField: this._checkField(this.longitudeFieldNames) - }); this.geomReady = (this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); + // should not overwrite if we have already set this (e.g. explicitly via state) + if (!this.geomReady) { + this.state.set({ + geomField: this._checkField(this.geometryFieldNames), + latField: this._checkField(this.latitudeFieldNames), + lonField: this._checkField(this.longitudeFieldNames) + }); + this.geomReady = (this.state.get('geomField') || (this.state.get('latField') && this.state.get('lonField'))); + } }, // Private: Check if a field in the current model exists in the provided diff --git a/src/view.js b/src/view.js index 358b2aa3..44881967 100644 --- a/src/view.js +++ b/src/view.js @@ -184,8 +184,8 @@ my.DataExplorer = Backbone.View.extend({ initialize: function(options) { var self = this; this.el = $(this.el); - // Hash of 'page' views (i.e. those for whole page) keyed by page name this._setupState(options.state); + // Hash of 'page' views (i.e. those for whole page) keyed by page name if (options.views) { this.pageViews = options.views; } else { diff --git a/test/view.test.js b/test/view.test.js index 19fcc177..4dec5d89 100644 --- a/test/view.test.js +++ b/test/view.test.js @@ -47,17 +47,26 @@ test('initialize state', function () { currentView: 'graph', 'view-grid': { hiddenFields: ['x'] + }, + 'view-map': { + latField: 'lat1', + lonField: 'lon1' } } }); ok(explorer.state.get('readOnly')); ok(explorer.state.get('currentView'), 'graph'); + // check the correct view is visible var css = explorer.el.find('.navigation a[data-view="graph"]').attr('class').split(' '); ok(_.contains(css, 'disabled'), css); - var css = explorer.el.find('.navigation a[data-view="grid"]').attr('class').split(' '); ok(!(_.contains(css, 'disabled')), css); + + // check pass through of view config + deepEqual(explorer.state.get('view-grid')['hiddenFields'], ['x']); + equal(explorer.state.get('view-map')['lonField'], 'lon1'); + $el.remove(); }); From 24295e78a2800e1c910c3c85a03a10bcea3eda87 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 21 Apr 2012 23:52:22 +0100 Subject: [PATCH 4/6] [view/map][s]: make map view much more robust regarding bad or missing data in location fields. --- src/view-map.js | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/view-map.js b/src/view-map.js index a6361d75..9553b9e3 100644 --- a/src/view-map.js +++ b/src/view-map.js @@ -256,9 +256,12 @@ my.Map = Backbone.View.extend({ if (!(docs instanceof Array)) docs = [docs]; + var count = 0; + var wrongSoFar = 0; _.every(docs,function(doc){ + count += 1; var feature = self._getGeometryFromDocument(doc); - if (typeof feature === 'undefined'){ + if (typeof feature === 'undefined' || feature === null){ // Empty field return true; } else if (feature instanceof Object){ @@ -275,16 +278,20 @@ my.Map = Backbone.View.extend({ feature.properties.cid = doc.cid; try { - self.features.addGeoJSON(feature); + self.features.addGeoJSON(feature); } catch (except) { - var msg = 'Wrong geometry value'; - if (except.message) msg += ' (' + except.message + ')'; + wrongSoFar += 1; + var msg = 'Wrong geometry value'; + if (except.message) msg += ' (' + except.message + ')'; + if (wrongSoFar <= 10) { my.notify(msg,{category:'error'}); - return false; + } } } else { - my.notify('Wrong geometry value',{category:'error'}); - return false; + wrongSoFar += 1 + if (wrongSoFar <= 10) { + my.notify('Wrong geometry value',{category:'error'}); + } } return true; }); @@ -317,13 +324,17 @@ my.Map = Backbone.View.extend({ return doc.attributes[this.state.get('geomField')]; } else if (this.state.get('lonField') && this.state.get('latField')){ // We'll create a GeoJSON like point object from the two lat/lon fields - return { - type: 'Point', - coordinates: [ - doc.attributes[this.state.get('lonField')], - doc.attributes[this.state.get('latField')] - ] - }; + var lon = doc.get(this.state.get('lonField')); + var lat = doc.get(this.state.get('latField')); + if (lon && lat) { + return { + type: 'Point', + coordinates: [ + doc.attributes[this.state.get('lonField')], + doc.attributes[this.state.get('latField')] + ] + }; + } } return null; } From 67ff75772282f761f4b82587f2a045f44a4119b1 Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sat, 21 Apr 2012 23:54:49 +0100 Subject: [PATCH 5/6] [#68,field][s]: link and markdown format for strings with default formatter. --- src/model.js | 19 ++++++++++++++++++- test/model.test.js | 18 +++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/model.js b/src/model.js index 4145507b..88c724a7 100644 --- a/src/model.js +++ b/src/model.js @@ -217,7 +217,8 @@ my.DocumentList = Backbone.Collection.extend({ // * format: (optional) used to indicate how the data should be formatted. For example: // * type=date, format=yyyy-mm-dd // * type=float, format=percentage -// * type=float, format='###,###.##' +// * type=string, format=link (render as hyperlink) +// * type=string, format=markdown (render as markdown if Showdown available) // * is_derived: (default: false) attribute indicating this field has no backend data but is just derived from other fields (see below). // // Following additional instance properties: @@ -273,6 +274,22 @@ my.Field = Backbone.Model.extend({ if (format === 'percentage') { return val + '%'; } + return val; + }, + 'string': function(val, field, doc) { + var format = field.get('format'); + if (format === 'link') { + return 'VAL'.replace(/VAL/g, val); + } else if (format === 'markdown') { + if (typeof Showdown !== 'undefined') { + var showdown = new Showdown.converter(); + out = showdown.makeHtml(val); + return out; + } else { + return val; + } + } + return val; } } }); diff --git a/test/model.test.js b/test/model.test.js index 2f0c4e6a..f4a4c623 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -39,7 +39,12 @@ test('Field: basics', function () { }); test('Field: default renderers', function () { - var doc = new recline.Model.Document({x: 12.3, myobject: {a: 1, b: 2}}); + var doc = new recline.Model.Document({ + x: 12.3, + myobject: {a: 1, b: 2}, + link: 'http://abc.com/', + markdown: '### ABC' + }); var field = new recline.Model.Field({id: 'myobject', type: 'object'}); var out = doc.getFieldValue(field); var exp = '{"a":1,"b":2}'; @@ -49,6 +54,17 @@ test('Field: default renderers', function () { var out = doc.getFieldValue(field); var exp = '12.3%'; equal(out, exp); + + var field = new recline.Model.Field({id: 'link', type: 'string', format: 'link'}); + var out = doc.getFieldValue(field); + var exp = 'http://abc.com/'; + equal(out, exp); + + var field = new recline.Model.Field({id: 'markdown', type: 'string', format: 'markdown'}); + var out = doc.getFieldValue(field); + // Showdown is not installed so nothing should happen + var exp = doc.get('markdown'); + equal(out, exp); }); test('Field: custom deriver and renderer', function () { From 31d829f53f0a84f2462cad27cf36e58e9466c3ae Mon Sep 17 00:00:00 2001 From: Rufus Pollock Date: Sun, 22 Apr 2012 00:03:59 +0100 Subject: [PATCH 6/6] [#17,backend][s]: add readonly attribute on backends indicating whether they are 'read-only'. --- src/backend/base.js | 7 +++++++ src/backend/dataproxy.js | 1 + src/backend/elasticsearch.js | 1 + src/backend/gdocs.js | 1 + src/backend/memory.js | 1 + test/backend.test.js | 7 +++++++ 6 files changed, 18 insertions(+) diff --git a/src/backend/base.js b/src/backend/base.js index e9c4eea7..1b06dc01 100644 --- a/src/backend/base.js +++ b/src/backend/base.js @@ -32,6 +32,13 @@ this.recline.Backend = this.recline.Backend || {}; // backends (see recline.Model.Dataset.initialize). __type__: 'base', + + // ### readonly + // + // Class level attribute indicating that this backend is read-only (that + // is, cannot be written to). + readonly: true, + // ### sync // // An implementation of Backbone.sync that will be used to override diff --git a/src/backend/dataproxy.js b/src/backend/dataproxy.js index 8f2b7496..16db3db6 100644 --- a/src/backend/dataproxy.js +++ b/src/backend/dataproxy.js @@ -18,6 +18,7 @@ this.recline.Backend = this.recline.Backend || {}; // Note that this is a **read-only** backend. my.DataProxy = my.Base.extend({ __type__: 'dataproxy', + readonly: true, defaults: { dataproxy_url: 'http://jsonpdataproxy.appspot.com' }, diff --git a/src/backend/elasticsearch.js b/src/backend/elasticsearch.js index 164393a8..0ccb5295 100644 --- a/src/backend/elasticsearch.js +++ b/src/backend/elasticsearch.js @@ -21,6 +21,7 @@ this.recline.Backend = this.recline.Backend || {}; //
http://localhost:9200/twitter/tweet
my.ElasticSearch = my.Base.extend({ __type__: 'elasticsearch', + readonly: true, _getESUrl: function(dataset) { var out = dataset.get('elasticsearch_url'); if (out) return out; diff --git a/src/backend/gdocs.js b/src/backend/gdocs.js index e6f29b55..c9b5b551 100644 --- a/src/backend/gdocs.js +++ b/src/backend/gdocs.js @@ -18,6 +18,7 @@ this.recline.Backend = this.recline.Backend || {}; // my.GDoc = my.Base.extend({ __type__: 'gdoc', + readonly: true, getUrl: function(dataset) { var url = dataset.get('url'); if (url.indexOf('feeds/list') != -1) { diff --git a/src/backend/memory.js b/src/backend/memory.js index adf06bab..e013aa19 100644 --- a/src/backend/memory.js +++ b/src/backend/memory.js @@ -71,6 +71,7 @@ this.recline.Backend = this.recline.Backend || {}; // my.Memory = my.Base.extend({ __type__: 'memory', + readonly: false, initialize: function() { this.datasets = {}; }, diff --git a/test/backend.test.js b/test/backend.test.js index f7d96f60..69686b8a 100644 --- a/test/backend.test.js +++ b/test/backend.test.js @@ -25,6 +25,11 @@ function makeBackendDataset() { return dataset; } +test('Memory Backend: readonly', function () { + var backend = new recline.Backend.Memory(); + equal(backend.readonly, false); +}); + test('Memory Backend: createDataset', function () { var dataset = recline.Backend.createDataset(memoryData.documents, memoryData.fields, memoryData.metadata); equal(memoryData.metadata.id, dataset.id); @@ -217,6 +222,8 @@ test('DataProxy Backend', function() { // needed only if not stubbing // stop(); var backend = new recline.Backend.DataProxy(); + ok(backend.readonly, false); + var dataset = new recline.Model.Dataset({ url: 'http://webstore.thedatahub.org/rufuspollock/gold_prices/data.csv' },