PageRenderTime 24ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/profiles/dkan/libraries/recline_deeplink/lib/backend.memory.js

https://gitlab.com/begemot.lery/opendata-dkan
JavaScript | 245 lines | 189 code | 22 blank | 34 comment | 33 complexity | f06b0891bdb4f91c887e4e46a8b4b269 MD5 | raw file
  1. this.recline = this.recline || {};
  2. this.recline.Backend = this.recline.Backend || {};
  3. this.recline.Backend.Memory = this.recline.Backend.Memory || {};
  4. (function(my) {
  5. "use strict";
  6. my.__type__ = 'memory';
  7. // private data - use either jQuery or Underscore Deferred depending on what is available
  8. var Deferred = (typeof jQuery !== "undefined" && jQuery.Deferred) || _.Deferred;
  9. // ## Data Wrapper
  10. //
  11. // Turn a simple array of JS objects into a mini data-store with
  12. // functionality like querying, faceting, updating (by ID) and deleting (by
  13. // ID).
  14. //
  15. // @param records list of hashes for each record/row in the data ({key:
  16. // value, key: value})
  17. // @param fields (optional) list of field hashes (each hash defining a field
  18. // as per recline.Model.Field). If fields not specified they will be taken
  19. // from the data.
  20. my.Store = function(records, fields) {
  21. var self = this;
  22. this.records = records;
  23. // backwards compatability (in v0.5 records was named data)
  24. this.data = this.records;
  25. if (fields) {
  26. this.fields = fields;
  27. } else {
  28. if (records) {
  29. this.fields = _.map(records[0], function(value, key) {
  30. return {id: key, type: 'string'};
  31. });
  32. }
  33. }
  34. this.update = function(doc) {
  35. _.each(self.records, function(internalDoc, idx) {
  36. if(doc.id === internalDoc.id) {
  37. self.records[idx] = doc;
  38. }
  39. });
  40. };
  41. this.remove = function(doc) {
  42. var newdocs = _.reject(self.records, function(internalDoc) {
  43. return (doc.id === internalDoc.id);
  44. });
  45. this.records = newdocs;
  46. };
  47. this.save = function(changes, dataset) {
  48. var self = this;
  49. var dfd = new Deferred();
  50. // TODO _.each(changes.creates) { ... }
  51. _.each(changes.updates, function(record) {
  52. self.update(record);
  53. });
  54. _.each(changes.deletes, function(record) {
  55. self.remove(record);
  56. });
  57. dfd.resolve();
  58. return dfd.promise();
  59. },
  60. this.query = function(queryObj) {
  61. var dfd = new Deferred();
  62. var numRows = queryObj.size || this.records.length;
  63. var start = queryObj.from || 0;
  64. var results = this.records;
  65. results = this._applyFilters(results, queryObj);
  66. results = this._applyFreeTextQuery(results, queryObj);
  67. // TODO: this is not complete sorting!
  68. // What's wrong is we sort on the *last* entry in the sort list if there are multiple sort criteria
  69. _.each(queryObj.sort, function(sortObj) {
  70. var fieldName = sortObj.field;
  71. results = _.sortBy(results, function(doc) {
  72. var _out = doc[fieldName];
  73. return _out;
  74. });
  75. if (sortObj.order == 'desc') {
  76. results.reverse();
  77. }
  78. });
  79. var facets = this.computeFacets(results, queryObj);
  80. var out = {
  81. total: results.length,
  82. hits: results.slice(start, start+numRows),
  83. facets: facets
  84. };
  85. dfd.resolve(out);
  86. return dfd.promise();
  87. };
  88. // in place filtering
  89. this._applyFilters = function(results, queryObj) {
  90. var filters = queryObj.filters;
  91. // register filters
  92. var filterFunctions = {
  93. term : term,
  94. terms : terms,
  95. range : range,
  96. geo_distance : geo_distance
  97. };
  98. var dataParsers = {
  99. integer: function (e) { return parseFloat(e, 10); },
  100. 'float': function (e) { return parseFloat(e, 10); },
  101. number: function (e) { return parseFloat(e, 10); },
  102. string : function (e) { return e.toString(); },
  103. date : function (e) { return moment(e).valueOf(); },
  104. datetime : function (e) { return new Date(e).valueOf(); }
  105. };
  106. var keyedFields = {};
  107. _.each(self.fields, function(field) {
  108. keyedFields[field.id] = field;
  109. });
  110. function getDataParser(filter) {
  111. var fieldType = keyedFields[filter.field].type || 'string';
  112. return dataParsers[fieldType];
  113. }
  114. // filter records
  115. return _.filter(results, function (record) {
  116. var passes = _.map(filters, function (filter) {
  117. return filterFunctions[filter.type](record, filter);
  118. });
  119. // return only these records that pass all filters
  120. return _.all(passes, _.identity);
  121. });
  122. // filters definitions
  123. function term(record, filter) {
  124. var parse = getDataParser(filter);
  125. var value = parse(record[filter.field]);
  126. var term = parse(filter.term);
  127. return (value === term);
  128. }
  129. function terms(record, filter) {
  130. var parse = getDataParser(filter);
  131. var value = parse(record[filter.field]);
  132. var terms = parse(filter.terms).split(",");
  133. return (_.indexOf(terms, value) >= 0);
  134. }
  135. function range(record, filter) {
  136. var fromnull = (_.isUndefined(filter.from) || filter.from === null || filter.from === '');
  137. var tonull = (_.isUndefined(filter.to) || filter.to === null || filter.to === '');
  138. var parse = getDataParser(filter);
  139. var value = parse(record[filter.field]);
  140. var from = parse(fromnull ? '' : filter.from);
  141. var to = parse(tonull ? '' : filter.to);
  142. // if at least one end of range is set do not allow '' to get through
  143. // note that for strings '' <= {any-character} e.g. '' <= 'a'
  144. if ((!fromnull || !tonull) && value === '') {
  145. return false;
  146. }
  147. return ((fromnull || value >= from) && (tonull || value <= to));
  148. }
  149. function geo_distance() {
  150. // TODO code here
  151. }
  152. };
  153. // we OR across fields but AND across terms in query string
  154. this._applyFreeTextQuery = function(results, queryObj) {
  155. if (queryObj.q) {
  156. var terms = queryObj.q.split(' ');
  157. var patterns=_.map(terms, function(term) {
  158. return new RegExp(term.toLowerCase());
  159. });
  160. results = _.filter(results, function(rawdoc) {
  161. var matches = true;
  162. _.each(patterns, function(pattern) {
  163. var foundmatch = false;
  164. _.each(self.fields, function(field) {
  165. var value = rawdoc[field.id];
  166. if ((value !== null) && (value !== undefined)) {
  167. value = value.toString();
  168. } else {
  169. // value can be null (apparently in some cases)
  170. value = '';
  171. }
  172. // TODO regexes?
  173. foundmatch = foundmatch || (pattern.test(value.toLowerCase()));
  174. // TODO: early out (once we are true should break to spare unnecessary testing)
  175. // if (foundmatch) return true;
  176. });
  177. matches = matches && foundmatch;
  178. // TODO: early out (once false should break to spare unnecessary testing)
  179. // if (!matches) return false;
  180. });
  181. return matches;
  182. });
  183. }
  184. return results;
  185. };
  186. this.computeFacets = function(records, queryObj) {
  187. var facetResults = {};
  188. if (!queryObj.facets) {
  189. return facetResults;
  190. }
  191. _.each(queryObj.facets, function(query, facetId) {
  192. // TODO: remove dependency on recline.Model
  193. facetResults[facetId] = new recline.Model.Facet({id: facetId}).toJSON();
  194. facetResults[facetId].termsall = {};
  195. });
  196. // faceting
  197. _.each(records, function(doc) {
  198. _.each(queryObj.facets, function(query, facetId) {
  199. var fieldId = query.terms.field;
  200. var val = doc[fieldId];
  201. var tmp = facetResults[facetId];
  202. if (val) {
  203. tmp.termsall[val] = tmp.termsall[val] ? tmp.termsall[val] + 1 : 1;
  204. } else {
  205. tmp.missing = tmp.missing + 1;
  206. }
  207. });
  208. });
  209. _.each(queryObj.facets, function(query, facetId) {
  210. var tmp = facetResults[facetId];
  211. var terms = _.map(tmp.termsall, function(count, term) {
  212. return { term: term, count: count };
  213. });
  214. tmp.terms = _.sortBy(terms, function(item) {
  215. // want descending order
  216. return -item.count;
  217. });
  218. tmp.terms = tmp.terms.slice(0, 10);
  219. });
  220. return facetResults;
  221. };
  222. };
  223. }(this.recline.Backend.Memory));