]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/autocomplete/autocomplete-sources.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / autocomplete / autocomplete-sources.js
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.3.0
6 build: 3167
7 */
8 YUI.add('autocomplete-sources', function(Y) {
9
10 /**
11  * Mixes support for JSONP and YQL result sources into AutoCompleteBase.
12  *
13  * @module autocomplete
14  * @submodule autocomplete-sources
15  */
16
17 var Lang = Y.Lang,
18
19     _SOURCE_SUCCESS = '_sourceSuccess',
20
21     MAX_RESULTS         = 'maxResults',
22     REQUEST_TEMPLATE    = 'requestTemplate',
23     RESULT_LIST_LOCATOR = 'resultListLocator';
24
25 function ACSources() {}
26
27 ACSources.prototype = {
28     /**
29      * Regular expression used to determine whether a String source is a YQL
30      * query.
31      *
32      * @property _YQL_SOURCE_REGEX
33      * @type RegExp
34      * @protected
35      * @for AutoCompleteBase
36      */
37     _YQL_SOURCE_REGEX: /^(?:select|set|use)\s+/i,
38
39     /**
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.
42      *
43      * @method _createIOSource
44      * @param {String} source URL.
45      * @return {Object} DataSource-like object.
46      * @protected
47      * @for AutoCompleteBase
48      */
49     _createIOSource: function (source) {
50         var cache    = {},
51             ioSource = {},
52             that     = this,
53             ioRequest, lastRequest, loading;
54
55         ioSource.sendRequest = function (request) {
56             var _sendRequest = function (request) {
57                 var query = request.request,
58                     maxResults, requestTemplate, url;
59
60                 if (cache[query]) {
61                     that[_SOURCE_SUCCESS](cache[query], request);
62                 } else {
63                     maxResults      = that.get(MAX_RESULTS);
64                     requestTemplate = that.get(REQUEST_TEMPLATE);
65                     url             = source;
66
67                     if (requestTemplate) {
68                         url += requestTemplate(query);
69                     }
70
71                     url = Lang.sub(url, {
72                         maxResults: maxResults > 0 ? maxResults : 1000,
73                         query     : encodeURIComponent(query)
74                     });
75
76                     // Cancel any outstanding requests.
77                     if (ioRequest && ioRequest.isInProgress()) {
78                         ioRequest.abort();
79                     }
80
81                     ioRequest = Y.io(url, {
82                         on: {
83                             success: function (tid, response) {
84                                 var data;
85
86                                 try {
87                                     data = Y.JSON.parse(response.responseText);
88                                 } catch (ex) {
89                                     Y.error('JSON parse error', ex);
90                                 }
91
92                                 if (data) {
93                                     cache[query] = data;
94                                     that[_SOURCE_SUCCESS](data, request);
95                                 }
96                             }
97                         }
98                     });
99                 }
100             };
101
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;
106
107             if (!loading) {
108                 loading = true;
109
110                 // Lazy-load the io and json-parse modules if necessary, then
111                 // overwrite the sendRequest method to bypass this check in the
112                 // future.
113                 Y.use('io-base', 'json-parse', function () {
114                     ioSource.sendRequest = _sendRequest;
115                     _sendRequest(lastRequest);
116                 });
117             }
118         };
119
120         return ioSource;
121     },
122
123     /**
124      * Creates a DataSource-like object that uses the specified JSONPRequest
125      * instance as a source. See the <code>source</code> attribute for more
126      * details.
127      *
128      * @method _createJSONPSource
129      * @param {JSONPRequest|String} source URL string or JSONPRequest instance.
130      * @return {Object} DataSource-like object.
131      * @protected
132      * @for AutoCompleteBase
133      */
134     _createJSONPSource: function (source) {
135         var cache       = {},
136             jsonpSource = {},
137             that        = this,
138             lastRequest, loading;
139
140         jsonpSource.sendRequest = function (request) {
141             var _sendRequest = function (request) {
142                 var query = request.request;
143
144                 if (cache[query]) {
145                     that[_SOURCE_SUCCESS](cache[query], request);
146                 } else {
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.
150                     //
151                     // This limitation is mentioned in the following JSONP
152                     // enhancement ticket:
153                     //
154                     // http://yuilibrary.com/projects/yui3/ticket/2529371
155                     source._config.on.success = function (data) {
156                         cache[query] = data;
157                         that[_SOURCE_SUCCESS](data, request);
158                     };
159
160                     source.send(query);
161                 }
162             };
163
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;
168
169             if (!loading) {
170                 loading = true;
171
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
176                     // one already.
177                     if (!(source instanceof Y.JSONPRequest)) {
178                         source = new Y.JSONPRequest(source, {
179                             format: Y.bind(that._jsonpFormatter, that)
180                         });
181                     }
182
183                     jsonpSource.sendRequest = _sendRequest;
184                     _sendRequest(lastRequest);
185                 });
186             }
187         };
188
189         return jsonpSource;
190     },
191
192     /**
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.
200      *
201      * @method _createStringSource
202      * @param {String} source URL or YQL query.
203      * @return {Object} DataSource-like object.
204      * @protected
205      * @for AutoCompleteBase
206      */
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
213             // JSONP.
214             return this._createJSONPSource(source);
215         } else {
216             // Not a YQL query or JSONP, so we'll assume it's an XHR URL.
217             return this._createIOSource(source);
218         }
219     },
220
221     /**
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
226      * queries.
227      *
228      * @method _createYQLSource
229      * @param {String} source YQL query.
230      * @return {Object} DataSource-like object.
231      * @protected
232      * @for AutoCompleteBase
233      */
234     _createYQLSource: function (source) {
235         var cache     = {},
236             yqlSource = {},
237             that      = this,
238             lastRequest, loading;
239
240         if (!this.get(RESULT_LIST_LOCATOR)) {
241             this.set(RESULT_LIST_LOCATOR, this._defaultYQLLocator);
242         }
243
244         yqlSource.sendRequest = function (request) {
245             var yqlRequest,
246
247             _sendRequest = function (request) {
248                 var query = request.request,
249                     callback, env, maxResults, opts, yqlQuery;
250
251                 if (cache[query]) {
252                     that[_SOURCE_SUCCESS](cache[query], request);
253                 } else {
254                     callback = function (data) {
255                         cache[query] = data;
256                         that[_SOURCE_SUCCESS](data, request);
257                     };
258
259                     env        = that.get('yqlEnv');
260                     maxResults = that.get(MAX_RESULTS);
261
262                     opts = {proto: that.get('yqlProtocol')};
263
264                     yqlQuery = Lang.sub(source, {
265                         maxResults: maxResults > 0 ? maxResults : 1000,
266                         query     : query
267                     });
268
269                     // Only create a new YQLRequest instance if this is the
270                     // first request. For subsequent requests, we'll reuse the
271                     // original instance.
272                     if (yqlRequest) {
273                         yqlRequest._callback   = callback;
274                         yqlRequest._opts       = opts;
275                         yqlRequest._params.q   = yqlQuery;
276
277                         if (env) {
278                             yqlRequest._params.env = env;
279                         }
280                     } else {
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);
285                     }
286
287                     yqlRequest.send();
288                 }
289             };
290
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;
295
296             if (!loading) {
297                 // Lazy-load the YQL module if necessary, then overwrite the
298                 // sendRequest method to bypass this check in the future.
299                 loading = true;
300
301                 Y.use('yql', function () {
302                     yqlSource.sendRequest = _sendRequest;
303                     _sendRequest(lastRequest);
304                 });
305             }
306         };
307
308         return yqlSource;
309     },
310
311     /**
312      * Default resultListLocator used when a string-based YQL source is set and
313      * the implementer hasn't already specified one.
314      *
315      * @method _defaultYQLLocator
316      * @param {Object} response YQL response object.
317      * @return {Array}
318      * @protected
319      * @for AutoCompleteBase
320      */
321     _defaultYQLLocator: function (response) {
322         var results = response && response.query && response.query.results,
323             values;
324
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
333             if (!Lang.isArray(results)) {
334                 results = [results];
335             }
336         } else {
337             results = [];
338         }
339
340         return results;
341     },
342
343     /**
344      * URL formatter passed to <code>JSONPRequest</code> instances.
345      *
346      * @method _jsonpFormatter
347      * @param {String} url
348      * @param {String} proxy
349      * @param {String} query
350      * @return {String} Formatted URL
351      * @protected
352      * @for AutoCompleteBase
353      */
354     _jsonpFormatter: function (url, proxy, query) {
355         var maxResults      = this.get(MAX_RESULTS),
356             requestTemplate = this.get(REQUEST_TEMPLATE);
357
358         if (requestTemplate) {
359             url += requestTemplate(query);
360         }
361
362         return Lang.sub(url, {
363             callback  : proxy,
364             maxResults: maxResults > 0 ? maxResults : 1000,
365             query     : encodeURIComponent(query)
366         });
367     }
368 };
369
370 ACSources.ATTRS = {
371     /**
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).
375      *
376      * @attribute yqlEnv
377      * @type String
378      * @default null
379      * @for AutoCompleteBase
380      */
381     yqlEnv: {
382         value: null
383     },
384
385     /**
386      * URL protocol to use when the <code>source</code> is set to a YQL query.
387      *
388      * @attribute yqlProtocol
389      * @type String
390      * @default 'http'
391      * @for AutoCompleteBase
392      */
393     yqlProtocol: {
394         value: 'http'
395     }
396 };
397
398 Y.Base.mix(Y.AutoCompleteBase, [ACSources]);
399
400
401 }, '3.3.0' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']});