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