Dashboard sipadu mbip
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

autocomplete-sources.js 16KB

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