# backend.memory.js

- Jump To … +
  backend.dataproxy.js backend.memory.js ecma-fixes.js model.js view.flot.js view.graph.js view.grid.js view.map.js view.multiview.js view.slickgrid.js view.timeline.js widget.facetviewer.js widget.fields.js widget.filtereditor.js widget.pager.js widget.queryeditor.js widget.valuefilter.js

- # backend.memory.js

- ¶

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