datahub/src/model.js

234 lines
7.1 KiB
JavaScript

this.recline = this.recline || {};
// Models module following classic module pattern
recline.Model = function($) {
var my = {};
// A Dataset model.
//
// Other than standard list of Backbone attributes it has two important attributes:
//
// * currentDocuments: a DocumentList containing the Documents we have currently loaded for viewing (you update currentDocuments by calling getRows)
// * docCount: total number of documents in this dataset (obtained on a fetch for this Dataset)
my.Dataset = Backbone.Model.extend({
__type__: 'Dataset',
initialize: function() {
this.currentDocuments = new my.DocumentList();
this.docCount = null;
},
// AJAX method with promise API to get rows (documents) from the backend.
//
// Resulting DocumentList are used to reset this.currentDocuments and are
// also returned.
//
// :param numRows: passed onto backend getDocuments.
// :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) {
var self = this;
var dfd = $.Deferred();
this.backend.getDocuments(this.id, numRows, start).then(function(rows) {
var docs = _.map(rows, function(row) {
return new my.Document(row);
});
self.currentDocuments.reset(docs);
dfd.resolve(self.currentDocuments);
});
return dfd.promise();
},
toTemplateJSON: function() {
var data = this.toJSON();
data.docCount = this.docCount;
return data;
}
});
my.Document = Backbone.Model.extend({
__type__: 'Document'
});
my.DocumentList = Backbone.Collection.extend({
__type__: 'DocumentList',
// webStore: new WebStore(this.url),
model: my.Document
});
// Backends section
// ================
my.setBackend = function(backend) {
Backbone.sync = backend.sync;
};
// Backend which just caches in memory
//
// Does not need to be a backbone model but provides some conveniences
my.BackendMemory = Backbone.Model.extend({
// Initialize a Backend with a local in-memory dataset.
//
// NB: We can handle one and only one dataset at a time.
//
// :param dataset: the data for a dataset on which operations will be
// performed. Its form should be a hash with metadata and data
// attributes.
//
// - metadata: hash of key/value attributes of any kind (but usually with title attribute)
// - data: hash with 2 keys:
// - headers: list of header names/labels
// - rows: list of hashes, each hash being one row. A row *must* have an id attribute which is unique.
//
// Example of data:
//
// {
// headers: ['x', 'y', 'z']
// , rows: [
// {id: 0, x: 1, y: 2, z: 3}
// , {id: 1, x: 2, y: 4, z: 6}
// ]
// };
initialize: function(dataset) {
// deep copy
this._datasetAsData = $.extend(true, {}, dataset);
_.bindAll(this, 'sync');
},
getDataset: function() {
var dataset = new my.Dataset({
id: this._datasetAsData.metadata.id
});
// this is a bit weird but problem is in sync this is set to parent model object so need to give dataset a reference to backend explicitly
dataset.backend = this;
return dataset;
},
sync: function(method, model, options) {
var self = this;
if (method === "read") {
var dfd = $.Deferred();
// this switching on object type is rather horrible
// think may make more sense to do work in individual objects rather than in central Backbone.sync
if (model.__type__ == 'Dataset') {
var dataset = model;
var rawDataset = this._datasetAsData;
dataset.set(rawDataset.metadata);
dataset.set({
headers: rawDataset.data.headers
});
dataset.docCount = rawDataset.data.rows.length;
dfd.resolve(dataset);
}
return dfd.promise();
} else if (method === 'update') {
var dfd = $.Deferred();
if (model.__type__ == 'Document') {
_.each(this._datasetAsData.data.rows, function(row, idx) {
if(row.id === model.id) {
self._datasetAsData.data.rows[idx] = model.toJSON();
}
});
dfd.resolve(model);
}
return dfd.promise();
} else if (method === 'delete') {
var dfd = $.Deferred();
if (model.__type__ == 'Document') {
this._datasetAsData.data.rows = _.reject(this._datasetAsData.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(datasetId, numRows, start) {
if (start === undefined) {
start = 0;
}
if (numRows === undefined) {
numRows = 10;
}
var dfd = $.Deferred();
rows = this._datasetAsData.data.rows;
var results = rows.slice(start, start+numRows);
dfd.resolve(results);
return dfd.promise();
}
});
// Webstore Backend for connecting to the Webstore
//
// Designed to only attach to one dataset and one dataset only ...
// Could generalize to support attaching to different datasets
my.BackendWebstore = Backbone.Model.extend({
// require url attribute in initialization data
initialize: function() {
this.webstoreTableUrl = this.get('url');
},
getDataset: function(id) {
var dataset = new my.Dataset({
id: id
});
dataset.backend = this;
return dataset;
},
sync: function(method, model, options) {
if (method === "read") {
// this switching on object type is rather horrible
// think may make more sense to do work in individual objects rather than in central Backbone.sync
if (this.__type__ == 'Dataset') {
var dataset = this;
// get the schema and return
var base = this.backend.get('url');
var schemaUrl = base + '/schema.json';
var jqxhr = $.ajax({
url: schemaUrl,
dataType: 'jsonp',
jsonp: '_callback'
});
var dfd = $.Deferred();
jqxhr.then(function(schema) {
headers = _.map(schema.data, function(item) {
return item.name;
});
dataset.set({
headers: headers
});
dataset.docCount = schema.count;
dfd.resolve(dataset, jqxhr);
});
return dfd.promise();
}
}
},
getDocuments: function(datasetId, numRows, start) {
if (start === undefined) {
start = 0;
}
if (numRows === undefined) {
numRows = 10;
}
var base = this.get('url');
var jqxhr = $.ajax({
url: base + '.json',
dataType: 'jsonp',
jsonp: '_callback',
cache: true
});
var dfd = $.Deferred();
jqxhr.then(function(results) {
dfd.resolve(results.data);
});
return dfd.promise();
}
});
return my;
}(jQuery);