Jump To …

memory.js

this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};
this.recline.Backend.Memory = this.recline.Backend.Memory || {};

(function($, my) {

createDataset

Convenience function to create a simple 'in-memory' dataset in one step.

@param data: list of hashes for each document/row in the data ({key: value, key: value}) @param fields: (optional) list of field hashes (each hash defining a hash as per recline.Model.Field). If fields not specified they will be taken from the data. @param metadata: (optional) dataset metadata - see recline.Model.Dataset. If not defined (or id not provided) id will be autogenerated.

  my.createDataset = function(data, fields, metadata) {
    var wrapper = new my.DataWrapper(data, fields);
    var backend = new my.Backbone();
    var dataset = new recline.Model.Dataset(metadata, backend);
    dataset._dataCache = wrapper;
    dataset.fetch();
    dataset.query();
    return dataset;
  };

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).

  my.DataWrapper = function(data, fields) {
    var self = this;
    this.data = data;
    if (fields) {
      this.fields = fields;
    } else {
      if (data) {
        this.fields = _.map(data[0], function(value, key) {
          return {id: key};
        });
      }
    }

    this.update = function(doc) {
      _.each(self.data, function(internalDoc, idx) {
        if(doc.id === internalDoc.id) {
          self.data[idx] = doc;
        }
      });
    };

    this.delete = function(doc) {
      var newdocs = _.reject(self.data, function(internalDoc) {
        return (doc.id === internalDoc.id);
      });
      this.data = newdocs;
    };

    this.query = function(queryObj) {
      var numRows = queryObj.size || this.data.length;
      var start = queryObj.from || 0;
      var results = this.data;
      results = this._applyFilters(results, queryObj);
      results = this._applyFreeTextQuery(results, queryObj);

not complete sorting!

      _.each(queryObj.sort, function(sortObj) {
        var fieldName = _.keys(sortObj)[0];
        results = _.sortBy(results, function(doc) {
          var _out = doc[fieldName];
          return (sortObj[fieldName].order == 'asc') ? _out : -1*_out;
        });
      });
      var total = results.length;
      var facets = this.computeFacets(results, queryObj);
      results = results.slice(start, start+numRows);
      return {
        total: total,
        documents: results,
        facets: facets
      };
    };

in place filtering

    this._applyFilters = function(results, queryObj) {
      _.each(queryObj.filters, function(filter) {
        results = _.filter(results, function(doc) {
          var fieldId = _.keys(filter.term)[0];
          return (doc[fieldId] == filter.term[fieldId]);
        });
      });
      return results;
    };

we OR across fields but AND across terms in query string

    this._applyFreeTextQuery = function(results, queryObj) {
      if (queryObj.q) {
        var terms = queryObj.q.split(' ');
        results = _.filter(results, function(rawdoc) {
          var matches = true;
          _.each(terms, function(term) {
            var foundmatch = false;
            _.each(self.fields, function(field) {
              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) 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(documents, 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(documents, 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;
    };
  };
  

Backbone

Backbone connector for memory store attached to a Dataset object

  my.Backbone = function() {
    this.__type__ = 'memory';
    this.sync = function(method, model, options) {
      var self = this;
      var dfd = $.Deferred();
      if (method === "read") {
        if (model.__type__ == 'Dataset') {
          model.fields.reset(model._dataCache.fields);
          dfd.resolve(model);
        }
        return dfd.promise();
      } else if (method === 'update') {
        if (model.__type__ == 'Document') {
          model.dataset._dataCache.update(model.toJSON());
          dfd.resolve(model);
        }
        return dfd.promise();
      } else if (method === 'delete') {
        if (model.__type__ == 'Document') {
          model.dataset._dataCache.delete(model.toJSON());
          dfd.resolve(model);
        }
        return dfd.promise();
      } else {
        alert('Not supported: sync on Memory backend with method ' + method + ' and model ' + model);
      }
    };

    this.query = function(model, queryObj) {
      var dfd = $.Deferred();
      var results = model._dataCache.query(queryObj);
      var hits = _.map(results.documents, function(row) {
        return { _source: row };
      });
      var out = {
        total: results.total,
        hits: hits,
        facets: results.facets
      };
      dfd.resolve(out);
      return dfd.promise();
    };
  };

}(jQuery, this.recline.Backend.Memory));