Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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