]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/autocomplete/autocomplete.js
Release 6.5.0
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / autocomplete / autocomplete.js
1 /*
2 Copyright (c) 2010, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 3.3.0
6 build: 3167
7 */
8 YUI.add('autocomplete-base', function(Y) {
9
10 /**
11  * Provides automatic input completion or suggestions for text input fields and
12  * textareas.
13  *
14  * @module autocomplete
15  * @since 3.3.0
16  */
17
18 /**
19  * <code>Y.Base</code> extension that provides core autocomplete logic (but no
20  * UI implementation) for a text input field or textarea. Must be mixed into a
21  * <code>Y.Base</code>-derived class to be useful.
22  *
23  * @module autocomplete
24  * @submodule autocomplete-base
25  */
26
27 /**
28  * <p>
29  * Extension that provides core autocomplete logic (but no UI implementation)
30  * for a text input field or textarea.
31  * </p>
32  *
33  * <p>
34  * The <code>AutoCompleteBase</code> class provides events and attributes that
35  * abstract away core autocomplete logic and configuration, but does not provide
36  * a widget implementation or suggestion UI. For a prepackaged autocomplete
37  * widget, see <code>AutoCompleteList</code>.
38  * </p>
39  *
40  * <p>
41  * This extension cannot be instantiated directly, since it doesn't provide an
42  * actual implementation. It's intended to be mixed into a
43  * <code>Y.Base</code>-based class or widget.
44  * </p>
45  *
46  * <p>
47  * <code>Y.Widget</code>-based example:
48  * </p>
49  *
50  * <pre>
51  * YUI().use('autocomplete-base', 'widget', function (Y) {
52  * &nbsp;&nbsp;var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
53  * &nbsp;&nbsp;&nbsp;&nbsp;// Custom prototype methods and properties.
54  * &nbsp;&nbsp;}, {
55  * &nbsp;&nbsp;&nbsp;&nbsp;// Custom static methods and properties.
56  * &nbsp;&nbsp;});
57  * &nbsp;
58  * &nbsp;&nbsp;// Custom implementation code.
59  * });
60  * </pre>
61  *
62  * <p>
63  * <code>Y.Base</code>-based example:
64  * </p>
65  *
66  * <pre>
67  * YUI().use('autocomplete-base', function (Y) {
68  * &nbsp;&nbsp;var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
69  * &nbsp;&nbsp;&nbsp;&nbsp;initializer: function () {
70  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._bindUIACBase();
71  * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._syncUIACBase();
72  * &nbsp;&nbsp;&nbsp;&nbsp;},
73  * &nbsp;
74  * &nbsp;&nbsp;&nbsp;&nbsp;// Custom prototype methods and properties.
75  * &nbsp;&nbsp;}, {
76  * &nbsp;&nbsp;&nbsp;&nbsp;// Custom static methods and properties.
77  * &nbsp;&nbsp;});
78  * &nbsp;
79  * &nbsp;&nbsp;// Custom implementation code.
80  * });
81  * </pre>
82  *
83  * @class AutoCompleteBase
84  */
85
86 var Escape  = Y.Escape,
87     Lang    = Y.Lang,
88     YArray  = Y.Array,
89     YObject = Y.Object,
90
91     isFunction = Lang.isFunction,
92     isString   = Lang.isString,
93     trim       = Lang.trim,
94
95     INVALID_VALUE = Y.Attribute.INVALID_VALUE,
96
97     _FUNCTION_VALIDATOR = '_functionValidator',
98     _SOURCE_SUCCESS     = '_sourceSuccess',
99
100     ALLOW_BROWSER_AC    = 'allowBrowserAutocomplete',
101     INPUT_NODE          = 'inputNode',
102     QUERY               = 'query',
103     QUERY_DELIMITER     = 'queryDelimiter',
104     REQUEST_TEMPLATE    = 'requestTemplate',
105     RESULTS             = 'results',
106     RESULT_LIST_LOCATOR = 'resultListLocator',
107     VALUE               = 'value',
108     VALUE_CHANGE        = 'valueChange',
109
110     EVT_CLEAR   = 'clear',
111     EVT_QUERY   = QUERY,
112     EVT_RESULTS = RESULTS;
113
114 function AutoCompleteBase() {
115     // AOP bindings.
116     Y.before(this._bindUIACBase, this, 'bindUI');
117     Y.before(this._destructorACBase, this, 'destructor');
118     Y.before(this._syncUIACBase, this, 'syncUI');
119
120     // -- Public Events --------------------------------------------------------
121
122     /**
123      * Fires after the query has been completely cleared or no longer meets the
124      * minimum query length requirement.
125      *
126      * @event clear
127      * @param {EventFacade} e Event facade with the following additional
128      *   properties:
129      *
130      * <dl>
131      *   <dt>prevVal (String)</dt>
132      *   <dd>
133      *     Value of the query before it was cleared.
134      *   </dd>
135      * </dl>
136      *
137      * @preventable _defClearFn
138      */
139     this.publish(EVT_CLEAR, {
140         defaultFn: this._defClearFn
141     });
142
143     /**
144      * Fires when the contents of the input field have changed and the input
145      * value meets the criteria necessary to generate an autocomplete query.
146      *
147      * @event query
148      * @param {EventFacade} e Event facade with the following additional
149      *   properties:
150      *
151      * <dl>
152      *   <dt>inputValue (String)</dt>
153      *   <dd>
154      *     Full contents of the text input field or textarea that generated
155      *     the query.
156      *   </dd>
157      *
158      *   <dt>query (String)</dt>
159      *   <dd>
160      *     Autocomplete query. This is the string that will be used to
161      *     request completion results. It may or may not be the same as
162      *     <code>inputValue</code>.
163      *   </dd>
164      * </dl>
165      *
166      * @preventable _defQueryFn
167      */
168     this.publish(EVT_QUERY, {
169         defaultFn: this._defQueryFn
170     });
171
172     /**
173      * Fires after query results are received from the <code>source</code>. If
174      * no source has been set, this event will not fire.
175      *
176      * @event results
177      * @param {EventFacade} e Event facade with the following additional
178      *   properties:
179      *
180      * <dl>
181      *   <dt>data (Array|Object)</dt>
182      *   <dd>
183      *     Raw, unfiltered result data (if available).
184      *   </dd>
185      *
186      *   <dt>query (String)</dt>
187      *   <dd>
188      *     Query that generated these results.
189      *   </dd>
190      *
191      *   <dt>results (Array)</dt>
192      *   <dd>
193      *     Array of filtered, formatted, and highlighted results. Each item in
194      *     the array is an object with the following properties:
195      *
196      *     <dl>
197      *       <dt>display (Node|HTMLElement|String)</dt>
198      *       <dd>
199      *         Formatted result HTML suitable for display to the user. If no
200      *         custom formatter is set, this will be an HTML-escaped version of
201      *         the string in the <code>text</code> property.
202      *       </dd>
203      *
204      *       <dt>highlighted (String)</dt>
205      *       <dd>
206      *         Highlighted (but not formatted) result text. This property will
207      *         only be set if a highlighter is in use.
208      *       </dd>
209      *
210      *       <dt>raw (mixed)</dt>
211      *       <dd>
212      *         Raw, unformatted result in whatever form it was provided by the
213      *         <code>source</code>.
214      *       </dd>
215      *
216      *       <dt>text (String)</dt>
217      *       <dd>
218      *         Plain text version of the result, suitable for being inserted
219      *         into the value of a text input field or textarea when the result
220      *         is selected by a user. This value is not HTML-escaped and should
221      *         not be inserted into the page using innerHTML.
222      *       </dd>
223      *     </dl>
224      *   </dd>
225      * </dl>
226      *
227      * @preventable _defResultsFn
228      */
229     this.publish(EVT_RESULTS, {
230         defaultFn: this._defResultsFn
231     });
232 }
233
234 // -- Public Static Properties -------------------------------------------------
235 AutoCompleteBase.ATTRS = {
236     /**
237      * Whether or not to enable the browser's built-in autocomplete
238      * functionality for input fields.
239      *
240      * @attribute allowBrowserAutocomplete
241      * @type Boolean
242      * @default false
243      */
244     allowBrowserAutocomplete: {
245         value: false
246     },
247
248     /**
249      * When a <code>queryDelimiter</code> is set, trailing delimiters will
250      * automatically be stripped from the input value by default when the
251      * input node loses focus. Set this to <code>true</code> to allow trailing
252      * delimiters.
253      *
254      * @attribute allowTrailingDelimiter
255      * @type Boolean
256      * @default false
257      */
258     allowTrailingDelimiter: {
259         value: false
260     },
261
262     /**
263      * Node to monitor for changes, which will generate <code>query</code>
264      * events when appropriate. May be either an input field or a textarea.
265      *
266      * @attribute inputNode
267      * @type Node|HTMLElement|String
268      * @writeonce
269      */
270     inputNode: {
271         setter: Y.one,
272         writeOnce: 'initOnly'
273     },
274
275     /**
276      * Maximum number of results to return. A value of <code>0</code> or less
277      * will allow an unlimited number of results.
278      *
279      * @attribute maxResults
280      * @type Number
281      * @default 0
282      */
283     maxResults: {
284         value: 0
285     },
286
287     /**
288      * Minimum number of characters that must be entered before a
289      * <code>query</code> event will be fired. A value of <code>0</code>
290      * allows empty queries; a negative value will effectively disable all
291      * <code>query</code> events.
292      *
293      * @attribute minQueryLength
294      * @type Number
295      * @default 1
296      */
297     minQueryLength: {
298         value: 1
299     },
300
301     /**
302      * <p>
303      * Current query, or <code>null</code> if there is no current query.
304      * </p>
305      *
306      * <p>
307      * The query might not be the same as the current value of the input
308      * node, both for timing reasons (due to <code>queryDelay</code>) and
309      * because when one or more <code>queryDelimiter</code> separators are
310      * in use, only the last portion of the delimited input string will be
311      * used as the query value.
312      * </p>
313      *
314      * @attribute query
315      * @type String|null
316      * @default null
317      * @readonly
318      */
319     query: {
320         readOnly: true,
321         value: null
322     },
323
324     /**
325      * <p>
326      * Number of milliseconds to delay after input before triggering a
327      * <code>query</code> event. If new input occurs before this delay is
328      * over, the previous input event will be ignored and a new delay will
329      * begin.
330      * </p>
331      *
332      * <p>
333      * This can be useful both to throttle queries to a remote data source
334      * and to avoid distracting the user by showing them less relevant
335      * results before they've paused their typing.
336      * </p>
337      *
338      * @attribute queryDelay
339      * @type Number
340      * @default 100
341      */
342     queryDelay: {
343         value: 100
344     },
345
346     /**
347      * Query delimiter string. When a delimiter is configured, the input value
348      * will be split on the delimiter, and only the last portion will be used in
349      * autocomplete queries and updated when the <code>query</code> attribute is
350      * modified.
351      *
352      * @attribute queryDelimiter
353      * @type String|null
354      * @default null
355      */
356     queryDelimiter: {
357         value: null
358     },
359
360     /**
361      * <p>
362      * Source request template. This can be a function that accepts a query as a
363      * parameter and returns a request string, or it can be a string containing
364      * the placeholder "{query}", which will be replaced with the actual
365      * URI-encoded query. In either case, the resulting string will be appended
366      * to the request URL when the <code>source</code> attribute is set to a
367      * remote DataSource, JSONP URL, or XHR URL (it will not be appended to YQL
368      * URLs).
369      * </p>
370      *
371      * <p>
372      * While <code>requestTemplate</code> may be set to either a function or
373      * a string, it will always be returned as a function that accepts a
374      * query argument and returns a string.
375      * </p>
376      *
377      * @attribute requestTemplate
378      * @type Function|String|null
379      * @default null
380      */
381     requestTemplate: {
382         setter: '_setRequestTemplate',
383         value: null
384     },
385
386     /**
387      * <p>
388      * Array of local result filter functions. If provided, each filter
389      * will be called with two arguments when results are received: the query
390      * and an array of result objects. See the documentation for the
391      * <code>results</code> event for a list of the properties available on each
392      * result object.
393      * </p>
394      *
395      * <p>
396      * Each filter is expected to return a filtered or modified version of the
397      * results array, which will then be passed on to subsequent filters, then
398      * the <code>resultHighlighter</code> function (if set), then the
399      * <code>resultFormatter</code> function (if set), and finally to
400      * subscribers to the <code>results</code> event.
401      * </p>
402      *
403      * <p>
404      * If no <code>source</code> is set, result filters will not be called.
405      * </p>
406      *
407      * <p>
408      * Prepackaged result filters provided by the autocomplete-filters and
409      * autocomplete-filters-accentfold modules can be used by specifying the
410      * filter name as a string, such as <code>'phraseMatch'</code> (assuming
411      * the necessary filters module is loaded).
412      * </p>
413      *
414      * @attribute resultFilters
415      * @type Array
416      * @default []
417      */
418     resultFilters: {
419         setter: '_setResultFilters',
420         value: []
421     },
422
423     /**
424      * <p>
425      * Function which will be used to format results. If provided, this function
426      * will be called with two arguments after results have been received and
427      * filtered: the query and an array of result objects. The formatter is
428      * expected to return an array of HTML strings or Node instances containing
429      * the desired HTML for each result.
430      * </p>
431      *
432      * <p>
433      * See the documentation for the <code>results</code> event for a list of
434      * the properties available on each result object.
435      * </p>
436      *
437      * <p>
438      * If no <code>source</code> is set, the formatter will not be called.
439      * </p>
440      *
441      * @attribute resultFormatter
442      * @type Function|null
443      */
444     resultFormatter: {
445         validator: _FUNCTION_VALIDATOR
446     },
447
448     /**
449      * <p>
450      * Function which will be used to highlight results. If provided, this
451      * function will be called with two arguments after results have been
452      * received and filtered: the query and an array of filtered result objects.
453      * The highlighter is expected to return an array of highlighted result
454      * text in the form of HTML strings.
455      * </p>
456      *
457      * <p>
458      * See the documentation for the <code>results</code> event for a list of
459      * the properties available on each result object.
460      * </p>
461      *
462      * <p>
463      * If no <code>source</code> is set, the highlighter will not be called.
464      * </p>
465      *
466      * @attribute resultHighlighter
467      * @type Function|null
468      */
469     resultHighlighter: {
470         setter: '_setResultHighlighter'
471     },
472
473     /**
474      * <p>
475      * Locator that should be used to extract an array of results from a
476      * non-array response.
477      * </p>
478      *
479      * <p>
480      * By default, no locator is applied, and all responses are assumed to be
481      * arrays by default. If all responses are already arrays, you don't need to
482      * define a locator.
483      * </p>
484      *
485      * <p>
486      * The locator may be either a function (which will receive the raw response
487      * as an argument and must return an array) or a string representing an
488      * object path, such as "foo.bar.baz" (which would return the value of
489      * <code>result.foo.bar.baz</code> if the response is an object).
490      * </p>
491      *
492      * <p>
493      * While <code>resultListLocator</code> may be set to either a function or a
494      * string, it will always be returned as a function that accepts a response
495      * argument and returns an array.
496      * </p>
497      *
498      * @attribute resultListLocator
499      * @type Function|String|null
500      */
501     resultListLocator: {
502         setter: '_setLocator'
503     },
504
505     /**
506      * Current results, or an empty array if there are no results.
507      *
508      * @attribute results
509      * @type Array
510      * @default []
511      * @readonly
512      */
513     results: {
514         readOnly: true,
515         value: []
516     },
517
518     /**
519      * <p>
520      * Locator that should be used to extract a plain text string from a
521      * non-string result item. The resulting text value will typically be the
522      * value that ends up being inserted into an input field or textarea when
523      * the user of an autocomplete implementation selects a result.
524      * </p>
525      *
526      * <p>
527      * By default, no locator is applied, and all results are assumed to be
528      * plain text strings. If all results are already plain text strings, you
529      * don't need to define a locator.
530      * </p>
531      *
532      * <p>
533      * The locator may be either a function (which will receive the raw result
534      * as an argument and must return a string) or a string representing an
535      * object path, such as "foo.bar.baz" (which would return the value of
536      * <code>result.foo.bar.baz</code> if the result is an object).
537      * </p>
538      *
539      * <p>
540      * While <code>resultTextLocator</code> may be set to either a function or a
541      * string, it will always be returned as a function that accepts a result
542      * argument and returns a string.
543      * </p>
544      *
545      * @attribute resultTextLocator
546      * @type Function|String|null
547      */
548     resultTextLocator: {
549         setter: '_setLocator'
550     },
551
552     /**
553      * <p>
554      * Source for autocomplete results. The following source types are
555      * supported:
556      * </p>
557      *
558      * <dl>
559      *   <dt>Array</dt>
560      *   <dd>
561      *     <p>
562      *     <i>Example:</i> <code>['first result', 'second result', 'etc']</code>
563      *     </p>
564      *
565      *     <p>
566      *     The full array will be provided to any configured filters for each
567      *     query. This is an easy way to create a fully client-side autocomplete
568      *     implementation.
569      *     </p>
570      *   </dd>
571      *
572      *   <dt>DataSource</dt>
573      *   <dd>
574      *     <p>
575      *     A <code>DataSource</code> instance or other object that provides a
576      *     DataSource-like <code>sendRequest</code> method. See the
577      *     <code>DataSource</code> documentation for details.
578      *     </p>
579      *   </dd>
580      *
581      *   <dt>Function</dt>
582      *   <dd>
583      *     <p>
584      *     <i>Example:</i> <code>function (query) { return ['foo', 'bar']; }</code>
585      *     </p>
586      *
587      *     <p>
588      *     A function source will be called with the current query as a
589      *     parameter, and should return an array of results.
590      *     </p>
591      *   </dd>
592      *
593      *   <dt>Object</dt>
594      *   <dd>
595      *     <p>
596      *     <i>Example:</i> <code>{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}</code>
597      *     </p>
598      *
599      *     <p>
600      *     An object will be treated as a query hashmap. If a property on the
601      *     object matches the current query, the value of that property will be
602      *     used as the response.
603      *     </p>
604      *
605      *     <p>
606      *     The response is assumed to be an array of results by default. If the
607      *     response is not an array, provide a <code>resultListLocator</code> to
608      *     process the response and return an array.
609      *     </p>
610      *   </dd>
611      * </dl>
612      *
613      * <p>
614      * If the optional <code>autocomplete-sources</code> module is loaded, then
615      * the following additional source types will be supported as well:
616      * </p>
617      *
618      * <dl>
619      *   <dt>String (JSONP URL)</dt>
620      *   <dd>
621      *     <p>
622      *     <i>Example:</i> <code>'http://example.com/search?q={query}&callback={callback}'</code>
623      *     </p>
624      *
625      *     <p>
626      *     If a URL with a <code>{callback}</code> placeholder is provided, it
627      *     will be used to make a JSONP request. The <code>{query}</code>
628      *     placeholder will be replaced with the current query, and the
629      *     <code>{callback}</code> placeholder will be replaced with an
630      *     internally-generated JSONP callback name. Both placeholders must
631      *     appear in the URL, or the request will fail. An optional
632      *     <code>{maxResults}</code> placeholder may also be provided, and will
633      *     be replaced with the value of the maxResults attribute (or 1000 if
634      *     the maxResults attribute is 0 or less).
635      *     </p>
636      *
637      *     <p>
638      *     The response is assumed to be an array of results by default. If the
639      *     response is not an array, provide a <code>resultListLocator</code> to
640      *     process the response and return an array.
641      *     </p>
642      *
643      *     <p>
644      *     <strong>The <code>jsonp</code> module must be loaded in order for
645      *     JSONP URL sources to work.</strong> If the <code>jsonp</code> module
646      *     is not already loaded, it will be loaded on demand if possible.
647      *     </p>
648      *   </dd>
649      *
650      *   <dt>String (XHR URL)</dt>
651      *   <dd>
652      *     <p>
653      *     <i>Example:</i> <code>'http://example.com/search?q={query}'</code>
654      *     </p>
655      *
656      *     <p>
657      *     If a URL without a <code>{callback}</code> placeholder is provided,
658      *     it will be used to make a same-origin XHR request. The
659      *     <code>{query}</code> placeholder will be replaced with the current
660      *     query. An optional <code>{maxResults}</code> placeholder may also be
661      *     provided, and will be replaced with the value of the maxResults
662      *     attribute (or 1000 if the maxResults attribute is 0 or less).
663      *     </p>
664      *
665      *     <p>
666      *     The response is assumed to be a JSON array of results by default. If
667      *     the response is a JSON object and not an array, provide a
668      *     <code>resultListLocator</code> to process the response and return an
669      *     array. If the response is in some form other than JSON, you will
670      *     need to use a custom DataSource instance as the source.
671      *     </p>
672      *
673      *     <p>
674      *     <strong>The <code>io-base</code> and <code>json-parse</code> modules
675      *     must be loaded in order for XHR URL sources to work.</strong> If
676      *     these modules are not already loaded, they will be loaded on demand
677      *     if possible.
678      *     </p>
679      *   </dd>
680      *
681      *   <dt>String (YQL query)</dt>
682      *   <dd>
683      *     <p>
684      *     <i>Example:</i> <code>'select * from search.suggest where query="{query}"'</code>
685      *     </p>
686      *
687      *     <p>
688      *     If a YQL query is provided, it will be used to make a YQL request.
689      *     The <code>{query}</code> placeholder will be replaced with the
690      *     current autocomplete query. This placeholder must appear in the YQL
691      *     query, or the request will fail. An optional
692      *     <code>{maxResults}</code> placeholder may also be provided, and will
693      *     be replaced with the value of the maxResults attribute (or 1000 if
694      *     the maxResults attribute is 0 or less).
695      *     </p>
696      *
697      *     <p>
698      *     <strong>The <code>yql</code> module must be loaded in order for YQL
699      *     sources to work.</strong> If the <code>yql</code> module is not
700      *     already loaded, it will be loaded on demand if possible.
701      *     </p>
702      *   </dd>
703      * </dl>
704      *
705      * <p>
706      * As an alternative to providing a source, you could simply listen for
707      * <code>query</code> events and handle them any way you see fit. Providing
708      * a source is optional, but will usually be simpler.
709      * </p>
710      *
711      * @attribute source
712      * @type Array|DataSource|Function|Object|String|null
713      */
714     source: {
715         setter: '_setSource'
716     },
717
718     /**
719      * If the <code>inputNode</code> specified at instantiation time has a
720      * <code>node-tokeninput</code> plugin attached to it, this attribute will
721      * be a reference to the <code>Y.Plugin.TokenInput</code> instance.
722      *
723      * @attribute tokenInput
724      * @type Plugin.TokenInput
725      * @readonly
726      */
727     tokenInput: {
728         readOnly: true
729     },
730
731     /**
732      * Current value of the input node.
733      *
734      * @attribute value
735      * @type String
736      * @default ''
737      */
738     value: {
739         // Why duplicate this._inputNode.get('value')? Because we need a
740         // reliable way to track the source of value changes. We want to perform
741         // completion when the user changes the value, but not when we change
742         // the value.
743         value: ''
744     }
745 };
746
747 AutoCompleteBase.CSS_PREFIX = 'ac';
748 AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
749
750 AutoCompleteBase.prototype = {
751     // -- Public Prototype Methods ---------------------------------------------
752
753     /**
754      * <p>
755      * Sends a request to the configured source. If no source is configured,
756      * this method won't do anything.
757      * </p>
758      *
759      * <p>
760      * Usually there's no reason to call this method manually; it will be
761      * called automatically when user input causes a <code>query</code> event to
762      * be fired. The only time you'll need to call this method manually is if
763      * you want to force a request to be sent when no user input has occurred.
764      * </p>
765      *
766      * @method sendRequest
767      * @param {String} query (optional) Query to send. If specified, the
768      *   <code>query</code> attribute will be set to this query. If not
769      *   specified, the current value of the <code>query</code> attribute will
770      *   be used.
771      * @param {Function} requestTemplate (optional) Request template function.
772      *   If not specified, the current value of the <code>requestTemplate</code>
773      *   attribute will be used.
774      * @chainable
775      */
776     sendRequest: function (query, requestTemplate) {
777         var request,
778             source = this.get('source');
779
780         if (query || query === '') {
781             this._set(QUERY, query);
782         } else {
783             query = this.get(QUERY);
784         }
785
786         if (source) {
787             if (!requestTemplate) {
788                 requestTemplate = this.get(REQUEST_TEMPLATE);
789             }
790
791             request = requestTemplate ? requestTemplate(query) : query;
792
793
794             source.sendRequest({
795                 request: request,
796                 callback: {
797                     success: Y.bind(this._onResponse, this, query)
798                 }
799             });
800         }
801
802         return this;
803     },
804
805     // -- Protected Lifecycle Methods ------------------------------------------
806
807     /**
808      * Attaches event listeners and behaviors.
809      *
810      * @method _bindUIACBase
811      * @protected
812      */
813     _bindUIACBase: function () {
814         var inputNode  = this.get(INPUT_NODE),
815             tokenInput = inputNode && inputNode.tokenInput;
816
817         // If the inputNode has a node-tokeninput plugin attached, bind to the
818         // plugin's inputNode instead.
819         if (tokenInput) {
820             inputNode = tokenInput.get(INPUT_NODE);
821             this._set('tokenInput', tokenInput);
822         }
823
824         if (!inputNode) {
825             Y.error('No inputNode specified.');
826             return;
827         }
828
829         this._inputNode = inputNode;
830
831         this._acBaseEvents = [
832             // This is the valueChange event on the inputNode, provided by the
833             // event-valuechange module, not our own valueChange.
834             inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
835
836             inputNode.on('blur', this._onInputBlur, this),
837
838             this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
839             this.after(VALUE_CHANGE, this._afterValueChange)
840         ];
841     },
842
843     /**
844      * Detaches AutoCompleteBase event listeners.
845      *
846      * @method _destructorACBase
847      * @protected
848      */
849     _destructorACBase: function () {
850         var events = this._acBaseEvents;
851
852         while (events && events.length) {
853             events.pop().detach();
854         }
855     },
856
857     /**
858      * Synchronizes the UI state of the <code>inputNode</code>.
859      *
860      * @method _syncUIACBase
861      * @protected
862      */
863     _syncUIACBase: function () {
864         this._syncBrowserAutocomplete();
865         this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
866     },
867
868     // -- Protected Prototype Methods ------------------------------------------
869
870     /**
871      * Creates a DataSource-like object that simply returns the specified array
872      * as a response. See the <code>source</code> attribute for more details.
873      *
874      * @method _createArraySource
875      * @param {Array} source
876      * @return {Object} DataSource-like object.
877      * @protected
878      */
879     _createArraySource: function (source) {
880         var that = this;
881
882         return {sendRequest: function (request) {
883             that[_SOURCE_SUCCESS](source.concat(), request);
884         }};
885     },
886
887     /**
888      * Creates a DataSource-like object that passes the query to a
889      * custom-defined function, which is expected to return an array as a
890      * response. See the <code>source</code> attribute for more details.
891      *
892      * @method _createFunctionSource
893      * @param {Function} source Function that accepts a query parameter and
894      *   returns an array of results.
895      * @return {Object} DataSource-like object.
896      * @protected
897      */
898     _createFunctionSource: function (source) {
899         var that = this;
900
901         return {sendRequest: function (request) {
902             that[_SOURCE_SUCCESS](source(request.request) || [], request);
903         }};
904     },
905
906     /**
907      * Creates a DataSource-like object that looks up queries as properties on
908      * the specified object, and returns the found value (if any) as a response.
909      * See the <code>source</code> attribute for more details.
910      *
911      * @method _createObjectSource
912      * @param {Object} source
913      * @return {Object} DataSource-like object.
914      * @protected
915      */
916     _createObjectSource: function (source) {
917         var that = this;
918
919         return {sendRequest: function (request) {
920             var query = request.request;
921
922             that[_SOURCE_SUCCESS](
923                 YObject.owns(source, query) ? source[query] : [],
924                 request
925             );
926         }};
927     },
928
929     /**
930      * Returns <code>true</code> if <i>value</i> is either a function or
931      * <code>null</code>.
932      *
933      * @method _functionValidator
934      * @param {Function|null} value Value to validate.
935      * @protected
936      */
937     _functionValidator: function (value) {
938         return value === null || isFunction(value);
939     },
940
941     /**
942      * Faster and safer alternative to Y.Object.getValue(). Doesn't bother
943      * casting the path to an array (since we already know it's an array) and
944      * doesn't throw an error if a value in the middle of the object hierarchy
945      * is neither <code>undefined</code> nor an object.
946      *
947      * @method _getObjectValue
948      * @param {Object} obj
949      * @param {Array} path
950      * @return {mixed} Located value, or <code>undefined</code> if the value was
951      *   not found at the specified path.
952      * @protected
953      */
954     _getObjectValue: function (obj, path) {
955         if (!obj) {
956             return;
957         }
958
959         for (var i = 0, len = path.length; obj && i < len; i++) {
960             obj = obj[path[i]];
961         }
962
963         return obj;
964     },
965
966     /**
967      * Parses result responses, performs filtering and highlighting, and fires
968      * the <code>results</code> event.
969      *
970      * @method _parseResponse
971      * @param {String} query Query that generated these results.
972      * @param {Object} response Response containing results.
973      * @param {Object} data Raw response data.
974      * @protected
975      */
976     _parseResponse: function (query, response, data) {
977         var facade = {
978                 data   : data,
979                 query  : query,
980                 results: []
981             },
982
983             listLocator = this.get(RESULT_LIST_LOCATOR),
984             results     = [],
985             unfiltered  = response && response.results,
986
987             filters,
988             formatted,
989             formatter,
990             highlighted,
991             highlighter,
992             i,
993             len,
994             maxResults,
995             result,
996             text,
997             textLocator;
998
999         if (unfiltered && listLocator) {
1000             unfiltered = listLocator(unfiltered);
1001         }
1002
1003         if (unfiltered && unfiltered.length) {
1004             filters     = this.get('resultFilters');
1005             textLocator = this.get('resultTextLocator');
1006
1007             // Create a lightweight result object for each result to make them
1008             // easier to work with. The various properties on the object
1009             // represent different formats of the result, and will be populated
1010             // as we go.
1011             for (i = 0, len = unfiltered.length; i < len; ++i) {
1012                 result = unfiltered[i];
1013                 text   = textLocator ? textLocator(result) : result.toString();
1014
1015                 results.push({
1016                     display: Escape.html(text),
1017                     raw    : result,
1018                     text   : text
1019                 });
1020             }
1021
1022             // Run the results through all configured result filters. Each
1023             // filter returns an array of (potentially fewer) result objects,
1024             // which is then passed to the next filter, and so on.
1025             for (i = 0, len = filters.length; i < len; ++i) {
1026                 results = filters[i](query, results.concat());
1027
1028                 if (!results) {
1029                     return;
1030                 }
1031
1032                 if (!results.length) {
1033                     break;
1034                 }
1035             }
1036
1037             if (results.length) {
1038                 formatter   = this.get('resultFormatter');
1039                 highlighter = this.get('resultHighlighter');
1040                 maxResults  = this.get('maxResults');
1041
1042                 // If maxResults is set and greater than 0, limit the number of
1043                 // results.
1044                 if (maxResults && maxResults > 0 &&
1045                         results.length > maxResults) {
1046                     results.length = maxResults;
1047                 }
1048
1049                 // Run the results through the configured highlighter (if any).
1050                 // The highlighter returns an array of highlighted strings (not
1051                 // an array of result objects), and these strings are then added
1052                 // to each result object.
1053                 if (highlighter) {
1054                     highlighted = highlighter(query, results.concat());
1055
1056                     if (!highlighted) {
1057                         return;
1058                     }
1059
1060                     for (i = 0, len = highlighted.length; i < len; ++i) {
1061                         result = results[i];
1062                         result.highlighted = highlighted[i];
1063                         result.display     = result.highlighted;
1064                     }
1065                 }
1066
1067                 // Run the results through the configured formatter (if any) to
1068                 // produce the final formatted results. The formatter returns an
1069                 // array of strings or Node instances (not an array of result
1070                 // objects), and these strings/Nodes are then added to each
1071                 // result object.
1072                 if (formatter) {
1073                     formatted = formatter(query, results.concat());
1074
1075                     if (!formatted) {
1076                         return;
1077                     }
1078
1079                     for (i = 0, len = formatted.length; i < len; ++i) {
1080                         results[i].display = formatted[i];
1081                     }
1082                 }
1083             }
1084         }
1085
1086         facade.results = results;
1087         this.fire(EVT_RESULTS, facade);
1088     },
1089
1090     /**
1091      * <p>
1092      * Returns the query portion of the specified input value, or
1093      * <code>null</code> if there is no suitable query within the input value.
1094      * </p>
1095      *
1096      * <p>
1097      * If a query delimiter is defined, the query will be the last delimited
1098      * part of of the string.
1099      * </p>
1100      *
1101      * @method _parseValue
1102      * @param {String} value Input value from which to extract the query.
1103      * @return {String|null} query
1104      * @protected
1105      */
1106     _parseValue: function (value) {
1107         var delim = this.get(QUERY_DELIMITER);
1108
1109         if (delim) {
1110             value = value.split(delim);
1111             value = value[value.length - 1];
1112         }
1113
1114         return Lang.trimLeft(value);
1115     },
1116
1117     /**
1118      * Setter for locator attributes.
1119      *
1120      * @method _setLocator
1121      * @param {Function|String|null} locator
1122      * @return {Function|null}
1123      * @protected
1124      */
1125     _setLocator: function (locator) {
1126         if (this[_FUNCTION_VALIDATOR](locator)) {
1127             return locator;
1128         }
1129
1130         var that = this;
1131
1132         locator = locator.toString().split('.');
1133
1134         return function (result) {
1135             return result && that._getObjectValue(result, locator);
1136         };
1137     },
1138
1139     /**
1140      * Setter for the <code>requestTemplate</code> attribute.
1141      *
1142      * @method _setRequestTemplate
1143      * @param {Function|String|null} template
1144      * @return {Function|null}
1145      * @protected
1146      */
1147     _setRequestTemplate: function (template) {
1148         if (this[_FUNCTION_VALIDATOR](template)) {
1149             return template;
1150         }
1151
1152         template = template.toString();
1153
1154         return function (query) {
1155             return Lang.sub(template, {query: encodeURIComponent(query)});
1156         };
1157     },
1158
1159     /**
1160      * Setter for the <code>resultFilters</code> attribute.
1161      *
1162      * @method _setResultFilters
1163      * @param {Array|Function|String|null} filters <code>null</code>, a filter
1164      *   function, an array of filter functions, or a string or array of strings
1165      *   representing the names of methods on
1166      *   <code>Y.AutoCompleteFilters</code>.
1167      * @return {Array} Array of filter functions (empty if <i>filters</i> is
1168      *   <code>null</code>).
1169      * @protected
1170      */
1171     _setResultFilters: function (filters) {
1172         var acFilters, getFilterFunction;
1173
1174         if (filters === null) {
1175             return [];
1176         }
1177
1178         acFilters = Y.AutoCompleteFilters;
1179
1180         getFilterFunction = function (filter) {
1181             if (isFunction(filter)) {
1182                 return filter;
1183             }
1184
1185             if (isString(filter) && acFilters &&
1186                     isFunction(acFilters[filter])) {
1187                 return acFilters[filter];
1188             }
1189
1190             return false;
1191         };
1192
1193         if (Lang.isArray(filters)) {
1194             filters = YArray.map(filters, getFilterFunction);
1195             return YArray.every(filters, function (f) { return !!f; }) ?
1196                     filters : INVALID_VALUE;
1197         } else {
1198             filters = getFilterFunction(filters);
1199             return filters ? [filters] : INVALID_VALUE;
1200         }
1201     },
1202
1203     /**
1204      * Setter for the <code>resultHighlighter</code> attribute.
1205      *
1206      * @method _setResultHighlighter
1207      * @param {Function|String|null} highlighter <code>null</code>, a
1208      *   highlighter function, or a string representing the name of a method on
1209      *   <code>Y.AutoCompleteHighlighters</code>.
1210      * @return {Function|null}
1211      * @protected
1212      */
1213     _setResultHighlighter: function (highlighter) {
1214         var acHighlighters;
1215
1216         if (this._functionValidator(highlighter)) {
1217             return highlighter;
1218         }
1219
1220         acHighlighters = Y.AutoCompleteHighlighters;
1221
1222         if (isString(highlighter) && acHighlighters &&
1223                 isFunction(acHighlighters[highlighter])) {
1224             return acHighlighters[highlighter];
1225         }
1226
1227         return INVALID_VALUE;
1228     },
1229
1230     /**
1231      * Setter for the <code>source</code> attribute. Returns a DataSource or
1232      * a DataSource-like object depending on the type of <i>source</i>.
1233      *
1234      * @method _setSource
1235      * @param {Array|DataSource|Object|String} source AutoComplete source. See
1236      *   the <code>source</code> attribute for details.
1237      * @return {DataSource|Object}
1238      * @protected
1239      */
1240     _setSource: function (source) {
1241         var sourcesNotLoaded = 'autocomplete-sources module not loaded';
1242
1243         if ((source && isFunction(source.sendRequest)) || source === null) {
1244             // Quacks like a DataSource instance (or null). Make it so!
1245             return source;
1246         }
1247
1248         switch (Lang.type(source)) {
1249         case 'string':
1250             if (this._createStringSource) {
1251                 return this._createStringSource(source);
1252             }
1253
1254             Y.error(sourcesNotLoaded);
1255             return INVALID_VALUE;
1256
1257         case 'array':
1258             // Wrap the array in a teensy tiny fake DataSource that just returns
1259             // the array itself for each request. Filters will do the rest.
1260             return this._createArraySource(source);
1261
1262         case 'function':
1263             return this._createFunctionSource(source);
1264
1265         case 'object':
1266             // If the object is a JSONPRequest instance, use it as a JSONP
1267             // source.
1268             if (Y.JSONPRequest && source instanceof Y.JSONPRequest) {
1269                 if (this._createJSONPSource) {
1270                     return this._createJSONPSource(source);
1271                 }
1272
1273                 Y.error(sourcesNotLoaded);
1274                 return INVALID_VALUE;
1275             }
1276
1277             // Not a JSONPRequest instance. Wrap the object in a teensy tiny
1278             // fake DataSource that looks for the request as a property on the
1279             // object and returns it if it exists, or an empty array otherwise.
1280             return this._createObjectSource(source);
1281         }
1282
1283         return INVALID_VALUE;
1284     },
1285
1286     /**
1287      * Shared success callback for non-DataSource sources.
1288      *
1289      * @method _sourceSuccess
1290      * @param {mixed} data Response data.
1291      * @param {Object} request Request object.
1292      * @protected
1293      */
1294     _sourceSuccess: function (data, request) {
1295         request.callback.success({
1296             data: data,
1297             response: {results: data}
1298         });
1299     },
1300
1301     /**
1302      * Synchronizes the UI state of the <code>allowBrowserAutocomplete</code>
1303      * attribute.
1304      *
1305      * @method _syncBrowserAutocomplete
1306      * @protected
1307      */
1308     _syncBrowserAutocomplete: function () {
1309         var inputNode = this.get(INPUT_NODE);
1310
1311         if (inputNode.get('nodeName').toLowerCase() === 'input') {
1312             inputNode.setAttribute('autocomplete',
1313                     this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
1314         }
1315     },
1316
1317     /**
1318      * <p>
1319      * Updates the query portion of the <code>value</code> attribute.
1320      * </p>
1321      *
1322      * <p>
1323      * If a query delimiter is defined, the last delimited portion of the input
1324      * value will be replaced with the specified <i>value</i>.
1325      * </p>
1326      *
1327      * @method _updateValue
1328      * @param {String} newVal New value.
1329      * @protected
1330      */
1331     _updateValue: function (newVal) {
1332         var delim = this.get(QUERY_DELIMITER),
1333             insertDelim,
1334             len,
1335             prevVal;
1336
1337         newVal = Lang.trimLeft(newVal);
1338
1339         if (delim) {
1340             insertDelim = trim(delim); // so we don't double up on spaces
1341             prevVal     = YArray.map(trim(this.get(VALUE)).split(delim), trim);
1342             len         = prevVal.length;
1343
1344             if (len > 1) {
1345                 prevVal[len - 1] = newVal;
1346                 newVal = prevVal.join(insertDelim + ' ');
1347             }
1348
1349             newVal = newVal + insertDelim + ' ';
1350         }
1351
1352         this.set(VALUE, newVal);
1353     },
1354
1355     // -- Protected Event Handlers ---------------------------------------------
1356
1357     /**
1358      * Handles change events for the <code>value</code> attribute.
1359      *
1360      * @method _afterValueChange
1361      * @param {EventFacade} e
1362      * @protected
1363      */
1364     _afterValueChange: function (e) {
1365         var delay,
1366             fire,
1367             minQueryLength,
1368             newVal = e.newVal,
1369             query,
1370             that;
1371
1372         // Don't query on value changes that didn't come from the user.
1373         if (e.src !== AutoCompleteBase.UI_SRC) {
1374             this._inputNode.set(VALUE, newVal);
1375             return;
1376         }
1377
1378
1379         minQueryLength = this.get('minQueryLength');
1380         query          = this._parseValue(newVal) || '';
1381
1382         if (minQueryLength >= 0 && query.length >= minQueryLength) {
1383             delay = this.get('queryDelay');
1384             that  = this;
1385
1386             fire = function () {
1387                 that.fire(EVT_QUERY, {
1388                     inputValue: newVal,
1389                     query     : query
1390                 });
1391             };
1392
1393             if (delay) {
1394                 clearTimeout(this._delay);
1395                 this._delay = setTimeout(fire, delay);
1396             } else {
1397                 fire();
1398             }
1399         } else {
1400             clearTimeout(this._delay);
1401
1402             this.fire(EVT_CLEAR, {
1403                 prevVal: e.prevVal ? this._parseValue(e.prevVal) : null
1404             });
1405         }
1406     },
1407
1408     /**
1409      * Handles <code>blur</code> events on the input node.
1410      *
1411      * @method _onInputBlur
1412      * @param {EventFacade} e
1413      * @protected
1414      */
1415     _onInputBlur: function (e) {
1416         var delim = this.get(QUERY_DELIMITER),
1417             delimPos,
1418             newVal,
1419             value;
1420
1421         // If a query delimiter is set and the input's value contains one or
1422         // more trailing delimiters, strip them.
1423         if (delim && !this.get('allowTrailingDelimiter')) {
1424             delim = Lang.trimRight(delim);
1425             value = newVal = this._inputNode.get(VALUE);
1426
1427             if (delim) {
1428                 while ((newVal = Lang.trimRight(newVal)) &&
1429                         (delimPos = newVal.length - delim.length) &&
1430                         newVal.lastIndexOf(delim) === delimPos) {
1431
1432                     newVal = newVal.substring(0, delimPos);
1433                 }
1434             } else {
1435                 // Delimiter is one or more space characters, so just trim the
1436                 // value.
1437                 newVal = Lang.trimRight(newVal);
1438             }
1439
1440             if (newVal !== value) {
1441                 this.set(VALUE, newVal);
1442             }
1443         }
1444     },
1445
1446     /**
1447      * Handles <code>valueChange</code> events on the input node and fires a
1448      * <code>query</code> event when the input value meets the configured
1449      * criteria.
1450      *
1451      * @method _onInputValueChange
1452      * @param {EventFacade} e
1453      * @protected
1454      */
1455     _onInputValueChange: function (e) {
1456         var newVal = e.newVal;
1457
1458         // Don't query if the internal value is the same as the new value
1459         // reported by valueChange.
1460         if (newVal === this.get(VALUE)) {
1461             return;
1462         }
1463
1464         this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
1465     },
1466
1467     /**
1468      * Handles source responses and fires the <code>results</code> event.
1469      *
1470      * @method _onResponse
1471      * @param {EventFacade} e
1472      * @protected
1473      */
1474     _onResponse: function (query, e) {
1475         // Ignore stale responses that aren't for the current query.
1476         if (query === this.get(QUERY)) {
1477             this._parseResponse(query, e.response, e.data);
1478         }
1479     },
1480
1481     // -- Protected Default Event Handlers -------------------------------------
1482
1483     /**
1484      * Default <code>clear</code> event handler. Sets the <code>results</code>
1485      * property to an empty array and <code>query</code> to null.
1486      *
1487      * @method _defClearFn
1488      * @protected
1489      */
1490     _defClearFn: function () {
1491         this._set(QUERY, null);
1492         this._set(RESULTS, []);
1493     },
1494
1495     /**
1496      * Default <code>query</code> event handler. Sets the <code>query</code>
1497      * property and sends a request to the source if one is configured.
1498      *
1499      * @method _defQueryFn
1500      * @param {EventFacade} e
1501      * @protected
1502      */
1503     _defQueryFn: function (e) {
1504         var query = e.query;
1505
1506         this.sendRequest(query); // sendRequest will set the 'query' attribute
1507     },
1508
1509     /**
1510      * Default <code>results</code> event handler. Sets the <code>results</code>
1511      * property to the latest results.
1512      *
1513      * @method _defResultsFn
1514      * @param {EventFacade} e
1515      * @protected
1516      */
1517     _defResultsFn: function (e) {
1518         this._set(RESULTS, e[RESULTS]);
1519     }
1520 };
1521
1522 Y.AutoCompleteBase = AutoCompleteBase;
1523
1524
1525 }, '3.3.0' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']});
1526 YUI.add('autocomplete-sources', function(Y) {
1527
1528 /**
1529  * Mixes support for JSONP and YQL result sources into AutoCompleteBase.
1530  *
1531  * @module autocomplete
1532  * @submodule autocomplete-sources
1533  */
1534
1535 var Lang = Y.Lang,
1536
1537     _SOURCE_SUCCESS = '_sourceSuccess',
1538
1539     MAX_RESULTS         = 'maxResults',
1540     REQUEST_TEMPLATE    = 'requestTemplate',
1541     RESULT_LIST_LOCATOR = 'resultListLocator';
1542
1543 function ACSources() {}
1544
1545 ACSources.prototype = {
1546     /**
1547      * Regular expression used to determine whether a String source is a YQL
1548      * query.
1549      *
1550      * @property _YQL_SOURCE_REGEX
1551      * @type RegExp
1552      * @protected
1553      * @for AutoCompleteBase
1554      */
1555     _YQL_SOURCE_REGEX: /^(?:select|set|use)\s+/i,
1556
1557     /**
1558      * Creates a DataSource-like object that uses <code>Y.io</code> as a source.
1559      * See the <code>source</code> attribute for more details.
1560      *
1561      * @method _createIOSource
1562      * @param {String} source URL.
1563      * @return {Object} DataSource-like object.
1564      * @protected
1565      * @for AutoCompleteBase
1566      */
1567     _createIOSource: function (source) {
1568         var cache    = {},
1569             ioSource = {},
1570             that     = this,
1571             ioRequest, lastRequest, loading;
1572
1573         ioSource.sendRequest = function (request) {
1574             var _sendRequest = function (request) {
1575                 var query = request.request,
1576                     maxResults, requestTemplate, url;
1577
1578                 if (cache[query]) {
1579                     that[_SOURCE_SUCCESS](cache[query], request);
1580                 } else {
1581                     maxResults      = that.get(MAX_RESULTS);
1582                     requestTemplate = that.get(REQUEST_TEMPLATE);
1583                     url             = source;
1584
1585                     if (requestTemplate) {
1586                         url += requestTemplate(query);
1587                     }
1588
1589                     url = Lang.sub(url, {
1590                         maxResults: maxResults > 0 ? maxResults : 1000,
1591                         query     : encodeURIComponent(query)
1592                     });
1593
1594                     // Cancel any outstanding requests.
1595                     if (ioRequest && ioRequest.isInProgress()) {
1596                         ioRequest.abort();
1597                     }
1598
1599                     ioRequest = Y.io(url, {
1600                         on: {
1601                             success: function (tid, response) {
1602                                 var data;
1603
1604                                 try {
1605                                     data = Y.JSON.parse(response.responseText);
1606                                 } catch (ex) {
1607                                     Y.error('JSON parse error', ex);
1608                                 }
1609
1610                                 if (data) {
1611                                     cache[query] = data;
1612                                     that[_SOURCE_SUCCESS](data, request);
1613                                 }
1614                             }
1615                         }
1616                     });
1617                 }
1618             };
1619
1620             // Keep track of the most recent request in case there are multiple
1621             // requests while we're waiting for the IO module to load. Only the
1622             // most recent request will be sent.
1623             lastRequest = request;
1624
1625             if (!loading) {
1626                 loading = true;
1627
1628                 // Lazy-load the io and json-parse modules if necessary, then
1629                 // overwrite the sendRequest method to bypass this check in the
1630                 // future.
1631                 Y.use('io-base', 'json-parse', function () {
1632                     ioSource.sendRequest = _sendRequest;
1633                     _sendRequest(lastRequest);
1634                 });
1635             }
1636         };
1637
1638         return ioSource;
1639     },
1640
1641     /**
1642      * Creates a DataSource-like object that uses the specified JSONPRequest
1643      * instance as a source. See the <code>source</code> attribute for more
1644      * details.
1645      *
1646      * @method _createJSONPSource
1647      * @param {JSONPRequest|String} source URL string or JSONPRequest instance.
1648      * @return {Object} DataSource-like object.
1649      * @protected
1650      * @for AutoCompleteBase
1651      */
1652     _createJSONPSource: function (source) {
1653         var cache       = {},
1654             jsonpSource = {},
1655             that        = this,
1656             lastRequest, loading;
1657
1658         jsonpSource.sendRequest = function (request) {
1659             var _sendRequest = function (request) {
1660                 var query = request.request;
1661
1662                 if (cache[query]) {
1663                     that[_SOURCE_SUCCESS](cache[query], request);
1664                 } else {
1665                     // Hack alert: JSONPRequest currently doesn't support
1666                     // per-request callbacks, so we're reaching into the protected
1667                     // _config object to make it happen.
1668                     //
1669                     // This limitation is mentioned in the following JSONP
1670                     // enhancement ticket:
1671                     //
1672                     // http://yuilibrary.com/projects/yui3/ticket/2529371
1673                     source._config.on.success = function (data) {
1674                         cache[query] = data;
1675                         that[_SOURCE_SUCCESS](data, request);
1676                     };
1677
1678                     source.send(query);
1679                 }
1680             };
1681
1682             // Keep track of the most recent request in case there are multiple
1683             // requests while we're waiting for the JSONP module to load. Only
1684             // the most recent request will be sent.
1685             lastRequest = request;
1686
1687             if (!loading) {
1688                 loading = true;
1689
1690                 // Lazy-load the JSONP module if necessary, then overwrite the
1691                 // sendRequest method to bypass this check in the future.
1692                 Y.use('jsonp', function () {
1693                     // Turn the source into a JSONPRequest instance if it isn't
1694                     // one already.
1695                     if (!(source instanceof Y.JSONPRequest)) {
1696                         source = new Y.JSONPRequest(source, {
1697                             format: Y.bind(that._jsonpFormatter, that)
1698                         });
1699                     }
1700
1701                     jsonpSource.sendRequest = _sendRequest;
1702                     _sendRequest(lastRequest);
1703                 });
1704             }
1705         };
1706
1707         return jsonpSource;
1708     },
1709
1710     /**
1711      * Creates a DataSource-like object that calls the specified  URL or
1712      * executes the specified YQL query for results. If the string starts
1713      * with "select ", "use ", or "set " (case-insensitive), it's assumed to be
1714      * a YQL query; otherwise, it's assumed to be a URL (which may be absolute
1715      * or relative). URLs containing a "{callback}" placeholder are assumed to
1716      * be JSONP URLs; all others will use XHR. See the <code>source</code>
1717      * attribute for more details.
1718      *
1719      * @method _createStringSource
1720      * @param {String} source URL or YQL query.
1721      * @return {Object} DataSource-like object.
1722      * @protected
1723      * @for AutoCompleteBase
1724      */
1725     _createStringSource: function (source) {
1726         if (this._YQL_SOURCE_REGEX.test(source)) {
1727             // Looks like a YQL query.
1728             return this._createYQLSource(source);
1729         } else if (source.indexOf('{callback}') !== -1) {
1730             // Contains a {callback} param and isn't a YQL query, so it must be
1731             // JSONP.
1732             return this._createJSONPSource(source);
1733         } else {
1734             // Not a YQL query or JSONP, so we'll assume it's an XHR URL.
1735             return this._createIOSource(source);
1736         }
1737     },
1738
1739     /**
1740      * Creates a DataSource-like object that uses the specified YQL query string
1741      * to create a YQL-based source. See the <code>source</code> attribute for
1742      * details. If no <code>resultListLocator</code> is defined, this method
1743      * will set a best-guess locator that might work for many typical YQL
1744      * queries.
1745      *
1746      * @method _createYQLSource
1747      * @param {String} source YQL query.
1748      * @return {Object} DataSource-like object.
1749      * @protected
1750      * @for AutoCompleteBase
1751      */
1752     _createYQLSource: function (source) {
1753         var cache     = {},
1754             yqlSource = {},
1755             that      = this,
1756             lastRequest, loading;
1757
1758         if (!this.get(RESULT_LIST_LOCATOR)) {
1759             this.set(RESULT_LIST_LOCATOR, this._defaultYQLLocator);
1760         }
1761
1762         yqlSource.sendRequest = function (request) {
1763             var yqlRequest,
1764
1765             _sendRequest = function (request) {
1766                 var query = request.request,
1767                     callback, env, maxResults, opts, yqlQuery;
1768
1769                 if (cache[query]) {
1770                     that[_SOURCE_SUCCESS](cache[query], request);
1771                 } else {
1772                     callback = function (data) {
1773                         cache[query] = data;
1774                         that[_SOURCE_SUCCESS](data, request);
1775                     };
1776
1777                     env        = that.get('yqlEnv');
1778                     maxResults = that.get(MAX_RESULTS);
1779
1780                     opts = {proto: that.get('yqlProtocol')};
1781
1782                     yqlQuery = Lang.sub(source, {
1783                         maxResults: maxResults > 0 ? maxResults : 1000,
1784                         query     : query
1785                     });
1786
1787                     // Only create a new YQLRequest instance if this is the
1788                     // first request. For subsequent requests, we'll reuse the
1789                     // original instance.
1790                     if (yqlRequest) {
1791                         yqlRequest._callback   = callback;
1792                         yqlRequest._opts       = opts;
1793                         yqlRequest._params.q   = yqlQuery;
1794
1795                         if (env) {
1796                             yqlRequest._params.env = env;
1797                         }
1798                     } else {
1799                         yqlRequest = new Y.YQLRequest(yqlQuery, {
1800                             on: {success: callback},
1801                             allowCache: false // temp workaround until JSONP has per-URL callback proxies
1802                         }, env ? {env: env} : null, opts);
1803                     }
1804
1805                     yqlRequest.send();
1806                 }
1807             };
1808
1809             // Keep track of the most recent request in case there are multiple
1810             // requests while we're waiting for the YQL module to load. Only the
1811             // most recent request will be sent.
1812             lastRequest = request;
1813
1814             if (!loading) {
1815                 // Lazy-load the YQL module if necessary, then overwrite the
1816                 // sendRequest method to bypass this check in the future.
1817                 loading = true;
1818
1819                 Y.use('yql', function () {
1820                     yqlSource.sendRequest = _sendRequest;
1821                     _sendRequest(lastRequest);
1822                 });
1823             }
1824         };
1825
1826         return yqlSource;
1827     },
1828
1829     /**
1830      * Default resultListLocator used when a string-based YQL source is set and
1831      * the implementer hasn't already specified one.
1832      *
1833      * @method _defaultYQLLocator
1834      * @param {Object} response YQL response object.
1835      * @return {Array}
1836      * @protected
1837      * @for AutoCompleteBase
1838      */
1839     _defaultYQLLocator: function (response) {
1840         var results = response && response.query && response.query.results,
1841             values;
1842
1843         if (results && Lang.isObject(results)) {
1844             // If there's only a single value on YQL's results object, that
1845             // value almost certainly contains the array of results we want. If
1846             // there are 0 or 2+ values, then the values themselves are most
1847             // likely the results we want.
1848             values  = Y.Object.values(results) || [];
1849             results = values.length === 1 ? values[0] : values;
1850
1851             if (!Lang.isArray(results)) {
1852                 results = [results];
1853             }
1854         } else {
1855             results = [];
1856         }
1857
1858         return results;
1859     },
1860
1861     /**
1862      * URL formatter passed to <code>JSONPRequest</code> instances.
1863      *
1864      * @method _jsonpFormatter
1865      * @param {String} url
1866      * @param {String} proxy
1867      * @param {String} query
1868      * @return {String} Formatted URL
1869      * @protected
1870      * @for AutoCompleteBase
1871      */
1872     _jsonpFormatter: function (url, proxy, query) {
1873         var maxResults      = this.get(MAX_RESULTS),
1874             requestTemplate = this.get(REQUEST_TEMPLATE);
1875
1876         if (requestTemplate) {
1877             url += requestTemplate(query);
1878         }
1879
1880         return Lang.sub(url, {
1881             callback  : proxy,
1882             maxResults: maxResults > 0 ? maxResults : 1000,
1883             query     : encodeURIComponent(query)
1884         });
1885     }
1886 };
1887
1888 ACSources.ATTRS = {
1889     /**
1890      * YQL environment file URL to load when the <code>source</code> is set to
1891      * a YQL query. Set this to <code>null</code> to use the default Open Data
1892      * Tables environment file (http://datatables.org/alltables.env).
1893      *
1894      * @attribute yqlEnv
1895      * @type String
1896      * @default null
1897      * @for AutoCompleteBase
1898      */
1899     yqlEnv: {
1900         value: null
1901     },
1902
1903     /**
1904      * URL protocol to use when the <code>source</code> is set to a YQL query.
1905      *
1906      * @attribute yqlProtocol
1907      * @type String
1908      * @default 'http'
1909      * @for AutoCompleteBase
1910      */
1911     yqlProtocol: {
1912         value: 'http'
1913     }
1914 };
1915
1916 Y.Base.mix(Y.AutoCompleteBase, [ACSources]);
1917
1918
1919 }, '3.3.0' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']});
1920 YUI.add('autocomplete-list', function(Y) {
1921
1922 /**
1923  * Traditional autocomplete dropdown list widget, just like Mom used to make.
1924  *
1925  * @module autocomplete
1926  * @submodule autocomplete-list
1927  * @class AutoCompleteList
1928  * @extends Widget
1929  * @uses AutoCompleteBase
1930  * @uses WidgetPosition
1931  * @uses WidgetPositionAlign
1932  * @uses WidgetStack
1933  * @constructor
1934  * @param {Object} config Configuration object.
1935  */
1936
1937 var Lang   = Y.Lang,
1938     Node   = Y.Node,
1939     YArray = Y.Array,
1940
1941     // keyCode constants.
1942     KEY_TAB = 9,
1943
1944     // String shorthand.
1945     _CLASS_ITEM        = '_CLASS_ITEM',
1946     _CLASS_ITEM_ACTIVE = '_CLASS_ITEM_ACTIVE',
1947     _CLASS_ITEM_HOVER  = '_CLASS_ITEM_HOVER',
1948     _SELECTOR_ITEM     = '_SELECTOR_ITEM',
1949
1950     ACTIVE_ITEM      = 'activeItem',
1951     ALWAYS_SHOW_LIST = 'alwaysShowList',
1952     CIRCULAR         = 'circular',
1953     HOVERED_ITEM     = 'hoveredItem',
1954     ID               = 'id',
1955     ITEM             = 'item',
1956     LIST             = 'list',
1957     RESULT           = 'result',
1958     RESULTS          = 'results',
1959     VISIBLE          = 'visible',
1960     WIDTH            = 'width',
1961
1962     // Event names.
1963     EVT_SELECT = 'select',
1964
1965 List = Y.Base.create('autocompleteList', Y.Widget, [
1966     Y.AutoCompleteBase,
1967     Y.WidgetPosition,
1968     Y.WidgetPositionAlign,
1969     Y.WidgetStack
1970 ], {
1971     // -- Prototype Properties -------------------------------------------------
1972     ARIA_TEMPLATE: '<div/>',
1973     ITEM_TEMPLATE: '<li/>',
1974     LIST_TEMPLATE: '<ul/>',
1975
1976     // -- Lifecycle Prototype Methods ------------------------------------------
1977     initializer: function () {
1978         var inputNode = this.get('inputNode');
1979
1980         if (!inputNode) {
1981             Y.error('No inputNode specified.');
1982             return;
1983         }
1984
1985         this._inputNode  = inputNode;
1986         this._listEvents = [];
1987
1988         // This ensures that the list is rendered inside the same parent as the
1989         // input node by default, which is necessary for proper ARIA support.
1990         this.DEF_PARENT_NODE = inputNode.get('parentNode');
1991
1992         // Cache commonly used classnames and selectors for performance.
1993         this[_CLASS_ITEM]        = this.getClassName(ITEM);
1994         this[_CLASS_ITEM_ACTIVE] = this.getClassName(ITEM, 'active');
1995         this[_CLASS_ITEM_HOVER]  = this.getClassName(ITEM, 'hover');
1996         this[_SELECTOR_ITEM]     = '.' + this[_CLASS_ITEM];
1997
1998         /**
1999          * Fires when an autocomplete suggestion is selected from the list,
2000          * typically via a keyboard action or mouse click.
2001          *
2002          * @event select
2003          * @param {EventFacade} e Event facade with the following additional
2004          *   properties:
2005          *
2006          * <dl>
2007          *   <dt>itemNode (Node)</dt>
2008          *   <dd>
2009          *     List item node that was selected.
2010          *   </dd>
2011          *
2012          *   <dt>result (Object)</dt>
2013          *   <dd>
2014          *     AutoComplete result object.
2015          *   </dd>
2016          * </dl>
2017          *
2018          * @preventable _defSelectFn
2019          */
2020         this.publish(EVT_SELECT, {
2021             defaultFn: this._defSelectFn
2022         });
2023     },
2024
2025     destructor: function () {
2026         while (this._listEvents.length) {
2027             this._listEvents.pop().detach();
2028         }
2029     },
2030
2031     bindUI: function () {
2032         this._bindInput();
2033         this._bindList();
2034     },
2035
2036     renderUI: function () {
2037         var ariaNode   = this._createAriaNode(),
2038             contentBox = this.get('contentBox'),
2039             inputNode  = this._inputNode,
2040             listNode,
2041             parentNode = inputNode.get('parentNode');
2042
2043         listNode = this._createListNode();
2044         this._set('listNode', listNode);
2045         contentBox.append(listNode);
2046
2047         inputNode.addClass(this.getClassName('input')).setAttrs({
2048             'aria-autocomplete': LIST,
2049             'aria-expanded'    : false,
2050             'aria-owns'        : listNode.get('id'),
2051             role               : 'combobox'
2052         });
2053
2054         // ARIA node must be outside the widget or announcements won't be made
2055         // when the widget is hidden.
2056         parentNode.append(ariaNode);
2057
2058         this._ariaNode    = ariaNode;
2059         this._boundingBox = this.get('boundingBox');
2060         this._contentBox  = contentBox;
2061         this._listNode    = listNode;
2062         this._parentNode  = parentNode;
2063     },
2064
2065     syncUI: function () {
2066         this._syncResults();
2067         this._syncVisibility();
2068     },
2069
2070     // -- Public Prototype Methods ---------------------------------------------
2071
2072     /**
2073      * Hides the list, unless the <code>alwaysShowList</code> attribute is
2074      * <code>true</code>.
2075      *
2076      * @method hide
2077      * @see show
2078      * @chainable
2079      */
2080     hide: function () {
2081         return this.get(ALWAYS_SHOW_LIST) ? this : this.set(VISIBLE, false);
2082     },
2083
2084     /**
2085      * Selects the specified <i>itemNode</i>, or the current
2086      * <code>activeItem</code> if <i>itemNode</i> is not specified.
2087      *
2088      * @method selectItem
2089      * @param {Node} itemNode (optional) Item node to select.
2090      * @chainable
2091      */
2092     selectItem: function (itemNode) {
2093         if (itemNode) {
2094             if (!itemNode.hasClass(this[_CLASS_ITEM])) {
2095                 return this;
2096             }
2097         } else {
2098             itemNode = this.get(ACTIVE_ITEM);
2099
2100             if (!itemNode) {
2101                 return this;
2102             }
2103         }
2104
2105         this.fire(EVT_SELECT, {
2106             itemNode: itemNode,
2107             result  : itemNode.getData(RESULT)
2108         });
2109
2110         return this;
2111     },
2112
2113     // -- Protected Prototype Methods ------------------------------------------
2114
2115     /**
2116      * Activates the next item after the currently active item. If there is no
2117      * next item and the <code>circular</code> attribute is <code>true</code>,
2118      * focus will wrap back to the input node.
2119      *
2120      * @method _activateNextItem
2121      * @chainable
2122      * @protected
2123      */
2124     _activateNextItem: function () {
2125         var item = this.get(ACTIVE_ITEM),
2126             nextItem;
2127
2128         if (item) {
2129             nextItem = item.next(this[_SELECTOR_ITEM]) ||
2130                     (this.get(CIRCULAR) ? null : item);
2131         } else {
2132             nextItem = this._getFirstItemNode();
2133         }
2134
2135         this.set(ACTIVE_ITEM, nextItem);
2136
2137         return this;
2138     },
2139
2140     /**
2141      * Activates the item previous to the currently active item. If there is no
2142      * previous item and the <code>circular</code> attribute is
2143      * <code>true</code>, focus will wrap back to the input node.
2144      *
2145      * @method _activatePrevItem
2146      * @chainable
2147      * @protected
2148      */
2149     _activatePrevItem: function () {
2150         var item     = this.get(ACTIVE_ITEM),
2151             prevItem = item ? item.previous(this[_SELECTOR_ITEM]) :
2152                     this.get(CIRCULAR) && this._getLastItemNode();
2153
2154         this.set(ACTIVE_ITEM, prevItem || null);
2155
2156         return this;
2157     },
2158
2159     /**
2160      * Appends the specified result <i>items</i> to the list inside a new item
2161      * node.
2162      *
2163      * @method _add
2164      * @param {Array|Node|HTMLElement|String} items Result item or array of
2165      *   result items.
2166      * @return {NodeList} Added nodes.
2167      * @protected
2168      */
2169     _add: function (items) {
2170         var itemNodes = [];
2171
2172         YArray.each(Lang.isArray(items) ? items : [items], function (item) {
2173             itemNodes.push(this._createItemNode(item).setData(RESULT, item));
2174         }, this);
2175
2176         itemNodes = Y.all(itemNodes);
2177         this._listNode.append(itemNodes.toFrag());
2178
2179         return itemNodes;
2180     },
2181
2182     /**
2183      * Updates the ARIA live region with the specified message.
2184      *
2185      * @method _ariaSay
2186      * @param {String} stringId String id (from the <code>strings</code>
2187      *   attribute) of the message to speak.
2188      * @param {Object} subs (optional) Substitutions for placeholders in the
2189      *   string.
2190      * @protected
2191      */
2192     _ariaSay: function (stringId, subs) {
2193         var message = this.get('strings.' + stringId);
2194         this._ariaNode.setContent(subs ? Lang.sub(message, subs) : message);
2195     },
2196
2197     /**
2198      * Binds <code>inputNode</code> events and behavior.
2199      *
2200      * @method _bindInput
2201      * @protected
2202      */
2203     _bindInput: function () {
2204         var inputNode = this._inputNode,
2205             alignNode, alignWidth, tokenInput;
2206
2207         // Null align means we can auto-align. Set align to false to prevent
2208         // auto-alignment, or a valid alignment config to customize the
2209         // alignment.
2210         if (this.get('align') === null) {
2211             // If this is a tokenInput, align with its bounding box.
2212             // Otherwise, align with the inputNode. Bit of a cheat.
2213             tokenInput = this.get('tokenInput');
2214             alignNode  = (tokenInput && tokenInput.get('boundingBox')) || inputNode;
2215
2216             this.set('align', {
2217                 node  : alignNode,
2218                 points: ['tl', 'bl']
2219             });
2220
2221             // If no width config is set, attempt to set the list's width to the
2222             // width of the alignment node. If the alignment node's width is
2223             // falsy, do nothing.
2224             if (!this.get(WIDTH) && (alignWidth = alignNode.get('offsetWidth'))) {
2225                 this.set(WIDTH, alignWidth);
2226             }
2227         }
2228
2229         // Attach inputNode events.
2230         this._listEvents.push(inputNode.on('blur', this._onListInputBlur, this));
2231     },
2232
2233     /**
2234      * Binds list events.
2235      *
2236      * @method _bindList
2237      * @protected
2238      */
2239     _bindList: function () {
2240         this._listEvents.concat([
2241             this.after({
2242               mouseover: this._afterMouseOver,
2243               mouseout : this._afterMouseOut,
2244
2245               activeItemChange    : this._afterActiveItemChange,
2246               alwaysShowListChange: this._afterAlwaysShowListChange,
2247               hoveredItemChange   : this._afterHoveredItemChange,
2248               resultsChange       : this._afterResultsChange,
2249               visibleChange       : this._afterVisibleChange
2250             }),
2251
2252             this._listNode.delegate('click', this._onItemClick, this[_SELECTOR_ITEM], this)
2253         ]);
2254     },
2255
2256     /**
2257      * Clears the contents of the tray.
2258      *
2259      * @method _clear
2260      * @protected
2261      */
2262     _clear: function () {
2263         this.set(ACTIVE_ITEM, null);
2264         this._set(HOVERED_ITEM, null);
2265
2266         this._listNode.get('children').remove(true);
2267     },
2268
2269     /**
2270      * Creates and returns an ARIA live region node.
2271      *
2272      * @method _createAriaNode
2273      * @return {Node} ARIA node.
2274      * @protected
2275      */
2276     _createAriaNode: function () {
2277         var ariaNode = Node.create(this.ARIA_TEMPLATE);
2278
2279         return ariaNode.addClass(this.getClassName('aria')).setAttrs({
2280             'aria-live': 'polite',
2281             role       : 'status'
2282         });
2283     },
2284
2285     /**
2286      * Creates and returns an item node with the specified <i>content</i>.
2287      *
2288      * @method _createItemNode
2289      * @param {Object} result Result object.
2290      * @return {Node} Item node.
2291      * @protected
2292      */
2293     _createItemNode: function (result) {
2294         var itemNode = Node.create(this.ITEM_TEMPLATE);
2295
2296         return itemNode.addClass(this[_CLASS_ITEM]).setAttrs({
2297             id  : Y.stamp(itemNode),
2298             role: 'option'
2299         }).setAttribute('data-text', result.text).append(result.display);
2300     },
2301
2302     /**
2303      * Creates and returns a list node.
2304      *
2305      * @method _createListNode
2306      * @return {Node} List node.
2307      * @protected
2308      */
2309     _createListNode: function () {
2310         var listNode = Node.create(this.LIST_TEMPLATE);
2311
2312         return listNode.addClass(this.getClassName(LIST)).setAttrs({
2313             id  : Y.stamp(listNode),
2314             role: 'listbox'
2315         });
2316     },
2317
2318     /**
2319      * Gets the first item node in the list, or <code>null</code> if the list is
2320      * empty.
2321      *
2322      * @method _getFirstItemNode
2323      * @return {Node|null}
2324      * @protected
2325      */
2326     _getFirstItemNode: function () {
2327         return this._listNode.one(this[_SELECTOR_ITEM]);
2328     },
2329
2330     /**
2331      * Gets the last item node in the list, or <code>null</code> if the list is
2332      * empty.
2333      *
2334      * @method _getLastItemNode
2335      * @return {Node|null}
2336      * @protected
2337      */
2338     _getLastItemNode: function () {
2339         return this._listNode.one(this[_SELECTOR_ITEM] + ':last-child');
2340     },
2341
2342     /**
2343      * Synchronizes the results displayed in the list with those in the
2344      * <i>results</i> argument, or with the <code>results</code> attribute if an
2345      * argument is not provided.
2346      *
2347      * @method _syncResults
2348      * @param {Array} results (optional) Results.
2349      * @protected
2350      */
2351     _syncResults: function (results) {
2352         var items;
2353
2354         if (!results) {
2355             results = this.get(RESULTS);
2356         }
2357
2358         this._clear();
2359
2360         if (results.length) {
2361             items = this._add(results);
2362             this._ariaSay('items_available');
2363         }
2364
2365         if (this.get('activateFirstItem') && !this.get(ACTIVE_ITEM)) {
2366             this.set(ACTIVE_ITEM, this._getFirstItemNode());
2367         }
2368     },
2369
2370     /**
2371      * Synchronizes the visibility of the tray with the <i>visible</i> argument,
2372      * or with the <code>visible</code> attribute if an argument is not
2373      * provided.
2374      *
2375      * @method _syncVisibility
2376      * @param {Boolean} visible (optional) Visibility.
2377      * @protected
2378      */
2379     _syncVisibility: function (visible) {
2380         if (this.get(ALWAYS_SHOW_LIST)) {
2381             visible = true;
2382             this.set(VISIBLE, visible);
2383         }
2384
2385         if (typeof visible === 'undefined') {
2386             visible = this.get(VISIBLE);
2387         }
2388
2389         this._inputNode.set('aria-expanded', visible);
2390         this._boundingBox.set('aria-hidden', !visible);
2391
2392         if (visible) {
2393             // Force WidgetPositionAlign to refresh its alignment.
2394             this._syncUIPosAlign();
2395         } else {
2396             this.set(ACTIVE_ITEM, null);
2397             this._set(HOVERED_ITEM, null);
2398
2399             // Force a reflow to work around a glitch in IE6 and 7 where some of
2400             // the contents of the list will sometimes remain visible after the
2401             // container is hidden.
2402             this._boundingBox.get('offsetWidth');
2403         }
2404     },
2405
2406     // -- Protected Event Handlers ---------------------------------------------
2407
2408     /**
2409      * Handles <code>activeItemChange</code> events.
2410      *
2411      * @method _afterActiveItemChange
2412      * @param {EventTarget} e
2413      * @protected
2414      */
2415     _afterActiveItemChange: function (e) {
2416         var inputNode = this._inputNode,
2417             newVal    = e.newVal,
2418             prevVal   = e.prevVal;
2419
2420         // The previous item may have disappeared by the time this handler runs,
2421         // so we need to be careful.
2422         if (prevVal && prevVal._node) {
2423             prevVal.removeClass(this[_CLASS_ITEM_ACTIVE]);
2424         }
2425
2426         if (newVal) {
2427             newVal.addClass(this[_CLASS_ITEM_ACTIVE]);
2428             inputNode.set('aria-activedescendant', newVal.get(ID));
2429         } else {
2430             inputNode.removeAttribute('aria-activedescendant');
2431         }
2432
2433         if (this.get('scrollIntoView')) {
2434             (newVal || inputNode).scrollIntoView();
2435         }
2436     },
2437
2438     /**
2439      * Handles <code>alwaysShowListChange</code> events.
2440      *
2441      * @method _afterAlwaysShowListChange
2442      * @param {EventTarget} e
2443      * @protected
2444      */
2445     _afterAlwaysShowListChange: function (e) {
2446         this.set(VISIBLE, e.newVal || this.get(RESULTS).length > 0);
2447     },
2448
2449     /**
2450      * Handles <code>hoveredItemChange</code> events.
2451      *
2452      * @method _afterHoveredItemChange
2453      * @param {EventTarget} e
2454      * @protected
2455      */
2456     _afterHoveredItemChange: function (e) {
2457         var newVal  = e.newVal,
2458             prevVal = e.prevVal;
2459
2460         if (prevVal) {
2461             prevVal.removeClass(this[_CLASS_ITEM_HOVER]);
2462         }
2463
2464         if (newVal) {
2465             newVal.addClass(this[_CLASS_ITEM_HOVER]);
2466         }
2467     },
2468
2469     /**
2470      * Handles <code>mouseover</code> events.
2471      *
2472      * @method _afterMouseOver
2473      * @param {EventTarget} e
2474      * @protected
2475      */
2476     _afterMouseOver: function (e) {
2477         var itemNode = e.domEvent.target.ancestor(this[_SELECTOR_ITEM], true);
2478
2479         this._mouseOverList = true;
2480
2481         if (itemNode) {
2482             this._set(HOVERED_ITEM, itemNode);
2483         }
2484     },
2485
2486     /**
2487      * Handles <code>mouseout</code> events.
2488      *
2489      * @method _afterMouseOut
2490      * @param {EventTarget} e
2491      * @protected
2492      */
2493     _afterMouseOut: function () {
2494         this._mouseOverList = false;
2495         this._set(HOVERED_ITEM, null);
2496     },
2497
2498     /**
2499      * Handles <code>resultsChange</code> events.
2500      *
2501      * @method _afterResultsChange
2502      * @param {EventFacade} e
2503      * @protected
2504      */
2505     _afterResultsChange: function (e) {
2506         this._syncResults(e.newVal);
2507
2508         if (!this.get(ALWAYS_SHOW_LIST)) {
2509             this.set(VISIBLE, !!e.newVal.length);
2510         }
2511     },
2512
2513     /**
2514      * Handles <code>visibleChange</code> events.
2515      *
2516      * @method _afterVisibleChange
2517      * @param {EventFacade} e
2518      * @protected
2519      */
2520     _afterVisibleChange: function (e) {
2521         this._syncVisibility(!!e.newVal);
2522     },
2523
2524     /**
2525      * Handles <code>inputNode</code> <code>blur</code> events.
2526      *
2527      * @method _onListInputBlur
2528      * @param {EventTarget} e
2529      * @protected
2530      */
2531     _onListInputBlur: function (e) {
2532         // Hide the list on inputNode blur events, unless the mouse is currently
2533         // over the list (which indicates that the user is probably interacting
2534         // with it). The _lastInputKey property comes from the
2535         // autocomplete-list-keys module.
2536         if (!this._mouseOverList || this._lastInputKey === KEY_TAB) {
2537             this.hide();
2538         }
2539     },
2540
2541     /**
2542      * Delegated event handler for item <code>click</code> events.
2543      *
2544      * @method _onItemClick
2545      * @param {EventTarget} e
2546      * @protected
2547      */
2548     _onItemClick: function (e) {
2549         var itemNode = e.currentTarget;
2550
2551         this.set(ACTIVE_ITEM, itemNode);
2552         this.selectItem(itemNode);
2553     },
2554
2555     // -- Protected Default Event Handlers -------------------------------------
2556
2557     /**
2558      * Default <code>select</code> event handler.
2559      *
2560      * @method _defSelectFn
2561      * @param {EventTarget} e
2562      * @protected
2563      */
2564     _defSelectFn: function (e) {
2565         var text = e.result.text;
2566
2567         // TODO: support typeahead completion, etc.
2568         this._inputNode.focus();
2569         this._updateValue(text);
2570         this._ariaSay('item_selected', {item: text});
2571         this.hide();
2572     }
2573 }, {
2574     ATTRS: {
2575         /**
2576          * If <code>true</code>, the first item in the list will be activated by
2577          * default when the list is initially displayed and when results change.
2578          *
2579          * @attribute activateFirstItem
2580          * @type Boolean
2581          * @default false
2582          */
2583         activateFirstItem: {
2584             value: false
2585         },
2586
2587         /**
2588          * Item that's currently active, if any. When the user presses enter,
2589          * this is the item that will be selected.
2590          *
2591          * @attribute activeItem
2592          * @type Node
2593          */
2594         activeItem: {
2595             setter: Y.one,
2596             value: null
2597         },
2598
2599         /**
2600          * If <code>true</code>, the list will remain visible even when there
2601          * are no results to display.
2602          *
2603          * @attribute alwaysShowList
2604          * @type Boolean
2605          * @default false
2606          */
2607         alwaysShowList: {
2608             value: false
2609         },
2610
2611         /**
2612          * If <code>true</code>, keyboard navigation will wrap around to the
2613          * opposite end of the list when navigating past the first or last item.
2614          *
2615          * @attribute circular
2616          * @type Boolean
2617          * @default true
2618          */
2619         circular: {
2620             value: true
2621         },
2622
2623         /**
2624          * Item currently being hovered over by the mouse, if any.
2625          *
2626          * @attribute hoveredItem
2627          * @type Node|null
2628          * @readonly
2629          */
2630         hoveredItem: {
2631             readOnly: true,
2632             value: null
2633         },
2634
2635         /**
2636          * Node that will contain result items.
2637          *
2638          * @attribute listNode
2639          * @type Node|null
2640          * @readonly
2641          */
2642         listNode: {
2643             readOnly: true,
2644             value: null
2645         },
2646
2647         /**
2648          * If <code>true</code>, the viewport will be scrolled to ensure that
2649          * the active list item is visible when necessary.
2650          *
2651          * @attribute scrollIntoView
2652          * @type Boolean
2653          * @default false
2654          */
2655         scrollIntoView: {
2656             value: false
2657         },
2658
2659         /**
2660          * Translatable strings used by the AutoCompleteList widget.
2661          *
2662          * @attribute strings
2663          * @type Object
2664          */
2665         strings: {
2666             valueFn: function () {
2667                 return Y.Intl.get('autocomplete-list');
2668             }
2669         },
2670
2671         /**
2672          * If <code>true</code>, pressing the tab key while the list is visible
2673          * will select the active item, if any.
2674          *
2675          * @attribute tabSelect
2676          * @type Boolean
2677          * @default true
2678          */
2679         tabSelect: {
2680             value: true
2681         },
2682
2683         // The "visible" attribute is documented in Widget.
2684         visible: {
2685             value: false
2686         }
2687     },
2688
2689     CSS_PREFIX: Y.ClassNameManager.getClassName('aclist')
2690 });
2691
2692 Y.AutoCompleteList = List;
2693
2694 /**
2695  * Alias for <a href="AutoCompleteList.html"><code>AutoCompleteList</code></a>.
2696  * See that class for API docs.
2697  *
2698  * @class AutoComplete
2699  */
2700
2701 Y.AutoComplete = List;
2702
2703
2704 }, '3.3.0' ,{lang:['en'], requires:['autocomplete-base', 'selector-css3', 'widget', 'widget-position', 'widget-position-align', 'widget-stack'], after:['autocomplete-sources'], skinnable:true});
2705 YUI.add('autocomplete-plugin', function(Y) {
2706
2707 /**
2708  * Binds an AutoCompleteList instance to a Node instance.
2709  *
2710  * @module autocomplete
2711  * @submodule autocomplete-plugin
2712  */
2713
2714 /**
2715  * <p>
2716  * Binds an AutoCompleteList instance to a Node instance.
2717  * </p>
2718  *
2719  * <p>
2720  * Example:
2721  * </p>
2722  *
2723  * <pre>
2724  * Y.one('#my-input').plug(Y.Plugin.AutoComplete, {
2725  * &nbsp;&nbsp;source: 'select * from search.suggest where query="{query}"'
2726  * });
2727  * &nbsp;
2728  * // You can now access the AutoCompleteList instance at Y.one('#my-input').ac
2729  * </pre>
2730  *
2731  * @class Plugin.AutoComplete
2732  * @extends AutoCompleteList
2733  */
2734
2735 var Plugin = Y.Plugin;
2736
2737 function ACListPlugin(config) {
2738     config.inputNode = config.host;
2739
2740     // Render by default.
2741     if (!config.render && config.render !== false) {
2742       config.render = true;
2743     }
2744
2745     ACListPlugin.superclass.constructor.apply(this, arguments);
2746 }
2747
2748 Y.extend(ACListPlugin, Y.AutoCompleteList, {}, {
2749     NAME      : 'autocompleteListPlugin',
2750     NS        : 'ac',
2751     CSS_PREFIX: Y.ClassNameManager.getClassName('aclist')
2752 });
2753
2754 Plugin.AutoComplete     = ACListPlugin;
2755 Plugin.AutoCompleteList = ACListPlugin;
2756
2757
2758 }, '3.3.0' ,{requires:['autocomplete-list', 'node-pluginhost']});
2759
2760
2761 YUI.add('autocomplete', function(Y){}, '3.3.0' ,{use:['autocomplete-base', 'autocomplete-sources', 'autocomplete-list', 'autocomplete-plugin']});
2762