diff --git a/src/backend.ckan.js b/src/backend.ckan.js
new file mode 100644
index 00000000..f700af75
--- /dev/null
+++ b/src/backend.ckan.js
@@ -0,0 +1,88 @@
+this.recline = this.recline || {};
+this.recline.Backend = this.recline.Backend || {};
+this.recline.Backend.Ckan = this.recline.Backend.Ckan || {};
+
+(function($, my) {
+ // ## CKAN Backend
+ //
+ // This provides connection to the CKAN DataStore (v2)
+ //
+ // General notes
+ //
+ // * Every dataset must have an id equal to its resource id on the CKAN instance
+ // * You should set the CKAN API endpoint for requests by setting API_ENDPOINT value on this module (recline.Backend.Ckan.API_ENDPOINT)
+
+ my.__type__ = 'ckan';
+
+ // Default CKAN API endpoint used for requests (you can change this but it will affect every request!)
+ my.API_ENDPOINT = 'http://datahub.io/api';
+
+ // ### fetch
+ my.fetch = function(dataset) {
+ var wrapper = my.DataStore();
+ var dfd = $.Deferred();
+ var jqxhr = wrapper.search({resource_id: dataset.id, limit: 0});
+ jqxhr.done(function(results) {
+ // map ckan types to our usual types ...
+ var fields = _.map(results.result.fields, function(field) {
+ field.type = field.type in CKAN_TYPES_MAP ? CKAN_TYPES_MAP[field.type] : field.type;
+ return field;
+ });
+ var out = {
+ fields: fields,
+ useMemoryStore: false
+ };
+ dfd.resolve(out);
+ });
+ return dfd.promise();
+ };
+
+ my.query = function(dataset, queryObj) {
+ var wrapper = my.DataStore(dataset.url);
+ var actualQuery = {
+ resource_id: dataset.id,
+ q: queryObj.q,
+ limit: queryObj.size,
+ offset: queryObj.from
+ };
+ var dfd = $.Deferred();
+ var jqxhr = wrapper.search(actualQuery);
+ jqxhr.done(function(results) {
+ var out = {
+ total: results.result.total,
+ hits: results.result.records,
+ };
+ dfd.resolve(out);
+ });
+ return dfd.promise();
+ };
+
+ // ### DataStore
+ //
+ // Simple wrapper around the CKAN DataStore API
+ //
+ // @param endpoint: CKAN api endpoint (e.g. http://datahub.io/api)
+ my.DataStore = function(endpoint) {
+ var that = {
+ endpoint: endpoint || my.API_ENDPOINT
+ };
+ that.search = function(data) {
+ var searchUrl = my.endpoint + '/3/action/datastore_search';
+ var jqxhr = $.ajax({
+ url: searchUrl,
+ data: data,
+ dataType: 'json'
+ });
+ return jqxhr;
+ }
+
+ return that;
+ }
+
+ var CKAN_TYPES_MAP = {
+ 'int4': 'integer',
+ 'float8': 'float',
+ 'text': 'string'
+ };
+
+}(jQuery, this.recline.Backend.Ckan));
diff --git a/test/backend.ckan.test.js b/test/backend.ckan.test.js
new file mode 100644
index 00000000..ccdfd668
--- /dev/null
+++ b/test/backend.ckan.test.js
@@ -0,0 +1,164 @@
+module("Backend CKAN");
+
+test("fetch", function() {
+ var dataset = new recline.Model.Dataset({
+ url: 'http://localhost:5000/dataset/test-data-viewer/resource/4f1299ab-a100-4e5f-ba81-e6d234a2f3bd',
+ backend: 'ckan'
+ });
+
+ var stub = sinon.stub($, 'ajax', function(options) {
+ if (options.url.indexOf('datastore_search') != -1) {
+ return {
+ done: function(callback) {
+ callback(sample_data);
+ return this;
+ },
+ fail: function() {
+ }
+ };
+ }
+ });
+
+ dataset.fetch().done(function(dataset) {
+ deepEqual(
+ _.pluck(dataset.fields.toJSON(), 'id'),
+ _.pluck(sample_data.result.fields, 'id')
+ );
+ // check we've mapped types correctly
+ equal(dataset.fields.get('x').get('type'), 'integer');
+ equal(dataset.fields.get('country').get('type'), 'string');
+
+ // fetch does a query so we can check for records
+ equal(dataset.recordCount, 6);
+ equal(dataset.records.length, 6);
+ equal(dataset.records.at(0).get('id'), 0);
+ equal(dataset.records.at(0).get('country'), 'DE');
+ });
+ $.ajax.restore();
+});
+
+var sample_data = {
+ "help": "",
+ "result": {
+ "fields": [
+ {
+ "id": "_id",
+ "type": "int4"
+ },
+ {
+ "id": "id",
+ "type": "int4"
+ },
+ {
+ "id": "date",
+ "type": "date"
+ },
+ {
+ "id": "x",
+ "type": "int4"
+ },
+ {
+ "id": "y",
+ "type": "int4"
+ },
+ {
+ "id": "z",
+ "type": "int4"
+ },
+ {
+ "id": "country",
+ "type": "text"
+ },
+ {
+ "id": "title",
+ "type": "text"
+ },
+ {
+ "id": "lat",
+ "type": "float8"
+ },
+ {
+ "id": "lon",
+ "type": "float8"
+ }
+ ],
+ "records": [
+ {
+ "_id": 1,
+ "country": "DE",
+ "date": "2011-01-01",
+ "id": 0,
+ "lat": 52.56,
+ "lon": 13.4,
+ "title": "first",
+ "x": 1,
+ "y": 2,
+ "z": 3
+ },
+ {
+ "_id": 2,
+ "country": "UK",
+ "date": "2011-02-02",
+ "id": 1,
+ "lat": 54.97,
+ "lon": -1.6,
+ "title": "second",
+ "x": 2,
+ "y": 4,
+ "z": 24
+ },
+ {
+ "_id": 3,
+ "country": "US",
+ "date": "2011-03-03",
+ "id": 2,
+ "lat": 40.0,
+ "lon": -75.5,
+ "title": "third",
+ "x": 3,
+ "y": 6,
+ "z": 9
+ },
+ {
+ "_id": 4,
+ "country": "UK",
+ "date": "2011-04-04",
+ "id": 3,
+ "lat": 57.27,
+ "lon": -6.2,
+ "title": "fourth",
+ "x": 4,
+ "y": 8,
+ "z": 6
+ },
+ {
+ "_id": 5,
+ "country": "UK",
+ "date": "2011-05-04",
+ "id": 4,
+ "lat": 51.58,
+ "lon": 0.0,
+ "title": "fifth",
+ "x": 5,
+ "y": 10,
+ "z": 15
+ },
+ {
+ "_id": 6,
+ "country": "DE",
+ "date": "2011-06-02",
+ "id": 5,
+ "lat": 51.04,
+ "lon": 7.9,
+ "title": "sixth",
+ "x": 6,
+ "y": 12,
+ "z": 18
+ }
+ ],
+ "resource_id": "4f1299ab-a100-4e5f-ba81-e6d234a2f3bd",
+ "total": 6
+ },
+ "success": true
+};
+
diff --git a/test/index.html b/test/index.html
index 83eeacb9..8830b603 100644
--- a/test/index.html
+++ b/test/index.html
@@ -36,6 +36,7 @@
+
@@ -43,6 +44,7 @@
+