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