text: YUI.add('autocomplete-base', function(Y) {
  
  /**
   * Provides automatic input completion or suggestions for text input fields and
   * textareas.
   *
   * @module autocomplete
   * @main autocomplete
   * @since 3.3.0
   */
  
  /**
   * <code>Y.Base</code> extension that provides core autocomplete logic (but no
   * UI implementation) for a text input field or textarea. Must be mixed into a
   * <code>Y.Base</code>-derived class to be useful.
   *
   * @submodule autocomplete-base
   */
  
  /**
   * <p>
   * Extension that provides core autocomplete logic (but no UI implementation)
   * for a text input field or textarea.
   * </p>
   *
   * <p>
   * The <code>AutoCompleteBase</code> class provides events and attributes that
   * abstract away core autocomplete logic and configuration, but does not provide
   * a widget implementation or suggestion UI. For a prepackaged autocomplete
   * widget, see <code>AutoCompleteList</code>.
   * </p>
   *
   * <p>
   * This extension cannot be instantiated directly, since it doesn't provide an
   * actual implementation. It's intended to be mixed into a
   * <code>Y.Base</code>-based class or widget.
   * </p>
   *
   * <p>
   * <code>Y.Widget</code>-based example:
   * </p>
   *
   * <pre>
   * YUI().use('autocomplete-base', 'widget', function (Y) {
   * &nbsp;&nbsp;var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
   * &nbsp;&nbsp;&nbsp;&nbsp;// Custom prototype methods and properties.
   * &nbsp;&nbsp;}, {
   * &nbsp;&nbsp;&nbsp;&nbsp;// Custom static methods and properties.
   * &nbsp;&nbsp;});
   * &nbsp;
   * &nbsp;&nbsp;// Custom implementation code.
   * });
   * </pre>
   *
   * <p>
   * <code>Y.Base</code>-based example:
   * </p>
   *
   * <pre>
   * YUI().use('autocomplete-base', function (Y) {
   * &nbsp;&nbsp;var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
   * &nbsp;&nbsp;&nbsp;&nbsp;initializer: function () {
   * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._bindUIACBase();
   * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._syncUIACBase();
   * &nbsp;&nbsp;&nbsp;&nbsp;},
   * &nbsp;
   * &nbsp;&nbsp;&nbsp;&nbsp;// Custom prototype methods and properties.
   * &nbsp;&nbsp;}, {
   * &nbsp;&nbsp;&nbsp;&nbsp;// Custom static methods and properties.
   * &nbsp;&nbsp;});
   * &nbsp;
   * &nbsp;&nbsp;// Custom implementation code.
   * });
   * </pre>
   *
   * @class AutoCompleteBase
   */
  
  var Escape  = Y.Escape,
      Lang    = Y.Lang,
      YArray  = Y.Array,
      YObject = Y.Object,
  
      isFunction = Lang.isFunction,
      isString   = Lang.isString,
      trim       = Lang.trim,
  
      INVALID_VALUE = Y.Attribute.INVALID_VALUE,
  
      _FUNCTION_VALIDATOR = '_functionValidator',
      _SOURCE_SUCCESS     = '_sourceSuccess',
  
      ALLOW_BROWSER_AC    = 'allowBrowserAutocomplete',
      INPUT_NODE          = 'inputNode',
      QUERY               = 'query',
      QUERY_DELIMITER     = 'queryDelimiter',
      REQUEST_TEMPLATE    = 'requestTemplate',
      RESULTS             = 'results',
      RESULT_LIST_LOCATOR = 'resultListLocator',
      VALUE               = 'value',
      VALUE_CHANGE        = 'valueChange',
  
      EVT_CLEAR   = 'clear',
      EVT_QUERY   = QUERY,
      EVT_RESULTS = RESULTS;
  
  function AutoCompleteBase() {
      // AOP bindings.
      Y.before(this._bindUIACBase, this, 'bindUI');
      Y.before(this._destructorACBase, this, 'destructor');
      Y.before(this._syncUIACBase, this, 'syncUI');
  
      // -- Public Events --------------------------------------------------------
  
      /**
       * Fires after the query has been completely cleared or no longer meets the
       * minimum query length requirement.
       *
       * @event clear
       * @param {EventFacade} e Event facade with the following additional
       *   properties:
       *
       * <dl>
       *   <dt>prevVal (String)</dt>
       *   <dd>
       *     Value of the query before it was cleared.
       *   </dd>
       *
       *   <dt>src (String)</dt>
       *   <dd>
       *     Source of the event.
       *   </dd>
       * </dl>
       *
       * @preventable _defClearFn
       */
      this.publish(EVT_CLEAR, {
          defaultFn: this._defClearFn
      });
  
      /**
       * Fires when the contents of the input field have changed and the input
       * value meets the criteria necessary to generate an autocomplete query.
       *
       * @event query
       * @param {EventFacade} e Event facade with the following additional
       *   properties:
       *
       * <dl>
       *   <dt>inputValue (String)</dt>
       *   <dd>
       *     Full contents of the text input field or textarea that generated
       *     the query.
       *   </dd>
       *
       *   <dt>query (String)</dt>
       *   <dd>
       *     Autocomplete query. This is the string that will be used to
       *     request completion results. It may or may not be the same as
       *     <code>inputValue</code>.
       *   </dd>
       *
       *   <dt>src (String)</dt>
       *   <dd>
       *     Source of the event.
       *   </dd>
       * </dl>
       *
       * @preventable _defQueryFn
       */
      this.publish(EVT_QUERY, {
          defaultFn: this._defQueryFn
      });
  
      /**
       * Fires after query results are received from the <code>source</code>. If
       * no source has been set, this event will not fire.
       *
       * @event results
       * @param {EventFacade} e Event facade with the following additional
       *   properties:
       *
       * <dl>
       *   <dt>data (Array|Object)</dt>
       *   <dd>
       *     Raw, unfiltered result data (if available).
       *   </dd>
       *
       *   <dt>query (String)</dt>
       *   <dd>
       *     Query that generated these results.
       *   </dd>
       *
       *   <dt>results (Array)</dt>
       *   <dd>
       *     Array of filtered, formatted, and highlighted results. Each item in
       *     the array is an object with the following properties:
       *
       *     <dl>
       *       <dt>display (Node|HTMLElement|String)</dt>
       *       <dd>
       *         Formatted result HTML suitable for display to the user. If no
       *         custom formatter is set, this will be an HTML-escaped version of
       *         the string in the <code>text</code> property.
       *       </dd>
       *
       *       <dt>highlighted (String)</dt>
       *       <dd>
       *         Highlighted (but not formatted) result text. This property will
       *         only be set if a highlighter is in use.
       *       </dd>
       *
       *       <dt>raw (mixed)</dt>
       *       <dd>
       *         Raw, unformatted result in whatever form it was provided by the
       *         <code>source</code>.
       *       </dd>
       *
       *       <dt>text (String)</dt>
       *       <dd>
       *         Plain text version of the result, suitable for being inserted
       *         into the value of a text input field or textarea when the result
       *         is selected by a user. This value is not HTML-escaped and should
       *         not be inserted into the page using innerHTML.
       *       </dd>
       *     </dl>
       *   </dd>
       * </dl>
       *
       * @preventable _defResultsFn
       */
      this.publish(EVT_RESULTS, {
          defaultFn: this._defResultsFn
      });
  }
  
  // -- Public Static Properties -------------------------------------------------
  AutoCompleteBase.ATTRS = {
      /**
       * Whether or not to enable the browser's built-in autocomplete
       * functionality for input fields.
       *
       * @attribute allowBrowserAutocomplete
       * @type Boolean
       * @default false
       */
      allowBrowserAutocomplete: {
          value: false
      },
  
      /**
       * When a <code>queryDelimiter</code> is set, trailing delimiters will
       * automatically be stripped from the input value by default when the
       * input node loses focus. Set this to <code>true</code> to allow trailing
       * delimiters.
       *
       * @attribute allowTrailingDelimiter
       * @type Boolean
       * @default false
       */
      allowTrailingDelimiter: {
          value: false
      },
  
      /**
       * Whether or not to enable in-memory caching in result sources that support
       * it.
       *
       * @attribute enableCache
       * @type Boolean
       * @default true
       * @since 3.5.0
       */
      enableCache: {
          lazyAdd: false, // we need the setter to run on init
          setter: '_setEnableCache',
          value: true
      },
  
      /**
       * Node to monitor for changes, which will generate <code>query</code>
       * events when appropriate. May be either an input field or a textarea.
       *
       * @attribute inputNode
       * @type Node|HTMLElement|String
       * @writeonce
       */
      inputNode: {
          setter: Y.one,
          writeOnce: 'initOnly'
      },
  
      /**
       * Maximum number of results to return. A value of <code>0</code> or less
       * will allow an unlimited number of results.
       *
       * @attribute maxResults
       * @type Number
       * @default 0
       */
      maxResults: {
          value: 0
      },
  
      /**
       * Minimum number of characters that must be entered before a
       * <code>query</code> event will be fired. A value of <code>0</code>
       * allows empty queries; a negative value will effectively disable all
       * <code>query</code> events.
       *
       * @attribute minQueryLength
       * @type Number
       * @default 1
       */
      minQueryLength: {
          value: 1
      },
  
      /**
       * <p>
       * Current query, or <code>null</code> if there is no current query.
       * </p>
       *
       * <p>
       * The query might not be the same as the current value of the input
       * node, both for timing reasons (due to <code>queryDelay</code>) and
       * because when one or more <code>queryDelimiter</code> separators are
       * in use, only the last portion of the delimited input string will be
       * used as the query value.
       * </p>
       *
       * @attribute query
       * @type String|null
       * @default null
       * @readonly
       */
      query: {
          readOnly: true,
          value: null
      },
  
      /**
       * <p>
       * Number of milliseconds to delay after input before triggering a
       * <code>query</code> event. If new input occurs before this delay is
       * over, the previous input event will be ignored and a new delay will
       * begin.
       * </p>
       *
       * <p>
       * This can be useful both to throttle queries to a remote data source
       * and to avoid distracting the user by showing them less relevant
       * results before they've paused their typing.
       * </p>
       *
       * @attribute queryDelay
       * @type Number
       * @default 100
       */
      queryDelay: {
          value: 100
      },
  
      /**
       * Query delimiter string. When a delimiter is configured, the input value
       * will be split on the delimiter, and only the last portion will be used in
       * autocomplete queries and updated when the <code>query</code> attribute is
       * modified.
       *
       * @attribute queryDelimiter
       * @type String|null
       * @default null
       */
      queryDelimiter: {
          value: null
      },
  
      /**
       * <p>
       * Source request template. This can be a function that accepts a query as a
       * parameter and returns a request string, or it can be a string containing
       * the placeholder "{query}", which will be replaced with the actual
       * URI-encoded query. In either case, the resulting string will be appended
       * to the request URL when the <code>source</code> attribute is set to a
       * remote DataSource, JSONP URL, or XHR URL (it will not be appended to YQL
       * URLs).
       * </p>
       *
       * <p>
       * While <code>requestTemplate</code> may be set to either a function or
       * a string, it will always be returned as a function that accepts a
       * query argument and returns a string.
       * </p>
       *
       * @attribute requestTemplate
       * @type Function|String|null
       * @default null
       */
      requestTemplate: {
          setter: '_setRequestTemplate',
          value: null
      },
  
      /**
       * <p>
       * Array of local result filter functions. If provided, each filter
       * will be called with two arguments when results are received: the query
       * and an array of result objects. See the documentation for the
       * <code>results</code> event for a list of the properties available on each
       * result object.
       * </p>
       *
       * <p>
       * Each filter is expected to return a filtered or modified version of the
       * results array, which will then be passed on to subsequent filters, then
       * the <code>resultHighlighter</code> function (if set), then the
       * <code>resultFormatter</code> function (if set), and finally to
       * subscribers to the <code>results</code> event.
       * </p>
       *
       * <p>
       * If no <code>source</code> is set, result filters will not be called.
       * </p>
       *
       * <p>
       * Prepackaged result filters provided by the autocomplete-filters and
       * autocomplete-filters-accentfold modules can be used by specifying the
       * filter name as a string, such as <code>'phraseMatch'</code> (assuming
       * the necessary filters module is loaded).
       * </p>
       *
       * @attribute resultFilters
       * @type Array
       * @default []
       */
      resultFilters: {
          setter: '_setResultFilters',
          value: []
      },
  
      /**
       * <p>
       * Function which will be used to format results. If provided, this function
       * will be called with two arguments after results have been received and
       * filtered: the query and an array of result objects. The formatter is
       * expected to return an array of HTML strings or Node instances containing
       * the desired HTML for each result.
       * </p>
       *
       * <p>
       * See the documentation for the <code>results</code> event for a list of
       * the properties available on each result object.
       * </p>
       *
       * <p>
       * If no <code>source</code> is set, the formatter will not be called.
       * </p>
       *
       * @attribute resultFormatter
       * @type Function|null
       */
      resultFormatter: {
          validator: _FUNCTION_VALIDATOR
      },
  
      /**
       * <p>
       * Function which will be used to highlight results. If provided, this
       * function will be called with two arguments after results have been
       * received and filtered: the query and an array of filtered result objects.
       * The highlighter is expected to return an array of highlighted result
       * text in the form of HTML strings.
       * </p>
       *
       * <p>
       * See the documentation for the <code>results</code> event for a list of
       * the properties available on each result object.
       * </p>
       *
       * <p>
       * If no <code>source</code> is set, the highlighter will not be called.
       * </p>
       *
       * @attribute resultHighlighter
       * @type Function|null
       */
      resultHighlighter: {
          setter: '_setResultHighlighter'
      },
  
      /**
       * <p>
       * Locator that should be used to extract an array of results from a
       * non-array response.
       * </p>
       *
       * <p>
       * By default, no locator is applied, and all responses are assumed to be
       * arrays by default. If all responses are already arrays, you don't need to
       * define a locator.
       * </p>
       *
       * <p>
       * The locator may be either a function (which will receive the raw response
       * as an argument and must return an array) or a string representing an
       * object path, such as "foo.bar.baz" (which would return the value of
       * <code>result.foo.bar.baz</code> if the response is an object).
       * </p>
       *
       * <p>
       * While <code>resultListLocator</code> may be set to either a function or a
       * string, it will always be returned as a function that accepts a response
       * argument and returns an array.
       * </p>
       *
       * @attribute resultListLocator
       * @type Function|String|null
       */
      resultListLocator: {
          setter: '_setLocator'
      },
  
      /**
       * Current results, or an empty array if there are no results.
       *
       * @attribute results
       * @type Array
       * @default []
       * @readonly
       */
      results: {
          readOnly: true,
          value: []
      },
  
      /**
       * <p>
       * Locator that should be used to extract a plain text string from a
       * non-string result item. The resulting text value will typically be the
       * value that ends up being inserted into an input field or textarea when
       * the user of an autocomplete implementation selects a result.
       * </p>
       *
       * <p>
       * By default, no locator is applied, and all results are assumed to be
       * plain text strings. If all results are already plain text strings, you
       * don't need to define a locator.
       * </p>
       *
       * <p>
       * The locator may be either a function (which will receive the raw result
       * as an argument and must return a string) or a string representing an
       * object path, such as "foo.bar.baz" (which would return the value of
       * <code>result.foo.bar.baz</code> if the result is an object).
       * </p>
       *
       * <p>
       * While <code>resultTextLocator</code> may be set to either a function or a
       * string, it will always be returned as a function that accepts a result
       * argument and returns a string.
       * </p>
       *
       * @attribute resultTextLocator
       * @type Function|String|null
       */
      resultTextLocator: {
          setter: '_setLocator'
      },
  
      /**
       * <p>
       * Source for autocomplete results. The following source types are
       * supported:
       * </p>
       *
       * <dl>
       *   <dt>Array</dt>
       *   <dd>
       *     <p>
       *     <i>Example:</i> <code>['first result', 'second result', 'etc']</code>
       *     </p>
       *
       *     <p>
       *     The full array will be provided to any configured filters for each
       *     query. This is an easy way to create a fully client-side autocomplete
       *     implementation.
       *     </p>
       *   </dd>
       *
       *   <dt>DataSource</dt>
       *   <dd>
       *     <p>
       *     A <code>DataSource</code> instance or other object that provides a
       *     DataSource-like <code>sendRequest</code> method. See the
       *     <code>DataSource</code> documentation for details.
       *     </p>
       *   </dd>
       *
       *   <dt>Function</dt>
       *   <dd>
       *     <p>
       *     <i>Example (synchronous):</i> <code>function (query) { return ['foo', 'bar']; }</code><br>
             <i>Example (async):</i> <code>function (query, callback) { callback(['foo', 'bar']); }</code>
       *     </p>
       *
       *     <p>
       *     A function source will be called with the current query and a
       *     callback function as parameters, and should either return an array of
       *     results (for synchronous operation) or return nothing and pass an
       *     array of results to the provided callback (for asynchronous
       *     operation).
       *     </p>
       *   </dd>
       *
       *   <dt>Object</dt>
       *   <dd>
       *     <p>
       *     <i>Example:</i> <code>{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}</code>
       *     </p>
       *
       *     <p>
       *     An object will be treated as a query hashmap. If a property on the
       *     object matches the current query, the value of that property will be
       *     used as the response.
       *     </p>
       *
       *     <p>
       *     The response is assumed to be an array of results by default. If the
       *     response is not an array, provide a <code>resultListLocator</code> to
       *     process the response and return an array.
       *     </p>
       *   </dd>
       * </dl>
       *
       * <p>
       * If the optional <code>autocomplete-sources</code> module is loaded, then
       * the following additional source types will be supported as well:
       * </p>
       *
       * <dl>
       *   <dt>&lt;select&gt; Node</dt>
       *   <dd>
       *     <p>
       *     You may provide a YUI Node instance wrapping a &lt;select&gt;
       *     element, and the options in the list will be used as results. You
       *     will also need to specify a <code>resultTextLocator</code> of 'text'
       *     or 'value', depending on what you want to use as the text of the
       *     result.
       *     </p>
       *
       *     <p>
       *     Each result will be an object with the following properties:
       *     </p>
       *
       *     <dl>
       *       <dt>html (String)</dt>
       *       <dd>
       *         <p>HTML content of the &lt;option&gt; element.</p>
       *       </dd>
       *
       *       <dt>index (Number)</dt>
       *       <dd>
       *         <p>Index of the &lt;option&gt; element in the list.</p>
       *       </dd>
       *
       *       <dt>node (Y.Node)</dt>
       *       <dd>
       *         <p>Node instance referring to the original &lt;option&gt; element.</p>
       *       </dd>
       *
       *       <dt>selected (Boolean)</dt>
       *       <dd>
       *         <p>Whether or not this item is currently selected in the
       *         &lt;select&gt; list.</p>
       *       </dd>
       *
       *       <dt>text (String)</dt>
       *       <dd>
       *         <p>Text content of the &lt;option&gt; element.</p>
       *       </dd>
       *
       *       <dt>value (String)</dt>
       *       <dd>
       *         <p>Value of the &lt;option&gt; element.</p>
       *       </dd>
       *     </dl>
       *   </dd>
       *
       *   <dt>String (JSONP URL)</dt>
       *   <dd>
       *     <p>
       *     <i>Example:</i> <code>'http://example.com/search?q={query}&callback={callback}'</code>
       *     </p>
       *
       *     <p>
       *     If a URL with a <code>{callback}</code> placeholder is provided, it
       *     will be used to make a JSONP request. The <code>{query}</code>
       *     placeholder will be replaced with the current query, and the
       *     <code>{callback}</code> placeholder will be replaced with an
       *     internally-generated JSONP callback name. Both placeholders must
       *     appear in the URL, or the request will fail. An optional
       *     <code>{maxResults}</code> placeholder may also be provided, and will
       *     be replaced with the value of the maxResults attribute (or 1000 if
       *     the maxResults attribute is 0 or less).
       *     </p>
       *
       *     <p>
       *     The response is assumed to be an array of results by default. If the
       *     response is not an array, provide a <code>resultListLocator</code> to
       *     process the response and return an array.
       *     </p>
       *
       *     <p>
       *     <strong>The <code>jsonp</code> module must be loaded in order for
       *     JSONP URL sources to work.</strong> If the <code>jsonp</code> module
       *     is not already loaded, it will be loaded on demand if possible.
       *     </p>
       *   </dd>
       *
       *   <dt>String (XHR URL)</dt>
       *   <dd>
       *     <p>
       *     <i>Example:</i> <code>'http://example.com/search?q={query}'</code>
       *     </p>
       *
       *     <p>
       *     If a URL without a <code>{callback}</code> placeholder is provided,
       *     it will be used to make a same-origin XHR request. The
       *     <code>{query}</code> placeholder will be replaced with the current
       *     query. An optional <code>{maxResults}</code> placeholder may also be
       *     provided, and will be replaced with the value of the maxResults
       *     attribute (or 1000 if the maxResults attribute is 0 or less).
       *     </p>
       *
       *     <p>
       *     The response is assumed to be a JSON array of results by default. If
       *     the response is a JSON object and not an array, provide a
       *     <code>resultListLocator</code> to process the response and return an
       *     array. If the response is in some form other than JSON, you will
       *     need to use a custom DataSource instance as the source.
       *     </p>
       *
       *     <p>
       *     <strong>The <code>io-base</code> and <code>json-parse</code> modules
       *     must be loaded in order for XHR URL sources to work.</strong> If
       *     these modules are not already loaded, they will be loaded on demand
       *     if possible.
       *     </p>
       *   </dd>
       *
       *   <dt>String (YQL query)</dt>
       *   <dd>
       *     <p>
       *     <i>Example:</i> <code>'select * from search.suggest where query="{query}"'</code>
       *     </p>
       *
       *     <p>
       *     If a YQL query is provided, it will be used to make a YQL request.
       *     The <code>{query}</code> placeholder will be replaced with the
       *     current autocomplete query. This placeholder must appear in the YQL
       *     query, or the request will fail. An optional
       *     <code>{maxResults}</code> placeholder may also be provided, and will
       *     be replaced with the value of the maxResults attribute (or 1000 if
       *     the maxResults attribute is 0 or less).
       *     </p>
       *
       *     <p>
       *     <strong>The <code>yql</code> module must be loaded in order for YQL
       *     sources to work.</strong> If the <code>yql</code> module is not
       *     already loaded, it will be loaded on demand if possible.
       *     </p>
       *   </dd>
       * </dl>
       *
       * <p>
       * As an alternative to providing a source, you could simply listen for
       * <code>query</code> events and handle them any way you see fit. Providing
       * a source is optional, but will usually be simpler.
       * </p>
       *
       * @attribute source
       * @type Array|DataSource|Function|Node|Object|String|null
       */
      source: {
          setter: '_setSource'
      },
  
      /**
       * <p>
       * May be used to force a specific source type, overriding the automatic
       * source type detection. It should almost never be necessary to do this,
       * but as they taught us in the Boy Scouts, one should always be prepared,
       * so it's here if you need it. Be warned that if you set this attribute and
       * something breaks, it's your own fault.
       * </p>
       *
       * <p>
       * Supported <code>sourceType</code> values are: 'array', 'datasource',
       * 'function', and 'object'.
       * </p>
       *
       * <p>
       * If the <code>autocomplete-sources</code> module is loaded, the following
       * additional source types are supported: 'io', 'jsonp', 'select',
       * 'string', 'yql'
       * </p>
       *
       * @attribute sourceType
       * @type String
       */
      sourceType: {
          value: null
      },
  
      /**
       * If the <code>inputNode</code> specified at instantiation time has a
       * <code>node-tokeninput</code> plugin attached to it, this attribute will
       * be a reference to the <code>Y.Plugin.TokenInput</code> instance.
       *
       * @attribute tokenInput
       * @type Plugin.TokenInput
       * @readonly
       */
      tokenInput: {
          readOnly: true
      },
  
      /**
       * Current value of the input node.
       *
       * @attribute value
       * @type String
       * @default ''
       */
      value: {
          // Why duplicate this._inputNode.get('value')? Because we need a
          // reliable way to track the source of value changes. We want to perform
          // completion when the user changes the value, but not when we change
          // the value.
          value: ''
      }
  };
  
  AutoCompleteBase.CSS_PREFIX = 'ac';
  AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
  
  /**
   * Mapping of built-in source types to their setter functions. DataSource
   * instances and DataSource-like objects are handled natively, so are not
   * mapped here.
   *
   * @property SOURCE_TYPES
   * @type {Object}
   * @static
   */
  AutoCompleteBase.SOURCE_TYPES = {
      array     : '_createArraySource',
      'function': '_createFunctionSource',
      object    : '_createObjectSource'
  };
  
  AutoCompleteBase.prototype = {
      // -- Public Prototype Methods ---------------------------------------------
  
      /**
       * Clears the result cache.
       *
       * @method clearCache
       * @chainable
       * @since 3.5.0
       */
      clearCache: function () {
          this._cache && (this._cache = {});
          return this;
      },
  
      /**
       * <p>
       * Sends a request to the configured source. If no source is configured,
       * this method won't do anything.
       * </p>
       *
       * <p>
       * Usually there's no reason to call this method manually; it will be
       * called automatically when user input causes a <code>query</code> event to
       * be fired. The only time you'll need to call this method manually is if
       * you want to force a request to be sent when no user input has occurred.
       * </p>
       *
       * @method sendRequest
       * @param {String} query (optional) Query to send. If specified, the
       *   <code>query</code> attribute will be set to this query. If not
       *   specified, the current value of the <code>query</code> attribute will
       *   be used.
       * @param {Function} requestTemplate (optional) Request template function.
       *   If not specified, the current value of the <code>requestTemplate</code>
       *   attribute will be used.
       * @chainable
       */
      sendRequest: function (query, requestTemplate) {
          var request,
              source = this.get('source');
  
          if (query || query === '') {
              this._set(QUERY, query);
          } else {
              query = this.get(QUERY) || '';
          }
  
          if (source) {
              if (!requestTemplate) {
                  requestTemplate = this.get(REQUEST_TEMPLATE);
              }
  
              request = requestTemplate ?
                  requestTemplate.call(this, query) : query;
  
  
              source.sendRequest({
                  query  : query,
                  request: request,
  
                  callback: {
                      success: Y.bind(this._onResponse, this, query)
                  }
              });
          }
  
          return this;
      },
  
      // -- Protected Lifecycle Methods ------------------------------------------
  
      /**
       * Attaches event listeners and behaviors.
       *
       * @method _bindUIACBase
       * @protected
       */
      _bindUIACBase: function () {
          var inputNode  = this.get(INPUT_NODE),
              tokenInput = inputNode && inputNode.tokenInput;
  
          // If the inputNode has a node-tokeninput plugin attached, bind to the
          // plugin's inputNode instead.
          if (tokenInput) {
              inputNode = tokenInput.get(INPUT_NODE);
              this._set('tokenInput', tokenInput);
          }
  
          if (!inputNode) {
              Y.error('No inputNode specified.');
              return;
          }
  
          this._inputNode = inputNode;
  
          this._acBaseEvents = new Y.EventHandle([
              // This is the valueChange event on the inputNode, provided by the
              // event-valuechange module, not our own valueChange.
              inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
              inputNode.on('blur', this._onInputBlur, this),
  
              this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
              this.after('sourceTypeChange', this._afterSourceTypeChange),
              this.after(VALUE_CHANGE, this._afterValueChange)
          ]);
      },
  
      /**
       * Detaches AutoCompleteBase event listeners.
       *
       * @method _destructorACBase
       * @protected
       */
      _destructorACBase: function () {
          this._acBaseEvents.detach();
      },
  
      /**
       * Synchronizes the UI state of the <code>inputNode</code>.
       *
       * @method _syncUIACBase
       * @protected
       */
      _syncUIACBase: function () {
          this._syncBrowserAutocomplete();
          this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
      },
  
      // -- Protected Prototype Methods ------------------------------------------
  
      /**
       * Creates a DataSource-like object that simply returns the specified array
       * as a response. See the <code>source</code> attribute for more details.
       *
       * @method _createArraySource
       * @param {Array} source
       * @return {Object} DataSource-like object.
       * @protected
       */
      _createArraySource: function (source) {
          var that = this;
  
          return {
              type: 'array',
              sendRequest: function (request) {
                  that[_SOURCE_SUCCESS](source.concat(), request);
              }
          };
      },
  
      /**
       * Creates a DataSource-like object that passes the query to a
       * custom-defined function, which is expected to call the provided callback
       * with an array of results. See the <code>source</code> attribute for more
       * details.
       *
       * @method _createFunctionSource
       * @param {Function} source Function that accepts a query and a callback as
       *   parameters, and calls the callback with an array of results.
       * @return {Object} DataSource-like object.
       * @protected
       */
      _createFunctionSource: function (source) {
          var that = this;
  
          return {
              type: 'function',
              sendRequest: function (request) {
                  var value;
  
                  function afterResults(results) {
                      that[_SOURCE_SUCCESS](results || [], request);
                  }
  
                  // Allow both synchronous and asynchronous functions. If we get
                  // a truthy return value, assume the function is synchronous.
                  if ((value = source(request.query, afterResults))) {
                      afterResults(value);
                  }
              }
          };
      },
  
      /**
       * Creates a DataSource-like object that looks up queries as properties on
       * the specified object, and returns the found value (if any) as a response.
       * See the <code>source</code> attribute for more details.
       *
       * @method _createObjectSource
       * @param {Object} source
       * @return {Object} DataSource-like object.
       * @protected
       */
      _createObjectSource: function (source) {
          var that = this;
  
          return {
              type: 'object',
              sendRequest: function (request) {
                  var query = request.query;
  
                  that[_SOURCE_SUCCESS](
                      YObject.owns(source, query) ? source[query] : [],
                      request
                  );
              }
          };
      },
  
      /**
       * Returns <code>true</code> if <i>value</i> is either a function or
       * <code>null</code>.
       *
       * @method _functionValidator
       * @param {Function|null} value Value to validate.
       * @protected
       */
      _functionValidator: function (value) {
          return value === null || isFunction(value);
      },
  
      /**
       * Faster and safer alternative to Y.Object.getValue(). Doesn't bother
       * casting the path to an array (since we already know it's an array) and
       * doesn't throw an error if a value in the middle of the object hierarchy
       * is neither <code>undefined</code> nor an object.
       *
       * @method _getObjectValue
       * @param {Object} obj
       * @param {Array} path
       * @return {mixed} Located value, or <code>undefined</code> if the value was
       *   not found at the specified path.
       * @protected
       */
      _getObjectValue: function (obj, path) {
          if (!obj) {
              return;
          }
  
          for (var i = 0, len = path.length; obj && i < len; i++) {
              obj = obj[path[i]];
          }
  
          return obj;
      },
  
      /**
       * Parses result responses, performs filtering and highlighting, and fires
       * the <code>results</code> event.
       *
       * @method _parseResponse
       * @param {String} query Query that generated these results.
       * @param {Object} response Response containing results.
       * @param {Object} data Raw response data.
       * @protected
       */
      _parseResponse: function (query, response, data) {
          var facade = {
                  data   : data,
                  query  : query,
                  results: []
              },
  
              listLocator = this.get(RESULT_LIST_LOCATOR),
              results     = [],
              unfiltered  = response && response.results,
  
              filters,
              formatted,
              formatter,
              highlighted,
              highlighter,
              i,
              len,
              maxResults,
              result,
              text,
              textLocator;
  
          if (unfiltered && listLocator) {
              unfiltered = listLocator.call(this, unfiltered);
          }
  
          if (unfiltered && unfiltered.length) {
              filters     = this.get('resultFilters');
              textLocator = this.get('resultTextLocator');
  
              // Create a lightweight result object for each result to make them
              // easier to work with. The various properties on the object
              // represent different formats of the result, and will be populated
              // as we go.
              for (i = 0, len = unfiltered.length; i < len; ++i) {
                  result = unfiltered[i];
  
                  text = textLocator ?
                          textLocator.call(this, result) :
                          result.toString();
  
                  results.push({
                      display: Escape.html(text),
                      raw    : result,
                      text   : text
                  });
              }
  
              // Run the results through all configured result filters. Each
              // filter returns an array of (potentially fewer) result objects,
              // which is then passed to the next filter, and so on.
              for (i = 0, len = filters.length; i < len; ++i) {
                  results = filters[i].call(this, query, results.concat());
  
                  if (!results) {
                      return;
                  }
  
                  if (!results.length) {
                      break;
                  }
              }
  
              if (results.length) {
                  formatter   = this.get('resultFormatter');
                  highlighter = this.get('resultHighlighter');
                  maxResults  = this.get('maxResults');
  
                  // If maxResults is set and greater than 0, limit the number of
                  // results.
                  if (maxResults && maxResults > 0 &&
                          results.length > maxResults) {
                      results.length = maxResults;
                  }
  
                  // Run the results through the configured highlighter (if any).
                  // The highlighter returns an array of highlighted strings (not
                  // an array of result objects), and these strings are then added
                  // to each result object.
                  if (highlighter) {
                      highlighted = highlighter.call(this, query,
                              results.concat());
  
                      if (!highlighted) {
                          return;
                      }
  
                      for (i = 0, len = highlighted.length; i < len; ++i) {
                          result = results[i];
                          result.highlighted = highlighted[i];
                          result.display     = result.highlighted;
                      }
                  }
  
                  // Run the results through the configured formatter (if any) to
                  // produce the final formatted results. The formatter returns an
                  // array of strings or Node instances (not an array of result
                  // objects), and these strings/Nodes are then added to each
                  // result object.
                  if (formatter) {
                      formatted = formatter.call(this, query, results.concat());
  
                      if (!formatted) {
                          return;
                      }
  
                      for (i = 0, len = formatted.length; i < len; ++i) {
                          results[i].display = formatted[i];
                      }
                  }
              }
          }
  
          facade.results = results;
          this.fire(EVT_RESULTS, facade);
      },
  
      /**
       * <p>
       * Returns the query portion of the specified input value, or
       * <code>null</code> if there is no suitable query within the input value.
       * </p>
       *
       * <p>
       * If a query delimiter is defined, the query will be the last delimited
       * part of of the string.
       * </p>
       *
       * @method _parseValue
       * @param {String} value Input value from which to extract the query.
       * @return {String|null} query
       * @protected
       */
      _parseValue: function (value) {
          var delim = this.get(QUERY_DELIMITER);
  
          if (delim) {
              value = value.split(delim);
              value = value[value.length - 1];
          }
  
          return Lang.trimLeft(value);
      },
  
      /**
       * Setter for the `enableCache` attribute.
       *
       * @method _setEnableCache
       * @param {Boolean} value
       * @protected
       * @since 3.5.0
       */
      _setEnableCache: function (value) {
          // When `this._cache` is an object, result sources will store cached
          // results in it. When it's falsy, they won't. This way result sources
          // don't need to get the value of the `enableCache` attribute on every
          // request, which would be sloooow.
          this._cache = value ? {} : null;
      },
  
      /**
       * Setter for locator attributes.
       *
       * @method _setLocator
       * @param {Function|String|null} locator
       * @return {Function|null}
       * @protected
       */
      _setLocator: function (locator) {
          if (this[_FUNCTION_VALIDATOR](locator)) {
              return locator;
          }
  
          var that = this;
  
          locator = locator.toString().split('.');
  
          return function (result) {
              return result && that._getObjectValue(result, locator);
          };
      },
  
      /**
       * Setter for the <code>requestTemplate</code> attribute.
       *
       * @method _setRequestTemplate
       * @param {Function|String|null} template
       * @return {Function|null}
       * @protected
       */
      _setRequestTemplate: function (template) {
          if (this[_FUNCTION_VALIDATOR](template)) {
              return template;
          }
  
          template = template.toString();
  
          return function (query) {
              return Lang.sub(template, {query: encodeURIComponent(query)});
          };
      },
  
      /**
       * Setter for the <code>resultFilters</code> attribute.
       *
       * @method _setResultFilters
       * @param {Array|Function|String|null} filters <code>null</code>, a filter
       *   function, an array of filter functions, or a string or array of strings
       *   representing the names of methods on
       *   <code>Y.AutoCompleteFilters</code>.
       * @return {Array} Array of filter functions (empty if <i>filters</i> is
       *   <code>null</code>).
       * @protected
       */
      _setResultFilters: function (filters) {
          var acFilters, getFilterFunction;
  
          if (filters === null) {
              return [];
          }
  
          acFilters = Y.AutoCompleteFilters;
  
          getFilterFunction = function (filter) {
              if (isFunction(filter)) {
                  return filter;
              }
  
              if (isString(filter) && acFilters &&
                      isFunction(acFilters[filter])) {
                  return acFilters[filter];
              }
  
              return false;
          };
  
          if (Lang.isArray(filters)) {
              filters = YArray.map(filters, getFilterFunction);
              return YArray.every(filters, function (f) { return !!f; }) ?
                      filters : INVALID_VALUE;
          } else {
              filters = getFilterFunction(filters);
              return filters ? [filters] : INVALID_VALUE;
          }
      },
  
      /**
       * Setter for the <code>resultHighlighter</code> attribute.
       *
       * @method _setResultHighlighter
       * @param {Function|String|null} highlighter <code>null</code>, a
       *   highlighter function, or a string representing the name of a method on
       *   <code>Y.AutoCompleteHighlighters</code>.
       * @return {Function|null}
       * @protected
       */
      _setResultHighlighter: function (highlighter) {
          var acHighlighters;
  
          if (this._functionValidator(highlighter)) {
              return highlighter;
          }
  
          acHighlighters = Y.AutoCompleteHighlighters;
  
          if (isString(highlighter) && acHighlighters &&
                  isFunction(acHighlighters[highlighter])) {
              return acHighlighters[highlighter];
          }
  
          return INVALID_VALUE;
      },
  
      /**
       * Setter for the <code>source</code> attribute. Returns a DataSource or
       * a DataSource-like object depending on the type of <i>source</i> and/or
       * the value of the <code>sourceType</code> attribute.
       *
       * @method _setSource
       * @param {mixed} source AutoComplete source. See the <code>source</code>
       *   attribute for details.
       * @return {DataSource|Object}
       * @protected
       */
      _setSource: function (source) {
          var sourceType = this.get('sourceType') || Lang.type(source),
              sourceSetter;
  
          if ((source && isFunction(source.sendRequest))
                  || source === null
                  || sourceType === 'datasource') {
  
              // Quacks like a DataSource instance (or null). Make it so!
              this._rawSource = source;
              return source;
          }
  
          // See if there's a registered setter for this source type.
          if ((sourceSetter = AutoCompleteBase.SOURCE_TYPES[sourceType])) {
              this._rawSource = source;
              return Lang.isString(sourceSetter) ?
                      this[sourceSetter](source) : sourceSetter(source);
          }
  
          Y.error("Unsupported source type '" + sourceType + "'. Maybe autocomplete-sources isn't loaded?");
          return INVALID_VALUE;
      },
  
      /**
       * Shared success callback for non-DataSource sources.
       *
       * @method _sourceSuccess
       * @param {mixed} data Response data.
       * @param {Object} request Request object.
       * @protected
       */
      _sourceSuccess: function (data, request) {
          request.callback.success({
              data: data,
              response: {results: data}
          });
      },
  
      /**
       * Synchronizes the UI state of the <code>allowBrowserAutocomplete</code>
       * attribute.
       *
       * @method _syncBrowserAutocomplete
       * @protected
       */
      _syncBrowserAutocomplete: function () {
          var inputNode = this.get(INPUT_NODE);
  
          if (inputNode.get('nodeName').toLowerCase() === 'input') {
              inputNode.setAttribute('autocomplete',
                      this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
          }
      },
  
      /**
       * <p>
       * Updates the query portion of the <code>value</code> attribute.
       * </p>
       *
       * <p>
       * If a query delimiter is defined, the last delimited portion of the input
       * value will be replaced with the specified <i>value</i>.
       * </p>
       *
       * @method _updateValue
       * @param {String} newVal New value.
       * @protected
       */
      _updateValue: function (newVal) {
          var delim = this.get(QUERY_DELIMITER),
              insertDelim,
              len,
              prevVal;
  
          newVal = Lang.trimLeft(newVal);
  
          if (delim) {
              insertDelim = trim(delim); // so we don't double up on spaces
              prevVal     = YArray.map(trim(this.get(VALUE)).split(delim), trim);
              len         = prevVal.length;
  
              if (len > 1) {
                  prevVal[len - 1] = newVal;
                  newVal = prevVal.join(insertDelim + ' ');
              }
  
              newVal = newVal + insertDelim + ' ';
          }
  
          this.set(VALUE, newVal);
      },
  
      // -- Protected Event Handlers ---------------------------------------------
  
      /**
       * Updates the current <code>source</code> based on the new
       * <code>sourceType</code> to ensure that the two attributes don't get out
       * of sync when they're changed separately.
       *
       * @method _afterSourceTypeChange
       * @param {EventFacade} e
       * @protected
       */
      _afterSourceTypeChange: function (e) {
          if (this._rawSource) {
              this.set('source', this._rawSource);
          }
      },
  
      /**
       * Handles change events for the <code>value</code> attribute.
       *
       * @method _afterValueChange
       * @param {EventFacade} e
       * @protected
       */
      _afterValueChange: function (e) {
          var newVal   = e.newVal,
              self     = this,
              uiChange = e.src === AutoCompleteBase.UI_SRC,
              delay, fire, minQueryLength, query;
  
          // Update the UI if the value was changed programmatically.
          if (!uiChange) {
              self._inputNode.set(VALUE, newVal);
          }
  
  
          minQueryLength = self.get('minQueryLength');
          query          = self._parseValue(newVal) || '';
  
          if (minQueryLength >= 0 && query.length >= minQueryLength) {
              // Only query on changes that originate from the UI.
              if (uiChange) {
                  delay = self.get('queryDelay');
  
                  fire = function () {
                      self.fire(EVT_QUERY, {
                          inputValue: newVal,
                          query     : query,
                          src       : e.src
                      });
                  };
  
                  if (delay) {
                      clearTimeout(self._delay);
                      self._delay = setTimeout(fire, delay);
                  } else {
                      fire();
                  }
              } else {
                  // For programmatic value changes, just update the query
                  // attribute without sending a query.
                  self._set(QUERY, query);
              }
          } else {
              clearTimeout(self._delay);
  
              self.fire(EVT_CLEAR, {
                  prevVal: e.prevVal ? self._parseValue(e.prevVal) : null,
                  src    : e.src
              });
          }
      },
  
      /**
       * Handles <code>blur</code> events on the input node.
       *
       * @method _onInputBlur
       * @param {EventFacade} e
       * @protected
       */
      _onInputBlur: function (e) {
          var delim = this.get(QUERY_DELIMITER),
              delimPos,
              newVal,
              value;
  
          // If a query delimiter is set and the input's value contains one or
          // more trailing delimiters, strip them.
          if (delim && !this.get('allowTrailingDelimiter')) {
              delim = Lang.trimRight(delim);
              value = newVal = this._inputNode.get(VALUE);
  
              if (delim) {
                  while ((newVal = Lang.trimRight(newVal)) &&
                          (delimPos = newVal.length - delim.length) &&
                          newVal.lastIndexOf(delim) === delimPos) {
  
                      newVal = newVal.substring(0, delimPos);
                  }
              } else {
                  // Delimiter is one or more space characters, so just trim the
                  // value.
                  newVal = Lang.trimRight(newVal);
              }
  
              if (newVal !== value) {
                  this.set(VALUE, newVal);
              }
          }
      },
  
      /**
       * Handles <code>valueChange</code> events on the input node and fires a
       * <code>query</code> event when the input value meets the configured
       * criteria.
       *
       * @method _onInputValueChange
       * @param {EventFacade} e
       * @protected
       */
      _onInputValueChange: function (e) {
          var newVal = e.newVal;
  
          // Don't query if the internal value is the same as the new value
          // reported by valueChange.
          if (newVal !== this.get(VALUE)) {
              this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
          }
      },
  
      /**
       * Handles source responses and fires the <code>results</code> event.
       *
       * @method _onResponse
       * @param {EventFacade} e
       * @protected
       */
      _onResponse: function (query, e) {
          // Ignore stale responses that aren't for the current query.
          if (query === (this.get(QUERY) || '')) {
              this._parseResponse(query || '', e.response, e.data);
          }
      },
  
      // -- Protected Default Event Handlers -------------------------------------
  
      /**
       * Default <code>clear</code> event handler. Sets the <code>results</code>
       * attribute to an empty array and <code>query</code> to null.
       *
       * @method _defClearFn
       * @protected
       */
      _defClearFn: function () {
          this._set(QUERY, null);
          this._set(RESULTS, []);
      },
  
      /**
       * Default <code>query</code> event handler. Sets the <code>query</code>
       * attribute and sends a request to the source if one is configured.
       *
       * @method _defQueryFn
       * @param {EventFacade} e
       * @protected
       */
      _defQueryFn: function (e) {
          this.sendRequest(e.query); // sendRequest will set the 'query' attribute
      },
  
      /**
       * Default <code>results</code> event handler. Sets the <code>results</code>
       * attribute to the latest results.
       *
       * @method _defResultsFn
       * @param {EventFacade} e
       * @protected
       */
      _defResultsFn: function (e) {
          this._set(RESULTS, e[RESULTS]);
      }
  };
  
  Y.AutoCompleteBase = AutoCompleteBase;
  
  
  }, '@VERSION@' ,{requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base'], optional:['autocomplete-sources']});
  