/profiles/dkan/libraries/recline_deeplink/lib/backend.memory.js
JavaScript | 245 lines | 189 code | 22 blank | 34 comment | 33 complexity | f06b0891bdb4f91c887e4e46a8b4b269 MD5 | raw file
- this.recline = this.recline || {};
- this.recline.Backend = this.recline.Backend || {};
- this.recline.Backend.Memory = this.recline.Backend.Memory || {};
- (function(my) {
- "use strict";
- my.__type__ = 'memory';
- // private data - use either jQuery or Underscore Deferred depending on what is available
- var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred;
- // ## Data Wrapper
- //
- // Turn a simple array of JS objects into a mini data-store with
- // functionality like querying, faceting, updating (by ID) and deleting (by
- // ID).
- //
- // @param records list of hashes for each record/row in the data ({key:
- // value, key: value})
- // @param fields (optional) list of field hashes (each hash defining a field
- // as per recline.Model.Field). If fields not specified they will be taken
- // from the data.
- my.Store = function(records, fields) {
- var self = this;
- this.records = records;
- // backwards compatability (in v0.5 records was named data)
- this.data = this.records;
- if (fields) {
- this.fields = fields;
- } else {
- if (records) {
- this.fields = _.map(records[0], function(value, key) {
- return {id: key, type: 'string'};
- });
- }
- }
- this.update = function(doc) {
- _.each(self.records, function(internalDoc, idx) {
- if(doc.id === internalDoc.id) {
- self.records[idx] = doc;
- }
- });
- };
- this.remove = function(doc) {
- var newdocs = _.reject(self.records, function(internalDoc) {
- return (doc.id === internalDoc.id);
- });
- this.records = newdocs;
- };
- this.save = function(changes, dataset) {
- var self = this;
- var dfd = new Deferred();
- // TODO _.each(changes.creates) { ... }
- _.each(changes.updates, function(record) {
- self.update(record);
- });
- _.each(changes.deletes, function(record) {
- self.remove(record);
- });
- dfd.resolve();
- return dfd.promise();
- },
- this.query = function(queryObj) {
- var dfd = new Deferred();
- var numRows = queryObj.size || this.records.length;
- var start = queryObj.from || 0;
- var results = this.records;
-
- results = this._applyFilters(results, queryObj);
- results = this._applyFreeTextQuery(results, queryObj);
- // TODO: this is not complete sorting!
- // What's wrong is we sort on the *last* entry in the sort list if there are multiple sort criteria
- _.each(queryObj.sort, function(sortObj) {
- var fieldName = sortObj.field;
- results = _.sortBy(results, function(doc) {
- var _out = doc[fieldName];
- return _out;
- });
- if (sortObj.order == 'desc') {
- results.reverse();
- }
- });
- var facets = this.computeFacets(results, queryObj);
- var out = {
- total: results.length,
- hits: results.slice(start, start+numRows),
- facets: facets
- };
- dfd.resolve(out);
- return dfd.promise();
- };
- // in place filtering
- this._applyFilters = function(results, queryObj) {
- var filters = queryObj.filters;
- // register filters
- var filterFunctions = {
- term : term,
- terms : terms,
- range : range,
- geo_distance : geo_distance
- };
- var dataParsers = {
- integer: function (e) { return parseFloat(e, 10); },
- 'float': function (e) { return parseFloat(e, 10); },
- number: function (e) { return parseFloat(e, 10); },
- string : function (e) { return e.toString(); },
- date : function (e) { return moment(e).valueOf(); },
- datetime : function (e) { return new Date(e).valueOf(); }
- };
- var keyedFields = {};
- _.each(self.fields, function(field) {
- keyedFields[field.id] = field;
- });
- function getDataParser(filter) {
- var fieldType = keyedFields[filter.field].type || 'string';
- return dataParsers[fieldType];
- }
- // filter records
- return _.filter(results, function (record) {
- var passes = _.map(filters, function (filter) {
- return filterFunctions[filter.type](record, filter);
- });
- // return only these records that pass all filters
- return _.all(passes, _.identity);
- });
- // filters definitions
- function term(record, filter) {
- var parse = getDataParser(filter);
- var value = parse(record[filter.field]);
- var term = parse(filter.term);
- return (value === term);
- }
- function terms(record, filter) {
- var parse = getDataParser(filter);
- var value = parse(record[filter.field]);
- var terms = parse(filter.terms).split(",");
- return (_.indexOf(terms, value) >= 0);
- }
- function range(record, filter) {
- var fromnull = (_.isUndefined(filter.from) || filter.from === null || filter.from === '');
- var tonull = (_.isUndefined(filter.to) || filter.to === null || filter.to === '');
- var parse = getDataParser(filter);
- var value = parse(record[filter.field]);
- var from = parse(fromnull ? '' : filter.from);
- var to = parse(tonull ? '' : filter.to);
- // if at least one end of range is set do not allow '' to get through
- // note that for strings '' <= {any-character} e.g. '' <= 'a'
- if ((!fromnull || !tonull) && value === '') {
- return false;
- }
- return ((fromnull || value >= from) && (tonull || value <= to));
- }
- function geo_distance() {
- // TODO code here
- }
- };
- // we OR across fields but AND across terms in query string
- this._applyFreeTextQuery = function(results, queryObj) {
- if (queryObj.q) {
- var terms = queryObj.q.split(' ');
- var patterns=_.map(terms, function(term) {
- return new RegExp(term.toLowerCase());
- });
- results = _.filter(results, function(rawdoc) {
- var matches = true;
- _.each(patterns, function(pattern) {
- var foundmatch = false;
- _.each(self.fields, function(field) {
- var value = rawdoc[field.id];
- if ((value !== null) && (value !== undefined)) {
- value = value.toString();
- } else {
- // value can be null (apparently in some cases)
- value = '';
- }
- // TODO regexes?
- foundmatch = foundmatch || (pattern.test(value.toLowerCase()));
- // TODO: early out (once we are true should break to spare unnecessary testing)
- // if (foundmatch) return true;
- });
- matches = matches && foundmatch;
- // TODO: early out (once false should break to spare unnecessary testing)
- // if (!matches) return false;
- });
- return matches;
- });
- }
- return results;
- };
- this.computeFacets = function(records, queryObj) {
- var facetResults = {};
- if (!queryObj.facets) {
- return facetResults;
- }
- _.each(queryObj.facets, function(query, facetId) {
- // TODO: remove dependency on recline.Model
- facetResults[facetId] = new recline.Model.Facet({id: facetId}).toJSON();
- facetResults[facetId].termsall = {};
- });
- // faceting
- _.each(records, function(doc) {
- _.each(queryObj.facets, function(query, facetId) {
- var fieldId = query.terms.field;
- var val = doc[fieldId];
- var tmp = facetResults[facetId];
- if (val) {
- tmp.termsall[val] = tmp.termsall[val] ? tmp.termsall[val] + 1 : 1;
- } else {
- tmp.missing = tmp.missing + 1;
- }
- });
- });
- _.each(queryObj.facets, function(query, facetId) {
- var tmp = facetResults[facetId];
- var terms = _.map(tmp.termsall, function(count, term) {
- return { term: term, count: count };
- });
- tmp.terms = _.sortBy(terms, function(item) {
- // want descending order
- return -item.count;
- });
- tmp.terms = tmp.terms.slice(0, 10);
- });
- return facetResults;
- };
- };
- }(this.recline.Backend.Memory));