model.js | |
|---|---|
Recline Backbone Models | this.recline = this.recline || {};
this.recline.Model = this.recline.Model || {};
(function($, my) { |
A Dataset modelA model has the following (non-Backbone) attributes: @property {FieldList} fields: (aka columns) is a @property {DocumentList} currentDocuments: a @property {number} docCount: total number of documents in this dataset @property {Backend} backend: the Backend (instance) for this Dataset. @property {Query} queryState: @property {FacetList} facets: FacetList object containing all current Facets. | my.Dataset = Backbone.Model.extend({
__type__: 'Dataset', |
initializeSets up instance properties (see above) @param {Object} model: standard set of model attributes passed to Backbone models @param {Object or String} backend: Backend instance (see
| initialize: function(model, backend) {
_.bindAll(this, 'query');
this.backend = backend;
if (typeof(backend) === 'string') {
this.backend = this._backendFromString(backend);
}
this.fields = new my.FieldList();
this.currentDocuments = new my.DocumentList();
this.facets = new my.FacetList();
this.docCount = null;
this.queryState = new my.Query();
this.queryState.bind('change', this.query);
this.queryState.bind('facet:add', this.query);
}, |
queryAJAX method with promise API to get documents from the backend. It will query based on current query state (given by this.queryState) updated by queryObj (if provided). Resulting DocumentList are used to reset this.currentDocuments and are also returned. | query: function(queryObj) {
var self = this;
this.trigger('query:start');
var actualQuery = self._prepareQuery(queryObj);
var dfd = $.Deferred();
this.backend.query(this, actualQuery).done(function(queryResult) {
self.docCount = queryResult.total;
var docs = _.map(queryResult.hits, function(hit) {
var _doc = new my.Document(hit._source);
_doc.backend = self.backend;
_doc.dataset = self;
return _doc;
});
self.currentDocuments.reset(docs);
if (queryResult.facets) {
var facets = _.map(queryResult.facets, function(facetResult, facetId) {
facetResult.id = facetId;
return new my.Facet(facetResult);
});
self.facets.reset(facets);
}
self.trigger('query:done');
dfd.resolve(self.currentDocuments);
})
.fail(function(arguments) {
self.trigger('query:fail', arguments);
dfd.reject(arguments);
});
return dfd.promise();
},
_prepareQuery: function(newQueryObj) {
if (newQueryObj) {
this.queryState.set(newQueryObj);
}
var out = this.queryState.toJSON();
return out;
},
toTemplateJSON: function() {
var data = this.toJSON();
data.docCount = this.docCount;
data.fields = this.fields.toJSON();
return data;
}, |
_backendFromString(backendString)See backend argument to initialize for details | _backendFromString: function(backendString) {
var parts = backendString.split('.'); |
| walk through the specified path xxx.yyy.zzz to get the final object which should be backend class | var current = window;
for(ii=0;ii<parts.length;ii++) {
if (!current) {
break;
}
current = current[parts[ii]];
}
if (current) {
return new current();
} |
| alternatively we just had a simple string | var backend = null;
if (recline && recline.Backend) {
_.each(_.keys(recline.Backend), function(name) {
if (name.toLowerCase() === backendString.toLowerCase()) {
backend = new recline.Backend[name]();
}
});
}
return backend;
}
}); |
Dataset.restoreRestore a Dataset instance from a serialized state. Serialized state for a Dataset is an Object like:
{
backend: {backend type - i.e. value of dataset.backend.type}
dataset: {dataset info needed for loading -- result of dataset.toJSON() would be sufficient but can be simpler }
// convenience - if url provided and dataste not this be used as dataset url
url: {dataset url}
...
} | my.Dataset.restore = function(state) { |
| hack-y - restoring a memory dataset does not mean much ... | var dataset = null;
if (state.url && !state.dataset) {
state.dataset = {url: state.url};
}
if (state.backend === 'memory') {
dataset = recline.Backend.createDataset(
[{stub: 'this is a stub dataset because we do not restore memory datasets'}],
[],
state.dataset // metadata
);
} else {
dataset = new recline.Model.Dataset(
state.dataset,
state.backend
);
}
return dataset;
}; |
A Document (aka Row)A single entry or row in the dataset | my.Document = Backbone.Model.extend({
__type__: 'Document',
initialize: function() {
_.bindAll(this, 'getFieldValue');
}, |
getFieldValueFor the provided Field get the corresponding rendered computed data value for this document. | getFieldValue: function(field) {
var val = this.get(field.id);
if (field.deriver) {
val = field.deriver(val, field, this);
}
if (field.renderer) {
val = field.renderer(val, field, this);
}
return val;
}
}); |
A Backbone collection of Documents | my.DocumentList = Backbone.Collection.extend({
__type__: 'DocumentList',
model: my.Document
}); |
A Field (aka Column) on a DatasetFollowing (Backbone) attributes as standard:
Following additional instance properties: @property {Function} renderer: a function to render the data for this field. Signature: function(value, field, doc) where value is the value of this cell, field is corresponding field object and document is the document object. Note that implementing functions can ignore arguments (e.g. function(value) would be a valid formatter function). @property {Function} deriver: a function to derive/compute the value of data in this field as a function of this field's value (if any) and the current document, its signature and behaviour is the same as for renderer. Use of this function allows you to define an entirely new value for data in this field. This provides support for a) 'derived/computed' fields: i.e. fields whose data are functions of the data in other fields b) transforming the value of this field prior to rendering. | my.Field = Backbone.Model.extend({ |
defaults - define default values | defaults: {
label: null,
type: 'string',
format: null,
is_derived: false
}, |
initialize@param {Object} data: standard Backbone model attributes @param {Object} options: renderer and/or deriver functions. | initialize: function(data, options) { |
| if a hash not passed in the first argument throw error | if ('0' in data) {
throw new Error('Looks like you did not pass a proper hash with id to Field constructor');
}
if (this.attributes.label === null) {
this.set({label: this.id});
}
if (options) {
this.renderer = options.renderer;
this.deriver = options.deriver;
}
if (!this.renderer) {
this.renderer = this.defaultRenderers[this.get('type')];
}
},
defaultRenderers: {
object: function(val, field, doc) {
return JSON.stringify(val);
},
'float': function(val, field, doc) {
var format = field.get('format');
if (format === 'percentage') {
return val + '%';
}
return val;
},
'string': function(val, field, doc) {
var format = field.get('format');
if (format === 'link') {
return '<a href="VAL">VAL</a>'.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;
}
}
});
my.FieldList = Backbone.Collection.extend({
model: my.Field
}); |
QueryQuery instances encapsulate a query to the backend (see query method on backend). Useful both for creating queries and for storing and manipulating query state - e.g. from a query editor). Query Structure and format Query structure should follow that of ElasticSearch query language. NB: It is up to specific backends how to implement and support this query structure. Different backends might choose to implement things differently or not support certain features. Please check your backend for details. Query object has the following key attributes:
Additions:
Examples
{
q: 'quick brown fox',
filters: [
{ term: { 'owner': 'jones' } }
]
}
| my.Query = Backbone.Model.extend({
defaults: function() {
return {
size: 100,
from: 0,
facets: {}, |
| http://www.elasticsearch.org/guide/reference/query-dsl/and-filter.html , filter: {} | filters: []
};
}, |
addTermFilterSet (update or add) a terms filter to filters See http://www.elasticsearch.org/guide/reference/query-dsl/terms-filter.html | addTermFilter: function(fieldId, value) {
var filters = this.get('filters');
var filter = { term: {} };
filter.term[fieldId] = value;
filters.push(filter);
this.set({filters: filters}); |
| change does not seem to be triggered automatically | if (value) {
this.trigger('change');
} else { |
| adding a new blank filter and do not want to trigger a new query | this.trigger('change:filters:new-blank');
}
}, |
removeFilterRemove a filter from filters at index filterIndex | removeFilter: function(filterIndex) {
var filters = this.get('filters');
filters.splice(filterIndex, 1);
this.set({filters: filters});
this.trigger('change');
}, |
addFacetAdd a Facet to this query See http://www.elasticsearch.org/guide/reference/api/search/facets/ | addFacet: function(fieldId) {
var facets = this.get('facets'); |
| Assume id and fieldId should be the same (TODO: this need not be true if we want to add two different type of facets on same field) | if (_.contains(_.keys(facets), fieldId)) {
return;
}
facets[fieldId] = {
terms: { field: fieldId }
};
this.set({facets: facets}, {silent: true});
this.trigger('facet:add', this);
},
addHistogramFacet: function(fieldId) {
var facets = this.get('facets');
facets[fieldId] = {
date_histogram: {
field: fieldId,
interval: 'day'
}
};
this.set({facets: facets}, {silent: true});
this.trigger('facet:add', this);
}
}); |
A Facet (Result)Object to store Facet information, that is summary information (e.g. values and counts) about a field obtained by some faceting method on the backend. Structure of a facet follows that of Facet results in ElasticSearch, see: http://www.elasticsearch.org/guide/reference/api/search/facets/ Specifically the object structure of a facet looks like (there is one addition compared to ElasticSearch: the "id" field which corresponds to the key used to specify this facet in the facet query):
{
"id": "id-of-facet",
// type of this facet (terms, range, histogram etc)
"_type" : "terms",
// total number of tokens in the facet
"total": 5,
// @property {number} number of documents which have no value for the field
"missing" : 0,
// number of facet values not included in the returned facets
"other": 0,
// term object ({term: , count: ...})
"terms" : [ {
"term" : "foo",
"count" : 2
}, {
"term" : "bar",
"count" : 2
}, {
"term" : "baz",
"count" : 1
}
]
}
| my.Facet = Backbone.Model.extend({
defaults: function() {
return {
_type: 'terms',
total: 0,
other: 0,
missing: 0,
terms: []
};
}
}); |
A Collection/List of Facets | my.FacetList = Backbone.Collection.extend({
model: my.Facet
}); |
Object StateConvenience Backbone model for storing (configuration) state of objects like Views. | my.ObjectState = Backbone.Model.extend({
}); |
Backend registryBackends will register themselves by id into this registry | my.backends = {};
}(jQuery, this.recline.Model));
|