src/cm/media/js/lib/yui/yui_3.10.3/build/autocomplete-sources/autocomplete-sources.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 Yahoo! Inc. All rights reserved.
       
     4 Licensed under the BSD License.
       
     5 http://yuilibrary.com/license/
       
     6 */
       
     7 
       
     8 YUI.add('autocomplete-sources', function (Y, NAME) {
       
     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 ACBase = Y.AutoCompleteBase,
       
    18     Lang   = Y.Lang,
       
    19 
       
    20     _SOURCE_SUCCESS = '_sourceSuccess',
       
    21 
       
    22     MAX_RESULTS         = 'maxResults',
       
    23     REQUEST_TEMPLATE    = 'requestTemplate',
       
    24     RESULT_LIST_LOCATOR = 'resultListLocator';
       
    25 
       
    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.
       
    30 
       
    31     @property _YQL_SOURCE_REGEX
       
    32     @type RegExp
       
    33     @protected
       
    34     @for AutoCompleteBase
       
    35     **/
       
    36     _YQL_SOURCE_REGEX: /^(?:select|set|use)\s+/i,
       
    37 
       
    38     /**
       
    39     Runs before AutoCompleteBase's `_createObjectSource()` method and augments
       
    40     it to support additional object-based source types.
       
    41 
       
    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') {
       
    52 
       
    53             return this._createSelectSource(source);
       
    54         }
       
    55 
       
    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         }
       
    61 
       
    62         // Fall back to a basic object source.
       
    63         return this._createObjectSource(source);
       
    64     },
       
    65 
       
    66     /**
       
    67     Creates a DataSource-like object that uses `Y.io` as a source. See the
       
    68     `source` attribute for more details.
       
    69 
       
    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;
       
    80 
       
    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;
       
    85 
       
    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             }
       
    91 
       
    92             // Cancel any outstanding requests.
       
    93             if (ioRequest && ioRequest.isInProgress()) {
       
    94                 ioRequest.abort();
       
    95             }
       
    96 
       
    97             ioRequest = Y.io(that._getXHRUrl(source, request), {
       
    98                 on: {
       
    99                     success: function (tid, response) {
       
   100                         var data;
       
   101 
       
   102                         try {
       
   103                             data = Y.JSON.parse(response.responseText);
       
   104                         } catch (ex) {
       
   105                             Y.error('JSON parse error', ex);
       
   106                         }
       
   107 
       
   108                         if (data) {
       
   109                             that._cache && (that._cache[cacheKey] = data);
       
   110                             that[_SOURCE_SUCCESS](data, request);
       
   111                         }
       
   112                     }
       
   113                 }
       
   114             });
       
   115         }
       
   116 
       
   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;
       
   122 
       
   123             if (loading) { return; }
       
   124 
       
   125             loading = true;
       
   126 
       
   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         };
       
   135 
       
   136         return ioSource;
       
   137     },
       
   138 
       
   139     /**
       
   140     Creates a DataSource-like object that uses the specified JSONPRequest
       
   141     instance as a source. See the `source` attribute for more details.
       
   142 
       
   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;
       
   153 
       
   154         function _sendRequest(request) {
       
   155             var cacheKey = request.request,
       
   156                 query    = request.query;
       
   157 
       
   158             if (that._cache && cacheKey in that._cache) {
       
   159                 that[_SOURCE_SUCCESS](that._cache[cacheKey], request);
       
   160                 return;
       
   161             }
       
   162 
       
   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             };
       
   175 
       
   176             source.send(query);
       
   177         }
       
   178 
       
   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;
       
   184 
       
   185             if (loading) { return; }
       
   186 
       
   187             loading = true;
       
   188 
       
   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                 }
       
   199 
       
   200                 jsonpSource.sendRequest = _sendRequest;
       
   201                 _sendRequest(lastRequest);
       
   202             });
       
   203         };
       
   204 
       
   205         return jsonpSource;
       
   206     },
       
   207 
       
   208     /**
       
   209     Creates a DataSource-like object that uses the specified `<select>` node as
       
   210     a source.
       
   211 
       
   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;
       
   220 
       
   221         return {
       
   222             type: 'select',
       
   223             sendRequest: function (request) {
       
   224                 var options = [];
       
   225 
       
   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                 });
       
   236 
       
   237                 that[_SOURCE_SUCCESS](options, request);
       
   238             }
       
   239         };
       
   240     },
       
   241 
       
   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.
       
   249 
       
   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     },
       
   269 
       
   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.
       
   275 
       
   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;
       
   286 
       
   287         if (!that.get(RESULT_LIST_LOCATOR)) {
       
   288             that.set(RESULT_LIST_LOCATOR, that._defaultYQLLocator);
       
   289         }
       
   290 
       
   291         function _sendRequest(request) {
       
   292             var query      = request.query,
       
   293                 env        = that.get('yqlEnv'),
       
   294                 maxResults = that.get(MAX_RESULTS),
       
   295                 callback, opts, yqlQuery;
       
   296 
       
   297             yqlQuery = Lang.sub(source, {
       
   298                 maxResults: maxResults > 0 ? maxResults : 1000,
       
   299                 request   : request.request,
       
   300                 query     : query
       
   301             });
       
   302 
       
   303             if (that._cache && yqlQuery in that._cache) {
       
   304                 that[_SOURCE_SUCCESS](that._cache[yqlQuery], request);
       
   305                 return;
       
   306             }
       
   307 
       
   308             callback = function (data) {
       
   309                 that._cache && (that._cache[yqlQuery] = data);
       
   310                 that[_SOURCE_SUCCESS](data, request);
       
   311             };
       
   312 
       
   313             opts = {proto: that.get('yqlProtocol')};
       
   314 
       
   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;
       
   322 
       
   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             }
       
   332 
       
   333             yqlRequest.send();
       
   334         }
       
   335 
       
   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;
       
   341 
       
   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;
       
   346 
       
   347                 Y.use('yql', function () {
       
   348                     yqlSource.sendRequest = _sendRequest;
       
   349                     _sendRequest(lastRequest);
       
   350                 });
       
   351             }
       
   352         };
       
   353 
       
   354         return yqlSource;
       
   355     },
       
   356 
       
   357     /**
       
   358     Default resultListLocator used when a string-based YQL source is set and the
       
   359     implementer hasn't already specified one.
       
   360 
       
   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;
       
   370 
       
   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;
       
   378 
       
   379             if (!Lang.isArray(results)) {
       
   380                 results = [results];
       
   381             }
       
   382         } else {
       
   383             results = [];
       
   384         }
       
   385 
       
   386         return results;
       
   387     },
       
   388 
       
   389     /**
       
   390     Returns a formatted XHR URL based on the specified base _url_, _query_, and
       
   391     the current _requestTemplate_ if any.
       
   392 
       
   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);
       
   403 
       
   404         if (request.query !== request.request) {
       
   405             // Append the request template to the URL.
       
   406             url += request.request;
       
   407         }
       
   408 
       
   409         return Lang.sub(url, {
       
   410             maxResults: maxResults > 0 ? maxResults : 1000,
       
   411             query     : encodeURIComponent(request.query)
       
   412         });
       
   413     },
       
   414 
       
   415     /**
       
   416     URL formatter passed to `JSONPRequest` instances.
       
   417 
       
   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);
       
   429 
       
   430         if (requestTemplate) {
       
   431             url += requestTemplate(query);
       
   432         }
       
   433 
       
   434         return Lang.sub(url, {
       
   435             callback  : proxy,
       
   436             maxResults: maxResults > 0 ? maxResults : 1000,
       
   437             query     : encodeURIComponent(query)
       
   438         });
       
   439     }
       
   440 });
       
   441 
       
   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).
       
   448 
       
   449     @attribute yqlEnv
       
   450     @type String
       
   451     @default null
       
   452     @for AutoCompleteBase
       
   453     **/
       
   454     yqlEnv: {
       
   455         value: null
       
   456     },
       
   457 
       
   458     /**
       
   459     URL protocol to use when the `source` is set to a YQL query.
       
   460 
       
   461     @attribute yqlProtocol
       
   462     @type String
       
   463     @default 'http'
       
   464     @for AutoCompleteBase
       
   465     **/
       
   466     yqlProtocol: {
       
   467         value: 'http'
       
   468     }
       
   469 });
       
   470 
       
   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);
       
   480 
       
   481 
       
   482 }, '3.10.3', {"optional": ["io-base", "json-parse", "jsonp", "yql"], "requires": ["autocomplete-base"]});