PageRenderTime 46ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/auiplugin/src/main/resources/experimental/js/atlassian/autocomplete/progressive-data-set.js

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