autocomplete-sources-debug.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /*
  2. YUI 3.4.1 (build 4118)
  3. Copyright 2011 Yahoo! Inc. All rights reserved.
  4. Licensed under the BSD License.
  5. http://yuilibrary.com/license/
  6. */
  7. YUI.add('autocomplete-sources', function(Y) {
  8. /**
  9. * Mixes support for JSONP and YQL result sources into AutoCompleteBase.
  10. *
  11. * @module autocomplete
  12. * @submodule autocomplete-sources
  13. */
  14. var ACBase = Y.AutoCompleteBase,
  15. Lang = Y.Lang,
  16. _SOURCE_SUCCESS = '_sourceSuccess',
  17. MAX_RESULTS = 'maxResults',
  18. REQUEST_TEMPLATE = 'requestTemplate',
  19. RESULT_LIST_LOCATOR = 'resultListLocator';
  20. // Add prototype properties and methods to AutoCompleteBase.
  21. Y.mix(ACBase.prototype, {
  22. /**
  23. * Regular expression used to determine whether a String source is a YQL
  24. * query.
  25. *
  26. * @property _YQL_SOURCE_REGEX
  27. * @type RegExp
  28. * @protected
  29. * @for AutoCompleteBase
  30. */
  31. _YQL_SOURCE_REGEX: /^(?:select|set|use)\s+/i,
  32. /**
  33. * Runs before AutoCompleteBase's <code>_createObjectSource()</code> method
  34. * and augments it to support additional object-based source types.
  35. *
  36. * @method _beforeCreateObjectSource
  37. * @param {String} source
  38. * @protected
  39. * @for AutoCompleteBase
  40. */
  41. _beforeCreateObjectSource: function (source) {
  42. // If the object is a <select> node, use the options as the result
  43. // source.
  44. if (source instanceof Y.Node &&
  45. source.get('nodeName').toLowerCase() === 'select') {
  46. return this._createSelectSource(source);
  47. }
  48. // If the object is a JSONPRequest instance, try to use it as a JSONP
  49. // source.
  50. if (Y.JSONPRequest && source instanceof Y.JSONPRequest) {
  51. return this._createJSONPSource(source);
  52. }
  53. // Fall back to a basic object source.
  54. return this._createObjectSource(source);
  55. },
  56. /**
  57. * Creates a DataSource-like object that uses <code>Y.io</code> as a source.
  58. * See the <code>source</code> attribute for more details.
  59. *
  60. * @method _createIOSource
  61. * @param {String} source URL.
  62. * @return {Object} DataSource-like object.
  63. * @protected
  64. * @for AutoCompleteBase
  65. */
  66. _createIOSource: function (source) {
  67. var cache = {},
  68. ioSource = {type: 'io'},
  69. that = this,
  70. ioRequest, lastRequest, loading;
  71. // Private internal _sendRequest method that will be assigned to
  72. // ioSource.sendRequest once io-base and json-parse are available.
  73. function _sendRequest(request) {
  74. var cacheKey = request.request,
  75. query = request.query;
  76. // Return immediately on a cached response.
  77. if (cache[cacheKey]) {
  78. that[_SOURCE_SUCCESS](cache[cacheKey], request);
  79. return;
  80. }
  81. // Cancel any outstanding requests.
  82. if (ioRequest && ioRequest.isInProgress()) {
  83. ioRequest.abort();
  84. }
  85. ioRequest = Y.io(that._getXHRUrl(source, request), {
  86. on: {
  87. success: function (tid, response) {
  88. var data;
  89. try {
  90. data = Y.JSON.parse(response.responseText);
  91. } catch (ex) {
  92. Y.error('JSON parse error', ex);
  93. }
  94. if (data) {
  95. cache[cacheKey] = data;
  96. that[_SOURCE_SUCCESS](data, request);
  97. }
  98. }
  99. }
  100. });
  101. }
  102. ioSource.sendRequest = function (request) {
  103. // Keep track of the most recent request in case there are multiple
  104. // requests while we're waiting for the IO module to load. Only the
  105. // most recent request will be sent.
  106. lastRequest = request;
  107. if (loading) { return; }
  108. loading = true;
  109. // Lazy-load the io-base and json-parse modules if necessary,
  110. // then overwrite the sendRequest method to bypass this check in
  111. // the future.
  112. Y.use('io-base', 'json-parse', function () {
  113. ioSource.sendRequest = _sendRequest;
  114. _sendRequest(lastRequest);
  115. });
  116. };
  117. return ioSource;
  118. },
  119. /**
  120. * Creates a DataSource-like object that uses the specified JSONPRequest
  121. * instance as a source. See the <code>source</code> attribute for more
  122. * details.
  123. *
  124. * @method _createJSONPSource
  125. * @param {JSONPRequest|String} source URL string or JSONPRequest instance.
  126. * @return {Object} DataSource-like object.
  127. * @protected
  128. * @for AutoCompleteBase
  129. */
  130. _createJSONPSource: function (source) {
  131. var cache = {},
  132. jsonpSource = {type: 'jsonp'},
  133. that = this,
  134. lastRequest, loading;
  135. function _sendRequest(request) {
  136. var cacheKey = request.request,
  137. query = request.query;
  138. if (cache[cacheKey]) {
  139. that[_SOURCE_SUCCESS](cache[cacheKey], request);
  140. return;
  141. }
  142. // Hack alert: JSONPRequest currently doesn't support
  143. // per-request callbacks, so we're reaching into the protected
  144. // _config object to make it happen.
  145. //
  146. // This limitation is mentioned in the following JSONP
  147. // enhancement ticket:
  148. //
  149. // http://yuilibrary.com/projects/yui3/ticket/2529371
  150. source._config.on.success = function (data) {
  151. cache[cacheKey] = data;
  152. that[_SOURCE_SUCCESS](data, request);
  153. };
  154. source.send(query);
  155. }
  156. jsonpSource.sendRequest = function (request) {
  157. // Keep track of the most recent request in case there are multiple
  158. // requests while we're waiting for the JSONP module to load. Only
  159. // the most recent request will be sent.
  160. lastRequest = request;
  161. if (loading) { return; }
  162. loading = true;
  163. // Lazy-load the JSONP module if necessary, then overwrite the
  164. // sendRequest method to bypass this check in the future.
  165. Y.use('jsonp', function () {
  166. // Turn the source into a JSONPRequest instance if it isn't
  167. // one already.
  168. if (!(source instanceof Y.JSONPRequest)) {
  169. source = new Y.JSONPRequest(source, {
  170. format: Y.bind(that._jsonpFormatter, that)
  171. });
  172. }
  173. jsonpSource.sendRequest = _sendRequest;
  174. _sendRequest(lastRequest);
  175. });
  176. };
  177. return jsonpSource;
  178. },
  179. /**
  180. * Creates a DataSource-like object that uses the specified &lt;select&gt;
  181. * node as a source.
  182. *
  183. * @method _createSelectSource
  184. * @param {Node} source YUI Node instance wrapping a &lt;select&gt; node.
  185. * @return {Object} DataSource-like object.
  186. * @protected
  187. * @for AutoCompleteBase
  188. */
  189. _createSelectSource: function (source) {
  190. var that = this;
  191. return {
  192. type: 'select',
  193. sendRequest: function (request) {
  194. var options = [];
  195. source.get('options').each(function (option) {
  196. options.push({
  197. html : option.get('innerHTML'),
  198. index : option.get('index'),
  199. node : option,
  200. selected: option.get('selected'),
  201. text : option.get('text'),
  202. value : option.get('value')
  203. });
  204. });
  205. that[_SOURCE_SUCCESS](options, request);
  206. }
  207. };
  208. },
  209. /**
  210. * Creates a DataSource-like object that calls the specified URL or
  211. * executes the specified YQL query for results. If the string starts
  212. * with "select ", "use ", or "set " (case-insensitive), it's assumed to be
  213. * a YQL query; otherwise, it's assumed to be a URL (which may be absolute
  214. * or relative). URLs containing a "{callback}" placeholder are assumed to
  215. * be JSONP URLs; all others will use XHR. See the <code>source</code>
  216. * attribute for more details.
  217. *
  218. * @method _createStringSource
  219. * @param {String} source URL or YQL query.
  220. * @return {Object} DataSource-like object.
  221. * @protected
  222. * @for AutoCompleteBase
  223. */
  224. _createStringSource: function (source) {
  225. if (this._YQL_SOURCE_REGEX.test(source)) {
  226. // Looks like a YQL query.
  227. return this._createYQLSource(source);
  228. } else if (source.indexOf('{callback}') !== -1) {
  229. // Contains a {callback} param and isn't a YQL query, so it must be
  230. // JSONP.
  231. return this._createJSONPSource(source);
  232. } else {
  233. // Not a YQL query or JSONP, so we'll assume it's an XHR URL.
  234. return this._createIOSource(source);
  235. }
  236. },
  237. /**
  238. * Creates a DataSource-like object that uses the specified YQL query string
  239. * to create a YQL-based source. See the <code>source</code> attribute for
  240. * details. If no <code>resultListLocator</code> is defined, this method
  241. * will set a best-guess locator that might work for many typical YQL
  242. * queries.
  243. *
  244. * @method _createYQLSource
  245. * @param {String} source YQL query.
  246. * @return {Object} DataSource-like object.
  247. * @protected
  248. * @for AutoCompleteBase
  249. */
  250. _createYQLSource: function (source) {
  251. var cache = {},
  252. yqlSource = {type: 'yql'},
  253. that = this,
  254. lastRequest, loading, yqlRequest;
  255. if (!this.get(RESULT_LIST_LOCATOR)) {
  256. this.set(RESULT_LIST_LOCATOR, this._defaultYQLLocator);
  257. }
  258. function _sendRequest(request) {
  259. var cacheKey = request.request,
  260. query = request.query,
  261. callback, env, maxResults, opts, yqlQuery;
  262. if (cache[cacheKey]) {
  263. that[_SOURCE_SUCCESS](cache[cacheKey], request);
  264. return;
  265. }
  266. callback = function (data) {
  267. cache[cacheKey] = data;
  268. that[_SOURCE_SUCCESS](data, request);
  269. };
  270. env = that.get('yqlEnv');
  271. maxResults = that.get(MAX_RESULTS);
  272. opts = {proto: that.get('yqlProtocol')};
  273. yqlQuery = Lang.sub(source, {
  274. maxResults: maxResults > 0 ? maxResults : 1000,
  275. query : query
  276. });
  277. // Only create a new YQLRequest instance if this is the
  278. // first request. For subsequent requests, we'll reuse the
  279. // original instance.
  280. if (yqlRequest) {
  281. yqlRequest._callback = callback;
  282. yqlRequest._opts = opts;
  283. yqlRequest._params.q = yqlQuery;
  284. if (env) {
  285. yqlRequest._params.env = env;
  286. }
  287. } else {
  288. yqlRequest = new Y.YQLRequest(yqlQuery, {
  289. on: {success: callback},
  290. allowCache: false // temp workaround until JSONP has per-URL callback proxies
  291. }, env ? {env: env} : null, opts);
  292. }
  293. yqlRequest.send();
  294. }
  295. yqlSource.sendRequest = function (request) {
  296. // Keep track of the most recent request in case there are multiple
  297. // requests while we're waiting for the YQL module to load. Only the
  298. // most recent request will be sent.
  299. lastRequest = request;
  300. if (!loading) {
  301. // Lazy-load the YQL module if necessary, then overwrite the
  302. // sendRequest method to bypass this check in the future.
  303. loading = true;
  304. Y.use('yql', function () {
  305. yqlSource.sendRequest = _sendRequest;
  306. _sendRequest(lastRequest);
  307. });
  308. }
  309. };
  310. return yqlSource;
  311. },
  312. /**
  313. * Default resultListLocator used when a string-based YQL source is set and
  314. * the implementer hasn't already specified one.
  315. *
  316. * @method _defaultYQLLocator
  317. * @param {Object} response YQL response object.
  318. * @return {Array}
  319. * @protected
  320. * @for AutoCompleteBase
  321. */
  322. _defaultYQLLocator: function (response) {
  323. var results = response && response.query && response.query.results,
  324. values;
  325. if (results && Lang.isObject(results)) {
  326. // If there's only a single value on YQL's results object, that
  327. // value almost certainly contains the array of results we want. If
  328. // there are 0 or 2+ values, then the values themselves are most
  329. // likely the results we want.
  330. values = Y.Object.values(results) || [];
  331. results = values.length === 1 ? values[0] : values;
  332. if (!Lang.isArray(results)) {
  333. results = [results];
  334. }
  335. } else {
  336. results = [];
  337. }
  338. return results;
  339. },
  340. /**
  341. * Returns a formatted XHR URL based on the specified base <i>url</i>,
  342. * <i>query</i>, and the current <i>requestTemplate</i> if any.
  343. *
  344. * @method _getXHRUrl
  345. * @param {String} url Base URL.
  346. * @param {Object} request Request object containing `query` and `request`
  347. * properties.
  348. * @return {String} Formatted URL.
  349. * @protected
  350. * @for AutoCompleteBase
  351. */
  352. _getXHRUrl: function (url, request) {
  353. var maxResults = this.get(MAX_RESULTS);
  354. if (request.query !== request.request) {
  355. // Append the request template to the URL.
  356. url += request.request;
  357. }
  358. return Lang.sub(url, {
  359. maxResults: maxResults > 0 ? maxResults : 1000,
  360. query : encodeURIComponent(request.query)
  361. });
  362. },
  363. /**
  364. * URL formatter passed to <code>JSONPRequest</code> instances.
  365. *
  366. * @method _jsonpFormatter
  367. * @param {String} url
  368. * @param {String} proxy
  369. * @param {String} query
  370. * @return {String} Formatted URL
  371. * @protected
  372. * @for AutoCompleteBase
  373. */
  374. _jsonpFormatter: function (url, proxy, query) {
  375. var maxResults = this.get(MAX_RESULTS),
  376. requestTemplate = this.get(REQUEST_TEMPLATE);
  377. if (requestTemplate) {
  378. url += requestTemplate(query);
  379. }
  380. return Lang.sub(url, {
  381. callback : proxy,
  382. maxResults: maxResults > 0 ? maxResults : 1000,
  383. query : encodeURIComponent(query)
  384. });
  385. }
  386. });
  387. // Add attributes to AutoCompleteBase.
  388. Y.mix(ACBase.ATTRS, {
  389. /**
  390. * YQL environment file URL to load when the <code>source</code> is set to
  391. * a YQL query. Set this to <code>null</code> to use the default Open Data
  392. * Tables environment file (http://datatables.org/alltables.env).
  393. *
  394. * @attribute yqlEnv
  395. * @type String
  396. * @default null
  397. * @for AutoCompleteBase
  398. */
  399. yqlEnv: {
  400. value: null
  401. },
  402. /**
  403. * URL protocol to use when the <code>source</code> is set to a YQL query.
  404. *
  405. * @attribute yqlProtocol
  406. * @type String
  407. * @default 'http'
  408. * @for AutoCompleteBase
  409. */
  410. yqlProtocol: {
  411. value: 'http'
  412. }
  413. });
  414. // Tell AutoCompleteBase about the new source types it can now support.
  415. Y.mix(ACBase.SOURCE_TYPES, {
  416. io : '_createIOSource',
  417. jsonp : '_createJSONPSource',
  418. object: '_beforeCreateObjectSource', // Run our version before the base version.
  419. select: '_createSelectSource',
  420. string: '_createStringSource',
  421. yql : '_createYQLSource'
  422. }, true);
  423. }, '3.4.1' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']});