src/cm/media/js/lib/yui/yui_3.10.3/build/autocomplete-base/autocomplete-base.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-base', function (Y, NAME) {
       
     9 
       
    10 /**
       
    11 Provides automatic input completion or suggestions for text input fields and
       
    12 textareas.
       
    13 
       
    14 @module autocomplete
       
    15 @main autocomplete
       
    16 @since 3.3.0
       
    17 **/
       
    18 
       
    19 /**
       
    20 `Y.Base` extension that provides core autocomplete logic (but no UI
       
    21 implementation) for a text input field or textarea. Must be mixed into a
       
    22 `Y.Base`-derived class to be useful.
       
    23 
       
    24 @module autocomplete
       
    25 @submodule autocomplete-base
       
    26 **/
       
    27 
       
    28 /**
       
    29 Extension that provides core autocomplete logic (but no UI implementation) for a
       
    30 text input field or textarea.
       
    31 
       
    32 The `AutoCompleteBase` class provides events and attributes that abstract away
       
    33 core autocomplete logic and configuration, but does not provide a widget
       
    34 implementation or suggestion UI. For a prepackaged autocomplete widget, see
       
    35 `AutoCompleteList`.
       
    36 
       
    37 This extension cannot be instantiated directly, since it doesn't provide an
       
    38 actual implementation. It's intended to be mixed into a `Y.Base`-based class or
       
    39 widget.
       
    40 
       
    41 `Y.Widget`-based example:
       
    42 
       
    43     YUI().use('autocomplete-base', 'widget', function (Y) {
       
    44         var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
       
    45             // Custom prototype methods and properties.
       
    46         }, {
       
    47             // Custom static methods and properties.
       
    48         });
       
    49 
       
    50         // Custom implementation code.
       
    51     });
       
    52 
       
    53 `Y.Base`-based example:
       
    54 
       
    55     YUI().use('autocomplete-base', function (Y) {
       
    56         var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
       
    57             initializer: function () {
       
    58                 this._bindUIACBase();
       
    59                 this._syncUIACBase();
       
    60             },
       
    61 
       
    62             // Custom prototype methods and properties.
       
    63         }, {
       
    64             // Custom static methods and properties.
       
    65         });
       
    66 
       
    67         // Custom implementation code.
       
    68     });
       
    69 
       
    70 @class AutoCompleteBase
       
    71 **/
       
    72 
       
    73 var Escape  = Y.Escape,
       
    74     Lang    = Y.Lang,
       
    75     YArray  = Y.Array,
       
    76     YObject = Y.Object,
       
    77 
       
    78     isFunction = Lang.isFunction,
       
    79     isString   = Lang.isString,
       
    80     trim       = Lang.trim,
       
    81 
       
    82     INVALID_VALUE = Y.Attribute.INVALID_VALUE,
       
    83 
       
    84     _FUNCTION_VALIDATOR = '_functionValidator',
       
    85     _SOURCE_SUCCESS     = '_sourceSuccess',
       
    86 
       
    87     ALLOW_BROWSER_AC    = 'allowBrowserAutocomplete',
       
    88     INPUT_NODE          = 'inputNode',
       
    89     QUERY               = 'query',
       
    90     QUERY_DELIMITER     = 'queryDelimiter',
       
    91     REQUEST_TEMPLATE    = 'requestTemplate',
       
    92     RESULTS             = 'results',
       
    93     RESULT_LIST_LOCATOR = 'resultListLocator',
       
    94     VALUE               = 'value',
       
    95     VALUE_CHANGE        = 'valueChange',
       
    96 
       
    97     EVT_CLEAR   = 'clear',
       
    98     EVT_QUERY   = QUERY,
       
    99     EVT_RESULTS = RESULTS;
       
   100 
       
   101 function AutoCompleteBase() {}
       
   102 
       
   103 AutoCompleteBase.prototype = {
       
   104     // -- Lifecycle Methods ----------------------------------------------------
       
   105     initializer: function () {
       
   106         // AOP bindings.
       
   107         Y.before(this._bindUIACBase, this, 'bindUI');
       
   108         Y.before(this._syncUIACBase, this, 'syncUI');
       
   109 
       
   110         // -- Public Events ----------------------------------------------------
       
   111 
       
   112         /**
       
   113         Fires after the query has been completely cleared or no longer meets the
       
   114         minimum query length requirement.
       
   115 
       
   116         @event clear
       
   117         @param {String} prevVal Value of the query before it was cleared.
       
   118         @param {String} src Source of the event.
       
   119         @preventable _defClearFn
       
   120         **/
       
   121         this.publish(EVT_CLEAR, {
       
   122             defaultFn: this._defClearFn
       
   123         });
       
   124 
       
   125         /**
       
   126         Fires when the contents of the input field have changed and the input
       
   127         value meets the criteria necessary to generate an autocomplete query.
       
   128 
       
   129         @event query
       
   130         @param {String} inputValue Full contents of the text input field or
       
   131             textarea that generated the query.
       
   132         @param {String} query AutoComplete query. This is the string that will
       
   133             be used to request completion results. It may or may not be the same
       
   134             as `inputValue`.
       
   135         @param {String} src Source of the event.
       
   136         @preventable _defQueryFn
       
   137         **/
       
   138         this.publish(EVT_QUERY, {
       
   139             defaultFn: this._defQueryFn
       
   140         });
       
   141 
       
   142         /**
       
   143         Fires after query results are received from the source. If no source has
       
   144         been set, this event will not fire.
       
   145 
       
   146         @event results
       
   147         @param {Array|Object} data Raw, unfiltered result data (if available).
       
   148         @param {String} query Query that generated these results.
       
   149         @param {Object[]} results Array of filtered, formatted, and highlighted
       
   150             results. Each item in the array is an object with the following
       
   151             properties:
       
   152 
       
   153             @param {Node|HTMLElement|String} results.display Formatted result
       
   154                 HTML suitable for display to the user. If no custom formatter is
       
   155                 set, this will be an HTML-escaped version of the string in the
       
   156                 `text` property.
       
   157             @param {String} [results.highlighted] Highlighted (but not
       
   158                 formatted) result text. This property will only be set if a
       
   159                 highlighter is in use.
       
   160             @param {Any} results.raw Raw, unformatted result in whatever form it
       
   161                 was provided by the source.
       
   162             @param {String} results.text Plain text version of the result,
       
   163                 suitable for being inserted into the value of a text input field
       
   164                 or textarea when the result is selected by a user. This value is
       
   165                 not HTML-escaped and should not be inserted into the page using
       
   166                 `innerHTML` or `Node#setContent()`.
       
   167 
       
   168         @preventable _defResultsFn
       
   169         **/
       
   170         this.publish(EVT_RESULTS, {
       
   171             defaultFn: this._defResultsFn
       
   172         });
       
   173     },
       
   174 
       
   175     destructor: function () {
       
   176         this._acBaseEvents && this._acBaseEvents.detach();
       
   177 
       
   178         delete this._acBaseEvents;
       
   179         delete this._cache;
       
   180         delete this._inputNode;
       
   181         delete this._rawSource;
       
   182     },
       
   183 
       
   184     // -- Public Prototype Methods ---------------------------------------------
       
   185 
       
   186     /**
       
   187     Clears the result cache.
       
   188 
       
   189     @method clearCache
       
   190     @chainable
       
   191     @since 3.5.0
       
   192     **/
       
   193     clearCache: function () {
       
   194         this._cache && (this._cache = {});
       
   195         return this;
       
   196     },
       
   197 
       
   198     /**
       
   199     Sends a request to the configured source. If no source is configured, this
       
   200     method won't do anything.
       
   201 
       
   202     Usually there's no reason to call this method manually; it will be called
       
   203     automatically when user input causes a `query` event to be fired. The only
       
   204     time you'll need to call this method manually is if you want to force a
       
   205     request to be sent when no user input has occurred.
       
   206 
       
   207     @method sendRequest
       
   208     @param {String} [query] Query to send. If specified, the `query` attribute
       
   209         will be set to this query. If not specified, the current value of the
       
   210         `query` attribute will be used.
       
   211     @param {Function} [requestTemplate] Request template function. If not
       
   212         specified, the current value of the `requestTemplate` attribute will be
       
   213         used.
       
   214     @chainable
       
   215     **/
       
   216     sendRequest: function (query, requestTemplate) {
       
   217         var request,
       
   218             source = this.get('source');
       
   219 
       
   220         if (query || query === '') {
       
   221             this._set(QUERY, query);
       
   222         } else {
       
   223             query = this.get(QUERY) || '';
       
   224         }
       
   225 
       
   226         if (source) {
       
   227             if (!requestTemplate) {
       
   228                 requestTemplate = this.get(REQUEST_TEMPLATE);
       
   229             }
       
   230 
       
   231             request = requestTemplate ?
       
   232                 requestTemplate.call(this, query) : query;
       
   233 
       
   234 
       
   235             source.sendRequest({
       
   236                 query  : query,
       
   237                 request: request,
       
   238 
       
   239                 callback: {
       
   240                     success: Y.bind(this._onResponse, this, query)
       
   241                 }
       
   242             });
       
   243         }
       
   244 
       
   245         return this;
       
   246     },
       
   247 
       
   248     // -- Protected Lifecycle Methods ------------------------------------------
       
   249 
       
   250     /**
       
   251     Attaches event listeners and behaviors.
       
   252 
       
   253     @method _bindUIACBase
       
   254     @protected
       
   255     **/
       
   256     _bindUIACBase: function () {
       
   257         var inputNode  = this.get(INPUT_NODE),
       
   258             tokenInput = inputNode && inputNode.tokenInput;
       
   259 
       
   260         // If the inputNode has a node-tokeninput plugin attached, bind to the
       
   261         // plugin's inputNode instead.
       
   262         if (tokenInput) {
       
   263             inputNode = tokenInput.get(INPUT_NODE);
       
   264             this._set('tokenInput', tokenInput);
       
   265         }
       
   266 
       
   267         if (!inputNode) {
       
   268             Y.error('No inputNode specified.');
       
   269             return;
       
   270         }
       
   271 
       
   272         this._inputNode = inputNode;
       
   273 
       
   274         this._acBaseEvents = new Y.EventHandle([
       
   275             // This is the valueChange event on the inputNode, provided by the
       
   276             // event-valuechange module, not our own valueChange.
       
   277             inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
       
   278             inputNode.on('blur', this._onInputBlur, this),
       
   279 
       
   280             this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
       
   281             this.after('sourceTypeChange', this._afterSourceTypeChange),
       
   282             this.after(VALUE_CHANGE, this._afterValueChange)
       
   283         ]);
       
   284     },
       
   285 
       
   286     /**
       
   287     Synchronizes the UI state of the `inputNode`.
       
   288 
       
   289     @method _syncUIACBase
       
   290     @protected
       
   291     **/
       
   292     _syncUIACBase: function () {
       
   293         this._syncBrowserAutocomplete();
       
   294         this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
       
   295     },
       
   296 
       
   297     // -- Protected Prototype Methods ------------------------------------------
       
   298 
       
   299     /**
       
   300     Creates a DataSource-like object that simply returns the specified array as
       
   301     a response. See the `source` attribute for more details.
       
   302 
       
   303     @method _createArraySource
       
   304     @param {Array} source
       
   305     @return {Object} DataSource-like object.
       
   306     @protected
       
   307     **/
       
   308     _createArraySource: function (source) {
       
   309         var that = this;
       
   310 
       
   311         return {
       
   312             type: 'array',
       
   313             sendRequest: function (request) {
       
   314                 that[_SOURCE_SUCCESS](source.concat(), request);
       
   315             }
       
   316         };
       
   317     },
       
   318 
       
   319     /**
       
   320     Creates a DataSource-like object that passes the query to a custom-defined
       
   321     function, which is expected to call the provided callback with an array of
       
   322     results. See the `source` attribute for more details.
       
   323 
       
   324     @method _createFunctionSource
       
   325     @param {Function} source Function that accepts a query and a callback as
       
   326       parameters, and calls the callback with an array of results.
       
   327     @return {Object} DataSource-like object.
       
   328     @protected
       
   329     **/
       
   330     _createFunctionSource: function (source) {
       
   331         var that = this;
       
   332 
       
   333         return {
       
   334             type: 'function',
       
   335             sendRequest: function (request) {
       
   336                 var value;
       
   337 
       
   338                 function afterResults(results) {
       
   339                     that[_SOURCE_SUCCESS](results || [], request);
       
   340                 }
       
   341 
       
   342                 // Allow both synchronous and asynchronous functions. If we get
       
   343                 // a truthy return value, assume the function is synchronous.
       
   344                 if ((value = source(request.query, afterResults))) {
       
   345                     afterResults(value);
       
   346                 }
       
   347             }
       
   348         };
       
   349     },
       
   350 
       
   351     /**
       
   352     Creates a DataSource-like object that looks up queries as properties on the
       
   353     specified object, and returns the found value (if any) as a response. See
       
   354     the `source` attribute for more details.
       
   355 
       
   356     @method _createObjectSource
       
   357     @param {Object} source
       
   358     @return {Object} DataSource-like object.
       
   359     @protected
       
   360     **/
       
   361     _createObjectSource: function (source) {
       
   362         var that = this;
       
   363 
       
   364         return {
       
   365             type: 'object',
       
   366             sendRequest: function (request) {
       
   367                 var query = request.query;
       
   368 
       
   369                 that[_SOURCE_SUCCESS](
       
   370                     YObject.owns(source, query) ? source[query] : [],
       
   371                     request
       
   372                 );
       
   373             }
       
   374         };
       
   375     },
       
   376 
       
   377     /**
       
   378     Returns `true` if _value_ is either a function or `null`.
       
   379 
       
   380     @method _functionValidator
       
   381     @param {Function|null} value Value to validate.
       
   382     @protected
       
   383     **/
       
   384     _functionValidator: function (value) {
       
   385         return value === null || isFunction(value);
       
   386     },
       
   387 
       
   388     /**
       
   389     Faster and safer alternative to `Y.Object.getValue()`. Doesn't bother
       
   390     casting the path to an array (since we already know it's an array) and
       
   391     doesn't throw an error if a value in the middle of the object hierarchy is
       
   392     neither `undefined` nor an object.
       
   393 
       
   394     @method _getObjectValue
       
   395     @param {Object} obj
       
   396     @param {Array} path
       
   397     @return {Any} Located value, or `undefined` if the value was
       
   398         not found at the specified path.
       
   399     @protected
       
   400     **/
       
   401     _getObjectValue: function (obj, path) {
       
   402         if (!obj) {
       
   403             return;
       
   404         }
       
   405 
       
   406         for (var i = 0, len = path.length; obj && i < len; i++) {
       
   407             obj = obj[path[i]];
       
   408         }
       
   409 
       
   410         return obj;
       
   411     },
       
   412 
       
   413     /**
       
   414     Parses result responses, performs filtering and highlighting, and fires the
       
   415     `results` event.
       
   416 
       
   417     @method _parseResponse
       
   418     @param {String} query Query that generated these results.
       
   419     @param {Object} response Response containing results.
       
   420     @param {Object} data Raw response data.
       
   421     @protected
       
   422     **/
       
   423     _parseResponse: function (query, response, data) {
       
   424         var facade = {
       
   425                 data   : data,
       
   426                 query  : query,
       
   427                 results: []
       
   428             },
       
   429 
       
   430             listLocator = this.get(RESULT_LIST_LOCATOR),
       
   431             results     = [],
       
   432             unfiltered  = response && response.results,
       
   433 
       
   434             filters,
       
   435             formatted,
       
   436             formatter,
       
   437             highlighted,
       
   438             highlighter,
       
   439             i,
       
   440             len,
       
   441             maxResults,
       
   442             result,
       
   443             text,
       
   444             textLocator;
       
   445 
       
   446         if (unfiltered && listLocator) {
       
   447             unfiltered = listLocator.call(this, unfiltered);
       
   448         }
       
   449 
       
   450         if (unfiltered && unfiltered.length) {
       
   451             filters     = this.get('resultFilters');
       
   452             textLocator = this.get('resultTextLocator');
       
   453 
       
   454             // Create a lightweight result object for each result to make them
       
   455             // easier to work with. The various properties on the object
       
   456             // represent different formats of the result, and will be populated
       
   457             // as we go.
       
   458             for (i = 0, len = unfiltered.length; i < len; ++i) {
       
   459                 result = unfiltered[i];
       
   460 
       
   461                 text = textLocator ?
       
   462                         textLocator.call(this, result) :
       
   463                         result.toString();
       
   464 
       
   465                 results.push({
       
   466                     display: Escape.html(text),
       
   467                     raw    : result,
       
   468                     text   : text
       
   469                 });
       
   470             }
       
   471 
       
   472             // Run the results through all configured result filters. Each
       
   473             // filter returns an array of (potentially fewer) result objects,
       
   474             // which is then passed to the next filter, and so on.
       
   475             for (i = 0, len = filters.length; i < len; ++i) {
       
   476                 results = filters[i].call(this, query, results.concat());
       
   477 
       
   478                 if (!results) {
       
   479                     return;
       
   480                 }
       
   481 
       
   482                 if (!results.length) {
       
   483                     break;
       
   484                 }
       
   485             }
       
   486 
       
   487             if (results.length) {
       
   488                 formatter   = this.get('resultFormatter');
       
   489                 highlighter = this.get('resultHighlighter');
       
   490                 maxResults  = this.get('maxResults');
       
   491 
       
   492                 // If maxResults is set and greater than 0, limit the number of
       
   493                 // results.
       
   494                 if (maxResults && maxResults > 0 &&
       
   495                         results.length > maxResults) {
       
   496                     results.length = maxResults;
       
   497                 }
       
   498 
       
   499                 // Run the results through the configured highlighter (if any).
       
   500                 // The highlighter returns an array of highlighted strings (not
       
   501                 // an array of result objects), and these strings are then added
       
   502                 // to each result object.
       
   503                 if (highlighter) {
       
   504                     highlighted = highlighter.call(this, query,
       
   505                             results.concat());
       
   506 
       
   507                     if (!highlighted) {
       
   508                         return;
       
   509                     }
       
   510 
       
   511                     for (i = 0, len = highlighted.length; i < len; ++i) {
       
   512                         result = results[i];
       
   513                         result.highlighted = highlighted[i];
       
   514                         result.display     = result.highlighted;
       
   515                     }
       
   516                 }
       
   517 
       
   518                 // Run the results through the configured formatter (if any) to
       
   519                 // produce the final formatted results. The formatter returns an
       
   520                 // array of strings or Node instances (not an array of result
       
   521                 // objects), and these strings/Nodes are then added to each
       
   522                 // result object.
       
   523                 if (formatter) {
       
   524                     formatted = formatter.call(this, query, results.concat());
       
   525 
       
   526                     if (!formatted) {
       
   527                         return;
       
   528                     }
       
   529 
       
   530                     for (i = 0, len = formatted.length; i < len; ++i) {
       
   531                         results[i].display = formatted[i];
       
   532                     }
       
   533                 }
       
   534             }
       
   535         }
       
   536 
       
   537         facade.results = results;
       
   538         this.fire(EVT_RESULTS, facade);
       
   539     },
       
   540 
       
   541     /**
       
   542     Returns the query portion of the specified input value, or `null` if there
       
   543     is no suitable query within the input value.
       
   544 
       
   545     If a query delimiter is defined, the query will be the last delimited part
       
   546     of of the string.
       
   547 
       
   548     @method _parseValue
       
   549     @param {String} value Input value from which to extract the query.
       
   550     @return {String|null} query
       
   551     @protected
       
   552     **/
       
   553     _parseValue: function (value) {
       
   554         var delim = this.get(QUERY_DELIMITER);
       
   555 
       
   556         if (delim) {
       
   557             value = value.split(delim);
       
   558             value = value[value.length - 1];
       
   559         }
       
   560 
       
   561         return Lang.trimLeft(value);
       
   562     },
       
   563 
       
   564     /**
       
   565     Setter for the `enableCache` attribute.
       
   566 
       
   567     @method _setEnableCache
       
   568     @param {Boolean} value
       
   569     @protected
       
   570     @since 3.5.0
       
   571     **/
       
   572     _setEnableCache: function (value) {
       
   573         // When `this._cache` is an object, result sources will store cached
       
   574         // results in it. When it's falsy, they won't. This way result sources
       
   575         // don't need to get the value of the `enableCache` attribute on every
       
   576         // request, which would be sloooow.
       
   577         this._cache = value ? {} : null;
       
   578     },
       
   579 
       
   580     /**
       
   581     Setter for locator attributes.
       
   582 
       
   583     @method _setLocator
       
   584     @param {Function|String|null} locator
       
   585     @return {Function|null}
       
   586     @protected
       
   587     **/
       
   588     _setLocator: function (locator) {
       
   589         if (this[_FUNCTION_VALIDATOR](locator)) {
       
   590             return locator;
       
   591         }
       
   592 
       
   593         var that = this;
       
   594 
       
   595         locator = locator.toString().split('.');
       
   596 
       
   597         return function (result) {
       
   598             return result && that._getObjectValue(result, locator);
       
   599         };
       
   600     },
       
   601 
       
   602     /**
       
   603     Setter for the `requestTemplate` attribute.
       
   604 
       
   605     @method _setRequestTemplate
       
   606     @param {Function|String|null} template
       
   607     @return {Function|null}
       
   608     @protected
       
   609     **/
       
   610     _setRequestTemplate: function (template) {
       
   611         if (this[_FUNCTION_VALIDATOR](template)) {
       
   612             return template;
       
   613         }
       
   614 
       
   615         template = template.toString();
       
   616 
       
   617         return function (query) {
       
   618             return Lang.sub(template, {query: encodeURIComponent(query)});
       
   619         };
       
   620     },
       
   621 
       
   622     /**
       
   623     Setter for the `resultFilters` attribute.
       
   624 
       
   625     @method _setResultFilters
       
   626     @param {Array|Function|String|null} filters `null`, a filter
       
   627         function, an array of filter functions, or a string or array of strings
       
   628         representing the names of methods on `Y.AutoCompleteFilters`.
       
   629     @return {Function[]} Array of filter functions (empty if <i>filters</i> is
       
   630         `null`).
       
   631     @protected
       
   632     **/
       
   633     _setResultFilters: function (filters) {
       
   634         var acFilters, getFilterFunction;
       
   635 
       
   636         if (filters === null) {
       
   637             return [];
       
   638         }
       
   639 
       
   640         acFilters = Y.AutoCompleteFilters;
       
   641 
       
   642         getFilterFunction = function (filter) {
       
   643             if (isFunction(filter)) {
       
   644                 return filter;
       
   645             }
       
   646 
       
   647             if (isString(filter) && acFilters &&
       
   648                     isFunction(acFilters[filter])) {
       
   649                 return acFilters[filter];
       
   650             }
       
   651 
       
   652             return false;
       
   653         };
       
   654 
       
   655         if (Lang.isArray(filters)) {
       
   656             filters = YArray.map(filters, getFilterFunction);
       
   657             return YArray.every(filters, function (f) { return !!f; }) ?
       
   658                     filters : INVALID_VALUE;
       
   659         } else {
       
   660             filters = getFilterFunction(filters);
       
   661             return filters ? [filters] : INVALID_VALUE;
       
   662         }
       
   663     },
       
   664 
       
   665     /**
       
   666     Setter for the `resultHighlighter` attribute.
       
   667 
       
   668     @method _setResultHighlighter
       
   669     @param {Function|String|null} highlighter `null`, a highlighter function, or
       
   670         a string representing the name of a method on
       
   671         `Y.AutoCompleteHighlighters`.
       
   672     @return {Function|null}
       
   673     @protected
       
   674     **/
       
   675     _setResultHighlighter: function (highlighter) {
       
   676         var acHighlighters;
       
   677 
       
   678         if (this[_FUNCTION_VALIDATOR](highlighter)) {
       
   679             return highlighter;
       
   680         }
       
   681 
       
   682         acHighlighters = Y.AutoCompleteHighlighters;
       
   683 
       
   684         if (isString(highlighter) && acHighlighters &&
       
   685                 isFunction(acHighlighters[highlighter])) {
       
   686             return acHighlighters[highlighter];
       
   687         }
       
   688 
       
   689         return INVALID_VALUE;
       
   690     },
       
   691 
       
   692     /**
       
   693     Setter for the `source` attribute. Returns a DataSource or a DataSource-like
       
   694     object depending on the type of _source_ and/or the value of the
       
   695     `sourceType` attribute.
       
   696 
       
   697     @method _setSource
       
   698     @param {Any} source AutoComplete source. See the `source` attribute for
       
   699         details.
       
   700     @return {DataSource|Object}
       
   701     @protected
       
   702     **/
       
   703     _setSource: function (source) {
       
   704         var sourceType = this.get('sourceType') || Lang.type(source),
       
   705             sourceSetter;
       
   706 
       
   707         if ((source && isFunction(source.sendRequest))
       
   708                 || source === null
       
   709                 || sourceType === 'datasource') {
       
   710 
       
   711             // Quacks like a DataSource instance (or null). Make it so!
       
   712             this._rawSource = source;
       
   713             return source;
       
   714         }
       
   715 
       
   716         // See if there's a registered setter for this source type.
       
   717         if ((sourceSetter = AutoCompleteBase.SOURCE_TYPES[sourceType])) {
       
   718             this._rawSource = source;
       
   719             return Lang.isString(sourceSetter) ?
       
   720                     this[sourceSetter](source) : sourceSetter(source);
       
   721         }
       
   722 
       
   723         Y.error("Unsupported source type '" + sourceType + "'. Maybe autocomplete-sources isn't loaded?");
       
   724         return INVALID_VALUE;
       
   725     },
       
   726 
       
   727     /**
       
   728     Shared success callback for non-DataSource sources.
       
   729 
       
   730     @method _sourceSuccess
       
   731     @param {Any} data Response data.
       
   732     @param {Object} request Request object.
       
   733     @protected
       
   734     **/
       
   735     _sourceSuccess: function (data, request) {
       
   736         request.callback.success({
       
   737             data: data,
       
   738             response: {results: data}
       
   739         });
       
   740     },
       
   741 
       
   742     /**
       
   743     Synchronizes the UI state of the `allowBrowserAutocomplete` attribute.
       
   744 
       
   745     @method _syncBrowserAutocomplete
       
   746     @protected
       
   747     **/
       
   748     _syncBrowserAutocomplete: function () {
       
   749         var inputNode = this.get(INPUT_NODE);
       
   750 
       
   751         if (inputNode.get('nodeName').toLowerCase() === 'input') {
       
   752             inputNode.setAttribute('autocomplete',
       
   753                     this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
       
   754         }
       
   755     },
       
   756 
       
   757     /**
       
   758     Updates the query portion of the `value` attribute.
       
   759 
       
   760     If a query delimiter is defined, the last delimited portion of the input
       
   761     value will be replaced with the specified _value_.
       
   762 
       
   763     @method _updateValue
       
   764     @param {String} newVal New value.
       
   765     @protected
       
   766     **/
       
   767     _updateValue: function (newVal) {
       
   768         var delim = this.get(QUERY_DELIMITER),
       
   769             insertDelim,
       
   770             len,
       
   771             prevVal;
       
   772 
       
   773         newVal = Lang.trimLeft(newVal);
       
   774 
       
   775         if (delim) {
       
   776             insertDelim = trim(delim); // so we don't double up on spaces
       
   777             prevVal     = YArray.map(trim(this.get(VALUE)).split(delim), trim);
       
   778             len         = prevVal.length;
       
   779 
       
   780             if (len > 1) {
       
   781                 prevVal[len - 1] = newVal;
       
   782                 newVal = prevVal.join(insertDelim + ' ');
       
   783             }
       
   784 
       
   785             newVal = newVal + insertDelim + ' ';
       
   786         }
       
   787 
       
   788         this.set(VALUE, newVal);
       
   789     },
       
   790 
       
   791     // -- Protected Event Handlers ---------------------------------------------
       
   792 
       
   793     /**
       
   794     Updates the current `source` based on the new `sourceType` to ensure that
       
   795     the two attributes don't get out of sync when they're changed separately.
       
   796 
       
   797     @method _afterSourceTypeChange
       
   798     @param {EventFacade} e
       
   799     @protected
       
   800     **/
       
   801     _afterSourceTypeChange: function (e) {
       
   802         if (this._rawSource) {
       
   803             this.set('source', this._rawSource);
       
   804         }
       
   805     },
       
   806 
       
   807     /**
       
   808     Handles change events for the `value` attribute.
       
   809 
       
   810     @method _afterValueChange
       
   811     @param {EventFacade} e
       
   812     @protected
       
   813     **/
       
   814     _afterValueChange: function (e) {
       
   815         var newVal   = e.newVal,
       
   816             self     = this,
       
   817             uiChange = e.src === AutoCompleteBase.UI_SRC,
       
   818             delay, fire, minQueryLength, query;
       
   819 
       
   820         // Update the UI if the value was changed programmatically.
       
   821         if (!uiChange) {
       
   822             self._inputNode.set(VALUE, newVal);
       
   823         }
       
   824 
       
   825 
       
   826         minQueryLength = self.get('minQueryLength');
       
   827         query          = self._parseValue(newVal) || '';
       
   828 
       
   829         if (minQueryLength >= 0 && query.length >= minQueryLength) {
       
   830             // Only query on changes that originate from the UI.
       
   831             if (uiChange) {
       
   832                 delay = self.get('queryDelay');
       
   833 
       
   834                 fire = function () {
       
   835                     self.fire(EVT_QUERY, {
       
   836                         inputValue: newVal,
       
   837                         query     : query,
       
   838                         src       : e.src
       
   839                     });
       
   840                 };
       
   841 
       
   842                 if (delay) {
       
   843                     clearTimeout(self._delay);
       
   844                     self._delay = setTimeout(fire, delay);
       
   845                 } else {
       
   846                     fire();
       
   847                 }
       
   848             } else {
       
   849                 // For programmatic value changes, just update the query
       
   850                 // attribute without sending a query.
       
   851                 self._set(QUERY, query);
       
   852             }
       
   853         } else {
       
   854             clearTimeout(self._delay);
       
   855 
       
   856             self.fire(EVT_CLEAR, {
       
   857                 prevVal: e.prevVal ? self._parseValue(e.prevVal) : null,
       
   858                 src    : e.src
       
   859             });
       
   860         }
       
   861     },
       
   862 
       
   863     /**
       
   864     Handles `blur` events on the input node.
       
   865 
       
   866     @method _onInputBlur
       
   867     @param {EventFacade} e
       
   868     @protected
       
   869     **/
       
   870     _onInputBlur: function (e) {
       
   871         var delim = this.get(QUERY_DELIMITER),
       
   872             delimPos,
       
   873             newVal,
       
   874             value;
       
   875 
       
   876         // If a query delimiter is set and the input's value contains one or
       
   877         // more trailing delimiters, strip them.
       
   878         if (delim && !this.get('allowTrailingDelimiter')) {
       
   879             delim = Lang.trimRight(delim);
       
   880             value = newVal = this._inputNode.get(VALUE);
       
   881 
       
   882             if (delim) {
       
   883                 while ((newVal = Lang.trimRight(newVal)) &&
       
   884                         (delimPos = newVal.length - delim.length) &&
       
   885                         newVal.lastIndexOf(delim) === delimPos) {
       
   886 
       
   887                     newVal = newVal.substring(0, delimPos);
       
   888                 }
       
   889             } else {
       
   890                 // Delimiter is one or more space characters, so just trim the
       
   891                 // value.
       
   892                 newVal = Lang.trimRight(newVal);
       
   893             }
       
   894 
       
   895             if (newVal !== value) {
       
   896                 this.set(VALUE, newVal);
       
   897             }
       
   898         }
       
   899     },
       
   900 
       
   901     /**
       
   902     Handles `valueChange` events on the input node and fires a `query` event
       
   903     when the input value meets the configured criteria.
       
   904 
       
   905     @method _onInputValueChange
       
   906     @param {EventFacade} e
       
   907     @protected
       
   908     **/
       
   909     _onInputValueChange: function (e) {
       
   910         var newVal = e.newVal;
       
   911 
       
   912         // Don't query if the internal value is the same as the new value
       
   913         // reported by valueChange.
       
   914         if (newVal !== this.get(VALUE)) {
       
   915             this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
       
   916         }
       
   917     },
       
   918 
       
   919     /**
       
   920     Handles source responses and fires the `results` event.
       
   921 
       
   922     @method _onResponse
       
   923     @param {EventFacade} e
       
   924     @protected
       
   925     **/
       
   926     _onResponse: function (query, e) {
       
   927         // Ignore stale responses that aren't for the current query.
       
   928         if (query === (this.get(QUERY) || '')) {
       
   929             this._parseResponse(query || '', e.response, e.data);
       
   930         }
       
   931     },
       
   932 
       
   933     // -- Protected Default Event Handlers -------------------------------------
       
   934 
       
   935     /**
       
   936     Default `clear` event handler. Sets the `results` attribute to an empty
       
   937     array and `query` to null.
       
   938 
       
   939     @method _defClearFn
       
   940     @protected
       
   941     **/
       
   942     _defClearFn: function () {
       
   943         this._set(QUERY, null);
       
   944         this._set(RESULTS, []);
       
   945     },
       
   946 
       
   947     /**
       
   948     Default `query` event handler. Sets the `query` attribute and sends a
       
   949     request to the source if one is configured.
       
   950 
       
   951     @method _defQueryFn
       
   952     @param {EventFacade} e
       
   953     @protected
       
   954     **/
       
   955     _defQueryFn: function (e) {
       
   956         this.sendRequest(e.query); // sendRequest will set the 'query' attribute
       
   957     },
       
   958 
       
   959     /**
       
   960     Default `results` event handler. Sets the `results` attribute to the latest
       
   961     results.
       
   962 
       
   963     @method _defResultsFn
       
   964     @param {EventFacade} e
       
   965     @protected
       
   966     **/
       
   967     _defResultsFn: function (e) {
       
   968         this._set(RESULTS, e[RESULTS]);
       
   969     }
       
   970 };
       
   971 
       
   972 AutoCompleteBase.ATTRS = {
       
   973     /**
       
   974     Whether or not to enable the browser's built-in autocomplete functionality
       
   975     for input fields.
       
   976 
       
   977     @attribute allowBrowserAutocomplete
       
   978     @type Boolean
       
   979     @default false
       
   980     **/
       
   981     allowBrowserAutocomplete: {
       
   982         value: false
       
   983     },
       
   984 
       
   985     /**
       
   986     When a `queryDelimiter` is set, trailing delimiters will automatically be
       
   987     stripped from the input value by default when the input node loses focus.
       
   988     Set this to `true` to allow trailing delimiters.
       
   989 
       
   990     @attribute allowTrailingDelimiter
       
   991     @type Boolean
       
   992     @default false
       
   993     **/
       
   994     allowTrailingDelimiter: {
       
   995         value: false
       
   996     },
       
   997 
       
   998     /**
       
   999     Whether or not to enable in-memory caching in result sources that support
       
  1000     it.
       
  1001 
       
  1002     @attribute enableCache
       
  1003     @type Boolean
       
  1004     @default true
       
  1005     @since 3.5.0
       
  1006     **/
       
  1007     enableCache: {
       
  1008         lazyAdd: false, // we need the setter to run on init
       
  1009         setter: '_setEnableCache',
       
  1010         value: true
       
  1011     },
       
  1012 
       
  1013     /**
       
  1014     Node to monitor for changes, which will generate `query` events when
       
  1015     appropriate. May be either an `<input>` or a `<textarea>`.
       
  1016 
       
  1017     @attribute inputNode
       
  1018     @type Node|HTMLElement|String
       
  1019     @initOnly
       
  1020     **/
       
  1021     inputNode: {
       
  1022         setter: Y.one,
       
  1023         writeOnce: 'initOnly'
       
  1024     },
       
  1025 
       
  1026     /**
       
  1027     Maximum number of results to return. A value of `0` or less will allow an
       
  1028     unlimited number of results.
       
  1029 
       
  1030     @attribute maxResults
       
  1031     @type Number
       
  1032     @default 0
       
  1033     **/
       
  1034     maxResults: {
       
  1035         value: 0
       
  1036     },
       
  1037 
       
  1038     /**
       
  1039     Minimum number of characters that must be entered before a `query` event
       
  1040     will be fired. A value of `0` allows empty queries; a negative value will
       
  1041     effectively disable all `query` events.
       
  1042 
       
  1043     @attribute minQueryLength
       
  1044     @type Number
       
  1045     @default 1
       
  1046     **/
       
  1047     minQueryLength: {
       
  1048         value: 1
       
  1049     },
       
  1050 
       
  1051     /**
       
  1052     Current query, or `null` if there is no current query.
       
  1053 
       
  1054     The query might not be the same as the current value of the input node, both
       
  1055     for timing reasons (due to `queryDelay`) and because when one or more
       
  1056     `queryDelimiter` separators are in use, only the last portion of the
       
  1057     delimited input string will be used as the query value.
       
  1058 
       
  1059     @attribute query
       
  1060     @type String|null
       
  1061     @default null
       
  1062     @readonly
       
  1063     **/
       
  1064     query: {
       
  1065         readOnly: true,
       
  1066         value: null
       
  1067     },
       
  1068 
       
  1069     /**
       
  1070     Number of milliseconds to delay after input before triggering a `query`
       
  1071     event. If new input occurs before this delay is over, the previous input
       
  1072     event will be ignored and a new delay will begin.
       
  1073 
       
  1074     This can be useful both to throttle queries to a remote data source and to
       
  1075     avoid distracting the user by showing them less relevant results before
       
  1076     they've paused their typing.
       
  1077 
       
  1078     @attribute queryDelay
       
  1079     @type Number
       
  1080     @default 100
       
  1081     **/
       
  1082     queryDelay: {
       
  1083         value: 100
       
  1084     },
       
  1085 
       
  1086     /**
       
  1087     Query delimiter string. When a delimiter is configured, the input value
       
  1088     will be split on the delimiter, and only the last portion will be used in
       
  1089     autocomplete queries and updated when the `query` attribute is
       
  1090     modified.
       
  1091 
       
  1092     @attribute queryDelimiter
       
  1093     @type String|null
       
  1094     @default null
       
  1095     **/
       
  1096     queryDelimiter: {
       
  1097         value: null
       
  1098     },
       
  1099 
       
  1100     /**
       
  1101     Source request template. This can be a function that accepts a query as a
       
  1102     parameter and returns a request string, or it can be a string containing the
       
  1103     placeholder "{query}", which will be replaced with the actual URI-encoded
       
  1104     query. In either case, the resulting string will be appended to the request
       
  1105     URL when the `source` attribute is set to a remote DataSource, JSONP URL, or
       
  1106     XHR URL (it will not be appended to YQL URLs).
       
  1107 
       
  1108     While `requestTemplate` may be set to either a function or a string, it will
       
  1109     always be returned as a function that accepts a query argument and returns a
       
  1110     string.
       
  1111 
       
  1112     @attribute requestTemplate
       
  1113     @type Function|String|null
       
  1114     @default null
       
  1115     **/
       
  1116     requestTemplate: {
       
  1117         setter: '_setRequestTemplate',
       
  1118         value: null
       
  1119     },
       
  1120 
       
  1121     /**
       
  1122     Array of local result filter functions. If provided, each filter will be
       
  1123     called with two arguments when results are received: the query and an array
       
  1124     of result objects. See the documentation for the `results` event for a list
       
  1125     of the properties available on each result object.
       
  1126 
       
  1127     Each filter is expected to return a filtered or modified version of the
       
  1128     results array, which will then be passed on to subsequent filters, then the
       
  1129     `resultHighlighter` function (if set), then the `resultFormatter` function
       
  1130     (if set), and finally to subscribers to the `results` event.
       
  1131 
       
  1132     If no `source` is set, result filters will not be called.
       
  1133 
       
  1134     Prepackaged result filters provided by the autocomplete-filters and
       
  1135     autocomplete-filters-accentfold modules can be used by specifying the filter
       
  1136     name as a string, such as `'phraseMatch'` (assuming the necessary filters
       
  1137     module is loaded).
       
  1138 
       
  1139     @attribute resultFilters
       
  1140     @type Array
       
  1141     @default []
       
  1142     **/
       
  1143     resultFilters: {
       
  1144         setter: '_setResultFilters',
       
  1145         value: []
       
  1146     },
       
  1147 
       
  1148     /**
       
  1149     Function which will be used to format results. If provided, this function
       
  1150     will be called with two arguments after results have been received and
       
  1151     filtered: the query and an array of result objects. The formatter is
       
  1152     expected to return an array of HTML strings or Node instances containing the
       
  1153     desired HTML for each result.
       
  1154 
       
  1155     See the documentation for the `results` event for a list of the properties
       
  1156     available on each result object.
       
  1157 
       
  1158     If no `source` is set, the formatter will not be called.
       
  1159 
       
  1160     @attribute resultFormatter
       
  1161     @type Function|null
       
  1162     **/
       
  1163     resultFormatter: {
       
  1164         validator: _FUNCTION_VALIDATOR,
       
  1165         value: null
       
  1166     },
       
  1167 
       
  1168     /**
       
  1169     Function which will be used to highlight results. If provided, this function
       
  1170     will be called with two arguments after results have been received and
       
  1171     filtered: the query and an array of filtered result objects. The highlighter
       
  1172     is expected to return an array of highlighted result text in the form of
       
  1173     HTML strings.
       
  1174 
       
  1175     See the documentation for the `results` event for a list of the properties
       
  1176     available on each result object.
       
  1177 
       
  1178     If no `source` is set, the highlighter will not be called.
       
  1179 
       
  1180     @attribute resultHighlighter
       
  1181     @type Function|null
       
  1182     **/
       
  1183     resultHighlighter: {
       
  1184         setter: '_setResultHighlighter',
       
  1185         value: null
       
  1186     },
       
  1187 
       
  1188     /**
       
  1189     Locator that should be used to extract an array of results from a non-array
       
  1190     response.
       
  1191 
       
  1192     By default, no locator is applied, and all responses are assumed to be
       
  1193     arrays by default. If all responses are already arrays, you don't need to
       
  1194     define a locator.
       
  1195 
       
  1196     The locator may be either a function (which will receive the raw response as
       
  1197     an argument and must return an array) or a string representing an object
       
  1198     path, such as "foo.bar.baz" (which would return the value of
       
  1199     `result.foo.bar.baz` if the response is an object).
       
  1200 
       
  1201     While `resultListLocator` may be set to either a function or a string, it
       
  1202     will always be returned as a function that accepts a response argument and
       
  1203     returns an array.
       
  1204 
       
  1205     @attribute resultListLocator
       
  1206     @type Function|String|null
       
  1207     **/
       
  1208     resultListLocator: {
       
  1209         setter: '_setLocator',
       
  1210         value: null
       
  1211     },
       
  1212 
       
  1213     /**
       
  1214     Current results, or an empty array if there are no results.
       
  1215 
       
  1216     @attribute results
       
  1217     @type Array
       
  1218     @default []
       
  1219     @readonly
       
  1220     **/
       
  1221     results: {
       
  1222         readOnly: true,
       
  1223         value: []
       
  1224     },
       
  1225 
       
  1226     /**
       
  1227     Locator that should be used to extract a plain text string from a non-string
       
  1228     result item. The resulting text value will typically be the value that ends
       
  1229     up being inserted into an input field or textarea when the user of an
       
  1230     autocomplete implementation selects a result.
       
  1231 
       
  1232     By default, no locator is applied, and all results are assumed to be plain
       
  1233     text strings. If all results are already plain text strings, you don't need
       
  1234     to define a locator.
       
  1235 
       
  1236     The locator may be either a function (which will receive the raw result as
       
  1237     an argument and must return a string) or a string representing an object
       
  1238     path, such as "foo.bar.baz" (which would return the value of
       
  1239     `result.foo.bar.baz` if the result is an object).
       
  1240 
       
  1241     While `resultTextLocator` may be set to either a function or a string, it
       
  1242     will always be returned as a function that accepts a result argument and
       
  1243     returns a string.
       
  1244 
       
  1245     @attribute resultTextLocator
       
  1246     @type Function|String|null
       
  1247     **/
       
  1248     resultTextLocator: {
       
  1249         setter: '_setLocator',
       
  1250         value: null
       
  1251     },
       
  1252 
       
  1253     /**
       
  1254     Source for autocomplete results. The following source types are supported:
       
  1255 
       
  1256     <dl>
       
  1257       <dt>Array</dt>
       
  1258       <dd>
       
  1259         <p>
       
  1260         The full array will be provided to any configured filters for each
       
  1261         query. This is an easy way to create a fully client-side autocomplete
       
  1262         implementation.
       
  1263         </p>
       
  1264 
       
  1265         <p>
       
  1266         Example: `['first result', 'second result', 'etc']`
       
  1267         </p>
       
  1268       </dd>
       
  1269 
       
  1270       <dt>DataSource</dt>
       
  1271       <dd>
       
  1272         A `DataSource` instance or other object that provides a DataSource-like
       
  1273         `sendRequest` method. See the `DataSource` documentation for details.
       
  1274       </dd>
       
  1275 
       
  1276       <dt>Function</dt>
       
  1277       <dd>
       
  1278         <p>
       
  1279         A function source will be called with the current query and a
       
  1280         callback function as parameters, and should either return an array of
       
  1281         results (for synchronous operation) or return nothing and pass an
       
  1282         array of results to the provided callback (for asynchronous
       
  1283         operation).
       
  1284         </p>
       
  1285 
       
  1286         <p>
       
  1287         Example (synchronous):
       
  1288         </p>
       
  1289 
       
  1290         <pre>
       
  1291         function (query) {
       
  1292             return ['foo', 'bar'];
       
  1293         }
       
  1294         </pre>
       
  1295 
       
  1296         <p>
       
  1297         Example (async):
       
  1298         </p>
       
  1299 
       
  1300         <pre>
       
  1301         function (query, callback) {
       
  1302             callback(['foo', 'bar']);
       
  1303         }
       
  1304         </pre>
       
  1305       </dd>
       
  1306 
       
  1307       <dt>Object</dt>
       
  1308       <dd>
       
  1309         <p>
       
  1310         An object will be treated as a query hashmap. If a property on the
       
  1311         object matches the current query, the value of that property will be
       
  1312         used as the response.
       
  1313         </p>
       
  1314 
       
  1315         <p>
       
  1316         The response is assumed to be an array of results by default. If the
       
  1317         response is not an array, provide a `resultListLocator` to
       
  1318         process the response and return an array.
       
  1319         </p>
       
  1320 
       
  1321         <p>
       
  1322         Example: `{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}`
       
  1323         </p>
       
  1324       </dd>
       
  1325     </dl>
       
  1326 
       
  1327     If the optional `autocomplete-sources` module is loaded, then
       
  1328     the following additional source types will be supported as well:
       
  1329 
       
  1330     <dl>
       
  1331       <dt>&lt;select&gt; Node</dt>
       
  1332       <dd>
       
  1333         You may provide a YUI Node instance wrapping a &lt;select&gt;
       
  1334         element, and the options in the list will be used as results. You
       
  1335         will also need to specify a `resultTextLocator` of 'text'
       
  1336         or 'value', depending on what you want to use as the text of the
       
  1337         result.
       
  1338 
       
  1339         Each result will be an object with the following properties:
       
  1340 
       
  1341         <dl>
       
  1342           <dt>html (String)</dt>
       
  1343           <dd>
       
  1344             <p>HTML content of the &lt;option&gt; element.</p>
       
  1345           </dd>
       
  1346 
       
  1347           <dt>index (Number)</dt>
       
  1348           <dd>
       
  1349             <p>Index of the &lt;option&gt; element in the list.</p>
       
  1350           </dd>
       
  1351 
       
  1352           <dt>node (Y.Node)</dt>
       
  1353           <dd>
       
  1354             <p>Node instance referring to the original &lt;option&gt; element.</p>
       
  1355           </dd>
       
  1356 
       
  1357           <dt>selected (Boolean)</dt>
       
  1358           <dd>
       
  1359             <p>Whether or not this item is currently selected in the
       
  1360             &lt;select&gt; list.</p>
       
  1361           </dd>
       
  1362 
       
  1363           <dt>text (String)</dt>
       
  1364           <dd>
       
  1365             <p>Text content of the &lt;option&gt; element.</p>
       
  1366           </dd>
       
  1367 
       
  1368           <dt>value (String)</dt>
       
  1369           <dd>
       
  1370             <p>Value of the &lt;option&gt; element.</p>
       
  1371           </dd>
       
  1372         </dl>
       
  1373       </dd>
       
  1374 
       
  1375       <dt>String (JSONP URL)</dt>
       
  1376       <dd>
       
  1377         <p>
       
  1378         If a URL with a `{callback}` placeholder is provided, it will be used to
       
  1379         make a JSONP request. The `{query}` placeholder will be replaced with
       
  1380         the current query, and the `{callback}` placeholder will be replaced
       
  1381         with an internally-generated JSONP callback name. Both placeholders must
       
  1382         appear in the URL, or the request will fail. An optional `{maxResults}`
       
  1383         placeholder may also be provided, and will be replaced with the value of
       
  1384         the maxResults attribute (or 1000 if the maxResults attribute is 0 or
       
  1385         less).
       
  1386         </p>
       
  1387 
       
  1388         <p>
       
  1389         The response is assumed to be an array of results by default. If the
       
  1390         response is not an array, provide a `resultListLocator` to process the
       
  1391         response and return an array.
       
  1392         </p>
       
  1393 
       
  1394         <p>
       
  1395         <strong>The `jsonp` module must be loaded in order for
       
  1396         JSONP URL sources to work.</strong> If the `jsonp` module
       
  1397         is not already loaded, it will be loaded on demand if possible.
       
  1398         </p>
       
  1399 
       
  1400         <p>
       
  1401         Example: `'http://example.com/search?q={query}&callback={callback}'`
       
  1402         </p>
       
  1403       </dd>
       
  1404 
       
  1405       <dt>String (XHR URL)</dt>
       
  1406       <dd>
       
  1407         <p>
       
  1408         If a URL without a `{callback}` placeholder is provided, it will be used
       
  1409         to make a same-origin XHR request. The `{query}` placeholder will be
       
  1410         replaced with the current query. An optional `{maxResults}` placeholder
       
  1411         may also be provided, and will be replaced with the value of the
       
  1412         maxResults attribute (or 1000 if the maxResults attribute is 0 or less).
       
  1413         </p>
       
  1414 
       
  1415         <p>
       
  1416         The response is assumed to be a JSON array of results by default. If the
       
  1417         response is a JSON object and not an array, provide a
       
  1418         `resultListLocator` to process the response and return an array. If the
       
  1419         response is in some form other than JSON, you will need to use a custom
       
  1420         DataSource instance as the source.
       
  1421         </p>
       
  1422 
       
  1423         <p>
       
  1424         <strong>The `io-base` and `json-parse` modules
       
  1425         must be loaded in order for XHR URL sources to work.</strong> If
       
  1426         these modules are not already loaded, they will be loaded on demand
       
  1427         if possible.
       
  1428         </p>
       
  1429 
       
  1430         <p>
       
  1431         Example: `'http://example.com/search?q={query}'`
       
  1432         </p>
       
  1433       </dd>
       
  1434 
       
  1435       <dt>String (YQL query)</dt>
       
  1436       <dd>
       
  1437         <p>
       
  1438         If a YQL query is provided, it will be used to make a YQL request. The
       
  1439         `{query}` placeholder will be replaced with the current autocomplete
       
  1440         query. This placeholder must appear in the YQL query, or the request
       
  1441         will fail. An optional `{maxResults}` placeholder may also be provided,
       
  1442         and will be replaced with the value of the maxResults attribute (or 1000
       
  1443         if the maxResults attribute is 0 or less).
       
  1444         </p>
       
  1445 
       
  1446         <p>
       
  1447         <strong>The `yql` module must be loaded in order for YQL
       
  1448         sources to work.</strong> If the `yql` module is not
       
  1449         already loaded, it will be loaded on demand if possible.
       
  1450         </p>
       
  1451 
       
  1452         <p>
       
  1453         Example: `'select * from search.suggest where query="{query}"'`
       
  1454         </p>
       
  1455       </dd>
       
  1456     </dl>
       
  1457 
       
  1458     As an alternative to providing a source, you could simply listen for `query`
       
  1459     events and handle them any way you see fit. Providing a source is optional,
       
  1460     but will usually be simpler.
       
  1461 
       
  1462     @attribute source
       
  1463     @type Array|DataSource|Function|Node|Object|String|null
       
  1464     **/
       
  1465     source: {
       
  1466         setter: '_setSource',
       
  1467         value: null
       
  1468     },
       
  1469 
       
  1470     /**
       
  1471     May be used to force a specific source type, overriding the automatic source
       
  1472     type detection. It should almost never be necessary to do this, but as they
       
  1473     taught us in the Boy Scouts, one should always be prepared, so it's here if
       
  1474     you need it. Be warned that if you set this attribute and something breaks,
       
  1475     it's your own fault.
       
  1476 
       
  1477     Supported `sourceType` values are: 'array', 'datasource', 'function', and
       
  1478     'object'.
       
  1479 
       
  1480     If the `autocomplete-sources` module is loaded, the following additional
       
  1481     source types are supported: 'io', 'jsonp', 'select', 'string', 'yql'
       
  1482 
       
  1483     @attribute sourceType
       
  1484     @type String
       
  1485     **/
       
  1486     sourceType: {
       
  1487         value: null
       
  1488     },
       
  1489 
       
  1490     /**
       
  1491     If the `inputNode` specified at instantiation time has a `node-tokeninput`
       
  1492     plugin attached to it, this attribute will be a reference to the
       
  1493     `Y.Plugin.TokenInput` instance.
       
  1494 
       
  1495     @attribute tokenInput
       
  1496     @type Plugin.TokenInput
       
  1497     @readonly
       
  1498     **/
       
  1499     tokenInput: {
       
  1500         readOnly: true
       
  1501     },
       
  1502 
       
  1503     /**
       
  1504     Current value of the input node.
       
  1505 
       
  1506     @attribute value
       
  1507     @type String
       
  1508     @default ''
       
  1509     **/
       
  1510     value: {
       
  1511         // Why duplicate this._inputNode.get('value')? Because we need a
       
  1512         // reliable way to track the source of value changes. We want to perform
       
  1513         // completion when the user changes the value, but not when we change
       
  1514         // the value.
       
  1515         value: ''
       
  1516     }
       
  1517 };
       
  1518 
       
  1519 // This tells Y.Base.create() to copy these static properties to any class
       
  1520 // AutoCompleteBase is mixed into.
       
  1521 AutoCompleteBase._buildCfg = {
       
  1522     aggregates: ['SOURCE_TYPES'],
       
  1523     statics   : ['UI_SRC']
       
  1524 };
       
  1525 
       
  1526 /**
       
  1527 Mapping of built-in source types to their setter functions. DataSource instances
       
  1528 and DataSource-like objects are handled natively, so are not mapped here.
       
  1529 
       
  1530 @property SOURCE_TYPES
       
  1531 @type {Object}
       
  1532 @static
       
  1533 **/
       
  1534 AutoCompleteBase.SOURCE_TYPES = {
       
  1535     array     : '_createArraySource',
       
  1536     'function': '_createFunctionSource',
       
  1537     object    : '_createObjectSource'
       
  1538 };
       
  1539 
       
  1540 AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
       
  1541 
       
  1542 Y.AutoCompleteBase = AutoCompleteBase;
       
  1543 
       
  1544 
       
  1545 }, '3.10.3', {
       
  1546     "optional": [
       
  1547         "autocomplete-sources"
       
  1548     ],
       
  1549     "requires": [
       
  1550         "array-extras",
       
  1551         "base-build",
       
  1552         "escape",
       
  1553         "event-valuechange",
       
  1554         "node-base"
       
  1555     ]
       
  1556 });