PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/packages/core/src/js/aui/progressive-data-set.js

https://bitbucket.org/atlassian/aui
JavaScript | 235 lines | 104 code | 21 blank | 110 comment | 14 complexity | 97b37396760324afb374c777728a422e MD5 | raw file
Possible License(s): Apache-2.0, JSON, LGPL-2.0
  1. import { bindAll, extend, first, isFunction, pluck } from 'underscore';
  2. import Backbone from 'backbone';
  3. import globalize from './internal/globalize';
  4. /**
  5. * @fileOverview describes a ProgressiveDataSet object.
  6. *
  7. * This object serves as part of a series of components to handle the various aspects of autocomplete controls.
  8. */
  9. var ProgressiveDataSet = Backbone.Collection.extend({
  10. /**
  11. * A queryable set of data that optimises the speed at which responses can be provided.
  12. *
  13. * ProgressiveDataSet should be given a matcher function so that it may filter results for queries locally.
  14. *
  15. * ProgressiveDataSet can be given a remote query endpoint to fetch data from. Should a remote endpoint
  16. * be provided, ProgressiveDataSet will leverage both client-side matching and query caching to reduce
  17. * the number of times the remote source need be queried.
  18. *
  19. * @example
  20. * var source = new ProgressiveDataSet([], {
  21. * model: Backbone.Model.extend({ idAttribute: "username" }),
  22. * queryEndpoint: "/jira/rest/latest/users",
  23. * queryParamKey: "username",
  24. * matcher: function(model, query) {
  25. * return _.startsWith(model.get('username'), query);
  26. * }
  27. * });
  28. * source.on('respond', doStuffWithMatchingResults);
  29. * source.query('john');
  30. *
  31. * @property {String} value the latest query for which the ProgressiveDataSet is responding to.
  32. * @property {Number} activeQueryCount the number of queries being run remotely.
  33. */
  34. initialize: function (models, options) {
  35. options || (options = {});
  36. if (options.matcher) {
  37. this.matcher = options.matcher;
  38. }
  39. if (options.model) {
  40. this.model = options.model; // Fixed in backbone 0.9.2
  41. }
  42. this._idAttribute = (new this.model()).idAttribute;
  43. this._maxResults = options.maxResults || 5;
  44. this._queryData = options.queryData || {};
  45. this._queryParamKey = options.queryParamKey || 'q';
  46. this._queryEndpoint = options.queryEndpoint || '';
  47. this.value = null;
  48. this.queryCache = {};
  49. this.activeQueryCount = 0;
  50. bindAll(this, 'query', 'respond');
  51. },
  52. url: function () {
  53. return this._queryEndpoint;
  54. },
  55. /**
  56. * Sets and runs a query against the ProgressiveDataSet.
  57. *
  58. * Bind to ProgressiveDataSet's 'respond' event to receive the results that match the latest query.
  59. *
  60. * @param {String} query the query to run.
  61. */
  62. query: function (query) {
  63. var remote;
  64. var results;
  65. this.value = query;
  66. results = this.getFilteredResults(query);
  67. this.respond(query, results);
  68. if (!query || !this._queryEndpoint || this.hasQueryCache(query) || !this.shouldGetMoreResults(results)) {
  69. return;
  70. }
  71. remote = this.fetch(query);
  72. this.activeQueryCount++;
  73. this.trigger('activity', {activity: true});
  74. remote.always(() => {
  75. this.activeQueryCount--;
  76. this.trigger('activity', {activity: !!this.activeQueryCount});
  77. });
  78. remote.done((resp, succ, xhr) => {
  79. this.addQueryCache(query, resp, xhr);
  80. });
  81. remote.done(() => {
  82. query = this.value;
  83. results = this.getFilteredResults(query);
  84. this.respond(query, results);
  85. });
  86. },
  87. /**
  88. * Gets all the data that should be sent in a remote request for data.
  89. * @param {String} query the value of the query to be run.
  90. * @return {Object} the data to to be sent to the remote when querying it.
  91. * @private
  92. */
  93. getQueryData: function (query) {
  94. var params = isFunction(this._queryData) ? this._queryData(query) : this._queryData;
  95. var data = extend({}, params);
  96. data[this._queryParamKey] = query;
  97. return data;
  98. },
  99. /**
  100. * Get data from a remote source that matches the query, and add it to this ProgressiveDataSet's set.
  101. *
  102. * @param {String} query the value of the query to be run.
  103. * @return {jQuery.Deferred} a deferred object representing the remote request.
  104. */
  105. fetch: function (query) {
  106. var data = this.getQueryData(query);
  107. // {add: true} for Backbone <= 0.9.2
  108. // {update: true, remove: false} for Backbone >= 0.9.9
  109. var params = {add: true, update: true, remove: false, data: data};
  110. var remote = Backbone.Collection.prototype.fetch.call(this, params);
  111. return remote;
  112. },
  113. /**
  114. * Triggers the 'respond' event on this ProgressiveDataSet for the given query and associated results.
  115. *
  116. * @param {String} query the query that was run
  117. * @param {Array} results a set of results that matched the query.
  118. * @return {Array} the results.
  119. * @private
  120. */
  121. respond: function (query, results) {
  122. this.trigger('respond', {
  123. query: query,
  124. results: results
  125. });
  126. return results;
  127. },
  128. /**
  129. * A hook-point to define a function that tests whether a model matches a query or not.
  130. *
  131. * This will be called by getFilteredResults in order to generate the list of results for a query.
  132. *
  133. * (For you java folks, it's essentially a predicate.)
  134. *
  135. * @param {Backbone.Model} item a model of the data to check for a match in.
  136. * @param {String} query the value to test against the item.
  137. * @returns {Boolean} true if the model matches the query, otherwise false.
  138. * @function
  139. */
  140. matcher: function (item, query) { }, // eslint-disable-line no-unused-vars
  141. /**
  142. * Filters the set of data contained by the ProgressiveDataSet down to a smaller set of results.
  143. *
  144. * The set will only consist of Models that "match" the query -- i.e., only Models where
  145. * a call to ProgressiveDataSet#matcher returns true.
  146. *
  147. * @param query {String} the value that results should match (according to the matcher function)
  148. * @return {Array} A set of Backbone Models that match the query.
  149. */
  150. getFilteredResults: function (query) {
  151. var results = [];
  152. if (!query) {
  153. return results;
  154. }
  155. results = this.filter(function (item) {
  156. return !!this.matcher(item, query);
  157. }, this);
  158. if (this._maxResults) {
  159. results = first(results, this._maxResults);
  160. }
  161. return results;
  162. },
  163. /**
  164. * Store a response in the query cache for a given query.
  165. *
  166. * @param {String} query the value to cache a response for.
  167. * @param {Object} response the data of the response from the server.
  168. * @param {XMLHttpRequest} xhr
  169. * @private
  170. */
  171. addQueryCache: function (query, response, xhr) {
  172. var cache = this.queryCache;
  173. var results = this.parse(response, xhr);
  174. cache[query] = pluck(results, this._idAttribute);
  175. },
  176. /**
  177. * Check if there is a query cache entry for a given query.
  178. *
  179. * @param query the value to check in the cache
  180. * @return {Boolean} true if the cache contains a response for the query, false otherwise.
  181. */
  182. hasQueryCache: function (query) {
  183. return this.queryCache.hasOwnProperty(query);
  184. },
  185. /**
  186. * Get the query cache entry for a given query.
  187. *
  188. * @param query the value to check in the cache
  189. * @return {Object[]} an array of values representing the IDs of the models the response for this query contained.
  190. */
  191. findQueryCache: function (query) {
  192. return this.queryCache[query];
  193. },
  194. /**
  195. *
  196. * @param {Array} results the set of results we know about right now.
  197. * @return {Boolean} true if the ProgressiveDataSet should look for more results.
  198. * @private
  199. */
  200. shouldGetMoreResults: function (results) {
  201. return results.length < this._maxResults;
  202. },
  203. /**
  204. *
  205. * @note Changing this value will trigger ProgressiveDataSet#event:respond if there is a query.
  206. * @param {Number} number how many results should the ProgressiveDataSet aim to retrieve for a query.
  207. */
  208. setMaxResults: function (number) {
  209. this._maxResults = number;
  210. this.value && this.respond(this.value, this.getFilteredResults(this.value));
  211. }
  212. });
  213. globalize('ProgressiveDataSet', ProgressiveDataSet);
  214. export default ProgressiveDataSet;