2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
8 YUI.add('autocomplete-sources', function(Y) {
11 * Mixes support for JSONP and YQL result sources into AutoCompleteBase.
13 * @module autocomplete
14 * @submodule autocomplete-sources
19 _SOURCE_SUCCESS = '_sourceSuccess',
21 MAX_RESULTS = 'maxResults',
22 REQUEST_TEMPLATE = 'requestTemplate',
23 RESULT_LIST_LOCATOR = 'resultListLocator';
25 function ACSources() {}
27 ACSources.prototype = {
29 * Regular expression used to determine whether a String source is a YQL
32 * @property _YQL_SOURCE_REGEX
35 * @for AutoCompleteBase
37 _YQL_SOURCE_REGEX: /^(?:select|set|use)\s+/i,
40 * Creates a DataSource-like object that uses <code>Y.io</code> as a source.
41 * See the <code>source</code> attribute for more details.
43 * @method _createIOSource
44 * @param {String} source URL.
45 * @return {Object} DataSource-like object.
47 * @for AutoCompleteBase
49 _createIOSource: function (source) {
53 ioRequest, lastRequest, loading;
55 ioSource.sendRequest = function (request) {
56 var _sendRequest = function (request) {
57 var query = request.request,
58 maxResults, requestTemplate, url;
61 that[_SOURCE_SUCCESS](cache[query], request);
63 maxResults = that.get(MAX_RESULTS);
64 requestTemplate = that.get(REQUEST_TEMPLATE);
67 if (requestTemplate) {
68 url += requestTemplate(query);
72 maxResults: maxResults > 0 ? maxResults : 1000,
73 query : encodeURIComponent(query)
76 // Cancel any outstanding requests.
77 if (ioRequest && ioRequest.isInProgress()) {
81 ioRequest = Y.io(url, {
83 success: function (tid, response) {
87 data = Y.JSON.parse(response.responseText);
89 Y.error('JSON parse error', ex);
94 that[_SOURCE_SUCCESS](data, request);
102 // Keep track of the most recent request in case there are multiple
103 // requests while we're waiting for the IO module to load. Only the
104 // most recent request will be sent.
105 lastRequest = request;
110 // Lazy-load the io and json-parse modules if necessary, then
111 // overwrite the sendRequest method to bypass this check in the
113 Y.use('io-base', 'json-parse', function () {
114 ioSource.sendRequest = _sendRequest;
115 _sendRequest(lastRequest);
124 * Creates a DataSource-like object that uses the specified JSONPRequest
125 * instance as a source. See the <code>source</code> attribute for more
128 * @method _createJSONPSource
129 * @param {JSONPRequest|String} source URL string or JSONPRequest instance.
130 * @return {Object} DataSource-like object.
132 * @for AutoCompleteBase
134 _createJSONPSource: function (source) {
138 lastRequest, loading;
140 jsonpSource.sendRequest = function (request) {
141 var _sendRequest = function (request) {
142 var query = request.request;
145 that[_SOURCE_SUCCESS](cache[query], request);
147 // Hack alert: JSONPRequest currently doesn't support
148 // per-request callbacks, so we're reaching into the protected
149 // _config object to make it happen.
151 // This limitation is mentioned in the following JSONP
152 // enhancement ticket:
154 // http://yuilibrary.com/projects/yui3/ticket/2529371
155 source._config.on.success = function (data) {
157 that[_SOURCE_SUCCESS](data, request);
164 // Keep track of the most recent request in case there are multiple
165 // requests while we're waiting for the JSONP module to load. Only
166 // the most recent request will be sent.
167 lastRequest = request;
172 // Lazy-load the JSONP module if necessary, then overwrite the
173 // sendRequest method to bypass this check in the future.
174 Y.use('jsonp', function () {
175 // Turn the source into a JSONPRequest instance if it isn't
177 if (!(source instanceof Y.JSONPRequest)) {
178 source = new Y.JSONPRequest(source, {
179 format: Y.bind(that._jsonpFormatter, that)
183 jsonpSource.sendRequest = _sendRequest;
184 _sendRequest(lastRequest);
193 * Creates a DataSource-like object that calls the specified URL or
194 * executes the specified YQL query for results. If the string starts
195 * with "select ", "use ", or "set " (case-insensitive), it's assumed to be
196 * a YQL query; otherwise, it's assumed to be a URL (which may be absolute
197 * or relative). URLs containing a "{callback}" placeholder are assumed to
198 * be JSONP URLs; all others will use XHR. See the <code>source</code>
199 * attribute for more details.
201 * @method _createStringSource
202 * @param {String} source URL or YQL query.
203 * @return {Object} DataSource-like object.
205 * @for AutoCompleteBase
207 _createStringSource: function (source) {
208 if (this._YQL_SOURCE_REGEX.test(source)) {
209 // Looks like a YQL query.
210 return this._createYQLSource(source);
211 } else if (source.indexOf('{callback}') !== -1) {
212 // Contains a {callback} param and isn't a YQL query, so it must be
214 return this._createJSONPSource(source);
216 // Not a YQL query or JSONP, so we'll assume it's an XHR URL.
217 return this._createIOSource(source);
222 * Creates a DataSource-like object that uses the specified YQL query string
223 * to create a YQL-based source. See the <code>source</code> attribute for
224 * details. If no <code>resultListLocator</code> is defined, this method
225 * will set a best-guess locator that might work for many typical YQL
228 * @method _createYQLSource
229 * @param {String} source YQL query.
230 * @return {Object} DataSource-like object.
232 * @for AutoCompleteBase
234 _createYQLSource: function (source) {
238 lastRequest, loading;
240 if (!this.get(RESULT_LIST_LOCATOR)) {
241 this.set(RESULT_LIST_LOCATOR, this._defaultYQLLocator);
244 yqlSource.sendRequest = function (request) {
247 _sendRequest = function (request) {
248 var query = request.request,
249 callback, env, maxResults, opts, yqlQuery;
252 that[_SOURCE_SUCCESS](cache[query], request);
254 callback = function (data) {
256 that[_SOURCE_SUCCESS](data, request);
259 env = that.get('yqlEnv');
260 maxResults = that.get(MAX_RESULTS);
262 opts = {proto: that.get('yqlProtocol')};
264 yqlQuery = Lang.sub(source, {
265 maxResults: maxResults > 0 ? maxResults : 1000,
269 // Only create a new YQLRequest instance if this is the
270 // first request. For subsequent requests, we'll reuse the
271 // original instance.
273 yqlRequest._callback = callback;
274 yqlRequest._opts = opts;
275 yqlRequest._params.q = yqlQuery;
278 yqlRequest._params.env = env;
281 yqlRequest = new Y.YQLRequest(yqlQuery, {
282 on: {success: callback},
283 allowCache: false // temp workaround until JSONP has per-URL callback proxies
284 }, env ? {env: env} : null, opts);
291 // Keep track of the most recent request in case there are multiple
292 // requests while we're waiting for the YQL module to load. Only the
293 // most recent request will be sent.
294 lastRequest = request;
297 // Lazy-load the YQL module if necessary, then overwrite the
298 // sendRequest method to bypass this check in the future.
301 Y.use('yql', function () {
302 yqlSource.sendRequest = _sendRequest;
303 _sendRequest(lastRequest);
312 * Default resultListLocator used when a string-based YQL source is set and
313 * the implementer hasn't already specified one.
315 * @method _defaultYQLLocator
316 * @param {Object} response YQL response object.
319 * @for AutoCompleteBase
321 _defaultYQLLocator: function (response) {
322 var results = response && response.query && response.query.results,
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;
333 if (!Lang.isArray(results)) {
344 * URL formatter passed to <code>JSONPRequest</code> instances.
346 * @method _jsonpFormatter
347 * @param {String} url
348 * @param {String} proxy
349 * @param {String} query
350 * @return {String} Formatted URL
352 * @for AutoCompleteBase
354 _jsonpFormatter: function (url, proxy, query) {
355 var maxResults = this.get(MAX_RESULTS),
356 requestTemplate = this.get(REQUEST_TEMPLATE);
358 if (requestTemplate) {
359 url += requestTemplate(query);
362 return Lang.sub(url, {
364 maxResults: maxResults > 0 ? maxResults : 1000,
365 query : encodeURIComponent(query)
372 * YQL environment file URL to load when the <code>source</code> is set to
373 * a YQL query. Set this to <code>null</code> to use the default Open Data
374 * Tables environment file (http://datatables.org/alltables.env).
379 * @for AutoCompleteBase
386 * URL protocol to use when the <code>source</code> is set to a YQL query.
388 * @attribute yqlProtocol
391 * @for AutoCompleteBase
398 Y.Base.mix(Y.AutoCompleteBase, [ACSources]);
401 }, '3.3.0' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']});