~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/app/javascript/lazr/yui/autocomplete/autocomplete-debug.js

Added lazr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
            Y.log('sendRequest: ' + request, 'info', 'autocomplete-base');
 
794
 
 
795
            source.sendRequest({
 
796
                request: request,
 
797
                callback: {
 
798
                    success: Y.bind(this._onResponse, this, query)
 
799
                }
 
800
            });
 
801
        }
 
802
 
 
803
        return this;
 
804
    },
 
805
 
 
806
    // -- Protected Lifecycle Methods ------------------------------------------
 
807
 
 
808
    /**
 
809
     * Attaches event listeners and behaviors.
 
810
     *
 
811
     * @method _bindUIACBase
 
812
     * @protected
 
813
     */
 
814
    _bindUIACBase: function () {
 
815
        var inputNode  = this.get(INPUT_NODE),
 
816
            tokenInput = inputNode && inputNode.tokenInput;
 
817
 
 
818
        // If the inputNode has a node-tokeninput plugin attached, bind to the
 
819
        // plugin's inputNode instead.
 
820
        if (tokenInput) {
 
821
            inputNode = tokenInput.get(INPUT_NODE);
 
822
            this._set('tokenInput', tokenInput);
 
823
        }
 
824
 
 
825
        if (!inputNode) {
 
826
            Y.error('No inputNode specified.');
 
827
            return;
 
828
        }
 
829
 
 
830
        this._inputNode = inputNode;
 
831
 
 
832
        this._acBaseEvents = [
 
833
            // This is the valueChange event on the inputNode, provided by the
 
834
            // event-valuechange module, not our own valueChange.
 
835
            inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
 
836
 
 
837
            inputNode.on('blur', this._onInputBlur, this),
 
838
 
 
839
            this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
 
840
            this.after(VALUE_CHANGE, this._afterValueChange)
 
841
        ];
 
842
    },
 
843
 
 
844
    /**
 
845
     * Detaches AutoCompleteBase event listeners.
 
846
     *
 
847
     * @method _destructorACBase
 
848
     * @protected
 
849
     */
 
850
    _destructorACBase: function () {
 
851
        var events = this._acBaseEvents;
 
852
 
 
853
        while (events && events.length) {
 
854
            events.pop().detach();
 
855
        }
 
856
    },
 
857
 
 
858
    /**
 
859
     * Synchronizes the UI state of the <code>inputNode</code>.
 
860
     *
 
861
     * @method _syncUIACBase
 
862
     * @protected
 
863
     */
 
864
    _syncUIACBase: function () {
 
865
        this._syncBrowserAutocomplete();
 
866
        this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
 
867
    },
 
868
 
 
869
    // -- Protected Prototype Methods ------------------------------------------
 
870
 
 
871
    /**
 
872
     * Creates a DataSource-like object that simply returns the specified array
 
873
     * as a response. See the <code>source</code> attribute for more details.
 
874
     *
 
875
     * @method _createArraySource
 
876
     * @param {Array} source
 
877
     * @return {Object} DataSource-like object.
 
878
     * @protected
 
879
     */
 
880
    _createArraySource: function (source) {
 
881
        var that = this;
 
882
 
 
883
        return {sendRequest: function (request) {
 
884
            that[_SOURCE_SUCCESS](source.concat(), request);
 
885
        }};
 
886
    },
 
887
 
 
888
    /**
 
889
     * Creates a DataSource-like object that passes the query to a
 
890
     * custom-defined function, which is expected to return an array as a
 
891
     * response. See the <code>source</code> attribute for more details.
 
892
     *
 
893
     * @method _createFunctionSource
 
894
     * @param {Function} source Function that accepts a query parameter and
 
895
     *   returns an array of results.
 
896
     * @return {Object} DataSource-like object.
 
897
     * @protected
 
898
     */
 
899
    _createFunctionSource: function (source) {
 
900
        var that = this;
 
901
 
 
902
        return {sendRequest: function (request) {
 
903
            that[_SOURCE_SUCCESS](source(request.request) || [], request);
 
904
        }};
 
905
    },
 
906
 
 
907
    /**
 
908
     * Creates a DataSource-like object that looks up queries as properties on
 
909
     * the specified object, and returns the found value (if any) as a response.
 
910
     * See the <code>source</code> attribute for more details.
 
911
     *
 
912
     * @method _createObjectSource
 
913
     * @param {Object} source
 
914
     * @return {Object} DataSource-like object.
 
915
     * @protected
 
916
     */
 
917
    _createObjectSource: function (source) {
 
918
        var that = this;
 
919
 
 
920
        return {sendRequest: function (request) {
 
921
            var query = request.request;
 
922
 
 
923
            that[_SOURCE_SUCCESS](
 
924
                YObject.owns(source, query) ? source[query] : [],
 
925
                request
 
926
            );
 
927
        }};
 
928
    },
 
929
 
 
930
    /**
 
931
     * Returns <code>true</code> if <i>value</i> is either a function or
 
932
     * <code>null</code>.
 
933
     *
 
934
     * @method _functionValidator
 
935
     * @param {Function|null} value Value to validate.
 
936
     * @protected
 
937
     */
 
938
    _functionValidator: function (value) {
 
939
        return value === null || isFunction(value);
 
940
    },
 
941
 
 
942
    /**
 
943
     * Faster and safer alternative to Y.Object.getValue(). Doesn't bother
 
944
     * casting the path to an array (since we already know it's an array) and
 
945
     * doesn't throw an error if a value in the middle of the object hierarchy
 
946
     * is neither <code>undefined</code> nor an object.
 
947
     *
 
948
     * @method _getObjectValue
 
949
     * @param {Object} obj
 
950
     * @param {Array} path
 
951
     * @return {mixed} Located value, or <code>undefined</code> if the value was
 
952
     *   not found at the specified path.
 
953
     * @protected
 
954
     */
 
955
    _getObjectValue: function (obj, path) {
 
956
        if (!obj) {
 
957
            return;
 
958
        }
 
959
 
 
960
        for (var i = 0, len = path.length; obj && i < len; i++) {
 
961
            obj = obj[path[i]];
 
962
        }
 
963
 
 
964
        return obj;
 
965
    },
 
966
 
 
967
    /**
 
968
     * Parses result responses, performs filtering and highlighting, and fires
 
969
     * the <code>results</code> event.
 
970
     *
 
971
     * @method _parseResponse
 
972
     * @param {String} query Query that generated these results.
 
973
     * @param {Object} response Response containing results.
 
974
     * @param {Object} data Raw response data.
 
975
     * @protected
 
976
     */
 
977
    _parseResponse: function (query, response, data) {
 
978
        var facade = {
 
979
                data   : data,
 
980
                query  : query,
 
981
                results: []
 
982
            },
 
983
 
 
984
            listLocator = this.get(RESULT_LIST_LOCATOR),
 
985
            results     = [],
 
986
            unfiltered  = response && response.results,
 
987
 
 
988
            filters,
 
989
            formatted,
 
990
            formatter,
 
991
            highlighted,
 
992
            highlighter,
 
993
            i,
 
994
            len,
 
995
            maxResults,
 
996
            result,
 
997
            text,
 
998
            textLocator;
 
999
 
 
1000
        if (unfiltered && listLocator) {
 
1001
            unfiltered = listLocator(unfiltered);
 
1002
        }
 
1003
 
 
1004
        if (unfiltered && unfiltered.length) {
 
1005
            filters     = this.get('resultFilters');
 
1006
            textLocator = this.get('resultTextLocator');
 
1007
 
 
1008
            // Create a lightweight result object for each result to make them
 
1009
            // easier to work with. The various properties on the object
 
1010
            // represent different formats of the result, and will be populated
 
1011
            // as we go.
 
1012
            for (i = 0, len = unfiltered.length; i < len; ++i) {
 
1013
                result = unfiltered[i];
 
1014
                text   = textLocator ? textLocator(result) : result.toString();
 
1015
 
 
1016
                results.push({
 
1017
                    display: Escape.html(text),
 
1018
                    raw    : result,
 
1019
                    text   : text
 
1020
                });
 
1021
            }
 
1022
 
 
1023
            // Run the results through all configured result filters. Each
 
1024
            // filter returns an array of (potentially fewer) result objects,
 
1025
            // which is then passed to the next filter, and so on.
 
1026
            for (i = 0, len = filters.length; i < len; ++i) {
 
1027
                results = filters[i](query, results.concat());
 
1028
 
 
1029
                if (!results) {
 
1030
                    Y.log("Filter didn't return anything.", 'warn', 'autocomplete-base');
 
1031
                    return;
 
1032
                }
 
1033
 
 
1034
                if (!results.length) {
 
1035
                    break;
 
1036
                }
 
1037
            }
 
1038
 
 
1039
            if (results.length) {
 
1040
                formatter   = this.get('resultFormatter');
 
1041
                highlighter = this.get('resultHighlighter');
 
1042
                maxResults  = this.get('maxResults');
 
1043
 
 
1044
                // If maxResults is set and greater than 0, limit the number of
 
1045
                // results.
 
1046
                if (maxResults && maxResults > 0 &&
 
1047
                        results.length > maxResults) {
 
1048
                    results.length = maxResults;
 
1049
                }
 
1050
 
 
1051
                // Run the results through the configured highlighter (if any).
 
1052
                // The highlighter returns an array of highlighted strings (not
 
1053
                // an array of result objects), and these strings are then added
 
1054
                // to each result object.
 
1055
                if (highlighter) {
 
1056
                    highlighted = highlighter(query, results.concat());
 
1057
 
 
1058
                    if (!highlighted) {
 
1059
                        Y.log("Highlighter didn't return anything.", 'warn', 'autocomplete-base');
 
1060
                        return;
 
1061
                    }
 
1062
 
 
1063
                    for (i = 0, len = highlighted.length; i < len; ++i) {
 
1064
                        result = results[i];
 
1065
                        result.highlighted = highlighted[i];
 
1066
                        result.display     = result.highlighted;
 
1067
                    }
 
1068
                }
 
1069
 
 
1070
                // Run the results through the configured formatter (if any) to
 
1071
                // produce the final formatted results. The formatter returns an
 
1072
                // array of strings or Node instances (not an array of result
 
1073
                // objects), and these strings/Nodes are then added to each
 
1074
                // result object.
 
1075
                if (formatter) {
 
1076
                    formatted = formatter(query, results.concat());
 
1077
 
 
1078
                    if (!formatted) {
 
1079
                        Y.log("Formatter didn't return anything.", 'warn', 'autocomplete-base');
 
1080
                        return;
 
1081
                    }
 
1082
 
 
1083
                    for (i = 0, len = formatted.length; i < len; ++i) {
 
1084
                        results[i].display = formatted[i];
 
1085
                    }
 
1086
                }
 
1087
            }
 
1088
        }
 
1089
 
 
1090
        facade.results = results;
 
1091
        this.fire(EVT_RESULTS, facade);
 
1092
    },
 
1093
 
 
1094
    /**
 
1095
     * <p>
 
1096
     * Returns the query portion of the specified input value, or
 
1097
     * <code>null</code> if there is no suitable query within the input value.
 
1098
     * </p>
 
1099
     *
 
1100
     * <p>
 
1101
     * If a query delimiter is defined, the query will be the last delimited
 
1102
     * part of of the string.
 
1103
     * </p>
 
1104
     *
 
1105
     * @method _parseValue
 
1106
     * @param {String} value Input value from which to extract the query.
 
1107
     * @return {String|null} query
 
1108
     * @protected
 
1109
     */
 
1110
    _parseValue: function (value) {
 
1111
        var delim = this.get(QUERY_DELIMITER);
 
1112
 
 
1113
        if (delim) {
 
1114
            value = value.split(delim);
 
1115
            value = value[value.length - 1];
 
1116
        }
 
1117
 
 
1118
        return Lang.trimLeft(value);
 
1119
    },
 
1120
 
 
1121
    /**
 
1122
     * Setter for locator attributes.
 
1123
     *
 
1124
     * @method _setLocator
 
1125
     * @param {Function|String|null} locator
 
1126
     * @return {Function|null}
 
1127
     * @protected
 
1128
     */
 
1129
    _setLocator: function (locator) {
 
1130
        if (this[_FUNCTION_VALIDATOR](locator)) {
 
1131
            return locator;
 
1132
        }
 
1133
 
 
1134
        var that = this;
 
1135
 
 
1136
        locator = locator.toString().split('.');
 
1137
 
 
1138
        return function (result) {
 
1139
            return result && that._getObjectValue(result, locator);
 
1140
        };
 
1141
    },
 
1142
 
 
1143
    /**
 
1144
     * Setter for the <code>requestTemplate</code> attribute.
 
1145
     *
 
1146
     * @method _setRequestTemplate
 
1147
     * @param {Function|String|null} template
 
1148
     * @return {Function|null}
 
1149
     * @protected
 
1150
     */
 
1151
    _setRequestTemplate: function (template) {
 
1152
        if (this[_FUNCTION_VALIDATOR](template)) {
 
1153
            return template;
 
1154
        }
 
1155
 
 
1156
        template = template.toString();
 
1157
 
 
1158
        return function (query) {
 
1159
            return Lang.sub(template, {query: encodeURIComponent(query)});
 
1160
        };
 
1161
    },
 
1162
 
 
1163
    /**
 
1164
     * Setter for the <code>resultFilters</code> attribute.
 
1165
     *
 
1166
     * @method _setResultFilters
 
1167
     * @param {Array|Function|String|null} filters <code>null</code>, a filter
 
1168
     *   function, an array of filter functions, or a string or array of strings
 
1169
     *   representing the names of methods on
 
1170
     *   <code>Y.AutoCompleteFilters</code>.
 
1171
     * @return {Array} Array of filter functions (empty if <i>filters</i> is
 
1172
     *   <code>null</code>).
 
1173
     * @protected
 
1174
     */
 
1175
    _setResultFilters: function (filters) {
 
1176
        var acFilters, getFilterFunction;
 
1177
 
 
1178
        if (filters === null) {
 
1179
            return [];
 
1180
        }
 
1181
 
 
1182
        acFilters = Y.AutoCompleteFilters;
 
1183
 
 
1184
        getFilterFunction = function (filter) {
 
1185
            if (isFunction(filter)) {
 
1186
                return filter;
 
1187
            }
 
1188
 
 
1189
            if (isString(filter) && acFilters &&
 
1190
                    isFunction(acFilters[filter])) {
 
1191
                return acFilters[filter];
 
1192
            }
 
1193
 
 
1194
            return false;
 
1195
        };
 
1196
 
 
1197
        if (Lang.isArray(filters)) {
 
1198
            filters = YArray.map(filters, getFilterFunction);
 
1199
            return YArray.every(filters, function (f) { return !!f; }) ?
 
1200
                    filters : INVALID_VALUE;
 
1201
        } else {
 
1202
            filters = getFilterFunction(filters);
 
1203
            return filters ? [filters] : INVALID_VALUE;
 
1204
        }
 
1205
    },
 
1206
 
 
1207
    /**
 
1208
     * Setter for the <code>resultHighlighter</code> attribute.
 
1209
     *
 
1210
     * @method _setResultHighlighter
 
1211
     * @param {Function|String|null} highlighter <code>null</code>, a
 
1212
     *   highlighter function, or a string representing the name of a method on
 
1213
     *   <code>Y.AutoCompleteHighlighters</code>.
 
1214
     * @return {Function|null}
 
1215
     * @protected
 
1216
     */
 
1217
    _setResultHighlighter: function (highlighter) {
 
1218
        var acHighlighters;
 
1219
 
 
1220
        if (this._functionValidator(highlighter)) {
 
1221
            return highlighter;
 
1222
        }
 
1223
 
 
1224
        acHighlighters = Y.AutoCompleteHighlighters;
 
1225
 
 
1226
        if (isString(highlighter) && acHighlighters &&
 
1227
                isFunction(acHighlighters[highlighter])) {
 
1228
            return acHighlighters[highlighter];
 
1229
        }
 
1230
 
 
1231
        return INVALID_VALUE;
 
1232
    },
 
1233
 
 
1234
    /**
 
1235
     * Setter for the <code>source</code> attribute. Returns a DataSource or
 
1236
     * a DataSource-like object depending on the type of <i>source</i>.
 
1237
     *
 
1238
     * @method _setSource
 
1239
     * @param {Array|DataSource|Object|String} source AutoComplete source. See
 
1240
     *   the <code>source</code> attribute for details.
 
1241
     * @return {DataSource|Object}
 
1242
     * @protected
 
1243
     */
 
1244
    _setSource: function (source) {
 
1245
        var sourcesNotLoaded = 'autocomplete-sources module not loaded';
 
1246
 
 
1247
        if ((source && isFunction(source.sendRequest)) || source === null) {
 
1248
            // Quacks like a DataSource instance (or null). Make it so!
 
1249
            return source;
 
1250
        }
 
1251
 
 
1252
        switch (Lang.type(source)) {
 
1253
        case 'string':
 
1254
            if (this._createStringSource) {
 
1255
                return this._createStringSource(source);
 
1256
            }
 
1257
 
 
1258
            Y.error(sourcesNotLoaded);
 
1259
            return INVALID_VALUE;
 
1260
 
 
1261
        case 'array':
 
1262
            // Wrap the array in a teensy tiny fake DataSource that just returns
 
1263
            // the array itself for each request. Filters will do the rest.
 
1264
            return this._createArraySource(source);
 
1265
 
 
1266
        case 'function':
 
1267
            return this._createFunctionSource(source);
 
1268
 
 
1269
        case 'object':
 
1270
            // If the object is a JSONPRequest instance, use it as a JSONP
 
1271
            // source.
 
1272
            if (Y.JSONPRequest && source instanceof Y.JSONPRequest) {
 
1273
                if (this._createJSONPSource) {
 
1274
                    return this._createJSONPSource(source);
 
1275
                }
 
1276
 
 
1277
                Y.error(sourcesNotLoaded);
 
1278
                return INVALID_VALUE;
 
1279
            }
 
1280
 
 
1281
            // Not a JSONPRequest instance. Wrap the object in a teensy tiny
 
1282
            // fake DataSource that looks for the request as a property on the
 
1283
            // object and returns it if it exists, or an empty array otherwise.
 
1284
            return this._createObjectSource(source);
 
1285
        }
 
1286
 
 
1287
        return INVALID_VALUE;
 
1288
    },
 
1289
 
 
1290
    /**
 
1291
     * Shared success callback for non-DataSource sources.
 
1292
     *
 
1293
     * @method _sourceSuccess
 
1294
     * @param {mixed} data Response data.
 
1295
     * @param {Object} request Request object.
 
1296
     * @protected
 
1297
     */
 
1298
    _sourceSuccess: function (data, request) {
 
1299
        request.callback.success({
 
1300
            data: data,
 
1301
            response: {results: data}
 
1302
        });
 
1303
    },
 
1304
 
 
1305
    /**
 
1306
     * Synchronizes the UI state of the <code>allowBrowserAutocomplete</code>
 
1307
     * attribute.
 
1308
     *
 
1309
     * @method _syncBrowserAutocomplete
 
1310
     * @protected
 
1311
     */
 
1312
    _syncBrowserAutocomplete: function () {
 
1313
        var inputNode = this.get(INPUT_NODE);
 
1314
 
 
1315
        if (inputNode.get('nodeName').toLowerCase() === 'input') {
 
1316
            inputNode.setAttribute('autocomplete',
 
1317
                    this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
 
1318
        }
 
1319
    },
 
1320
 
 
1321
    /**
 
1322
     * <p>
 
1323
     * Updates the query portion of the <code>value</code> attribute.
 
1324
     * </p>
 
1325
     *
 
1326
     * <p>
 
1327
     * If a query delimiter is defined, the last delimited portion of the input
 
1328
     * value will be replaced with the specified <i>value</i>.
 
1329
     * </p>
 
1330
     *
 
1331
     * @method _updateValue
 
1332
     * @param {String} newVal New value.
 
1333
     * @protected
 
1334
     */
 
1335
    _updateValue: function (newVal) {
 
1336
        var delim = this.get(QUERY_DELIMITER),
 
1337
            insertDelim,
 
1338
            len,
 
1339
            prevVal;
 
1340
 
 
1341
        newVal = Lang.trimLeft(newVal);
 
1342
 
 
1343
        if (delim) {
 
1344
            insertDelim = trim(delim); // so we don't double up on spaces
 
1345
            prevVal     = YArray.map(trim(this.get(VALUE)).split(delim), trim);
 
1346
            len         = prevVal.length;
 
1347
 
 
1348
            if (len > 1) {
 
1349
                prevVal[len - 1] = newVal;
 
1350
                newVal = prevVal.join(insertDelim + ' ');
 
1351
            }
 
1352
 
 
1353
            newVal = newVal + insertDelim + ' ';
 
1354
        }
 
1355
 
 
1356
        this.set(VALUE, newVal);
 
1357
    },
 
1358
 
 
1359
    // -- Protected Event Handlers ---------------------------------------------
 
1360
 
 
1361
    /**
 
1362
     * Handles change events for the <code>value</code> attribute.
 
1363
     *
 
1364
     * @method _afterValueChange
 
1365
     * @param {EventFacade} e
 
1366
     * @protected
 
1367
     */
 
1368
    _afterValueChange: function (e) {
 
1369
        var delay,
 
1370
            fire,
 
1371
            minQueryLength,
 
1372
            newVal = e.newVal,
 
1373
            query,
 
1374
            that;
 
1375
 
 
1376
        // Don't query on value changes that didn't come from the user.
 
1377
        if (e.src !== AutoCompleteBase.UI_SRC) {
 
1378
            this._inputNode.set(VALUE, newVal);
 
1379
            return;
 
1380
        }
 
1381
 
 
1382
        Y.log('valueChange: new: "' + newVal + '"; old: "' + e.prevVal + '"', 'info', 'autocomplete-base');
 
1383
 
 
1384
        minQueryLength = this.get('minQueryLength');
 
1385
        query          = this._parseValue(newVal) || '';
 
1386
 
 
1387
        if (minQueryLength >= 0 && query.length >= minQueryLength) {
 
1388
            delay = this.get('queryDelay');
 
1389
            that  = this;
 
1390
 
 
1391
            fire = function () {
 
1392
                that.fire(EVT_QUERY, {
 
1393
                    inputValue: newVal,
 
1394
                    query     : query
 
1395
                });
 
1396
            };
 
1397
 
 
1398
            if (delay) {
 
1399
                clearTimeout(this._delay);
 
1400
                this._delay = setTimeout(fire, delay);
 
1401
            } else {
 
1402
                fire();
 
1403
            }
 
1404
        } else {
 
1405
            clearTimeout(this._delay);
 
1406
 
 
1407
            this.fire(EVT_CLEAR, {
 
1408
                prevVal: e.prevVal ? this._parseValue(e.prevVal) : null
 
1409
            });
 
1410
        }
 
1411
    },
 
1412
 
 
1413
    /**
 
1414
     * Handles <code>blur</code> events on the input node.
 
1415
     *
 
1416
     * @method _onInputBlur
 
1417
     * @param {EventFacade} e
 
1418
     * @protected
 
1419
     */
 
1420
    _onInputBlur: function (e) {
 
1421
        var delim = this.get(QUERY_DELIMITER),
 
1422
            delimPos,
 
1423
            newVal,
 
1424
            value;
 
1425
 
 
1426
        // If a query delimiter is set and the input's value contains one or
 
1427
        // more trailing delimiters, strip them.
 
1428
        if (delim && !this.get('allowTrailingDelimiter')) {
 
1429
            delim = Lang.trimRight(delim);
 
1430
            value = newVal = this._inputNode.get(VALUE);
 
1431
 
 
1432
            if (delim) {
 
1433
                while ((newVal = Lang.trimRight(newVal)) &&
 
1434
                        (delimPos = newVal.length - delim.length) &&
 
1435
                        newVal.lastIndexOf(delim) === delimPos) {
 
1436
 
 
1437
                    newVal = newVal.substring(0, delimPos);
 
1438
                }
 
1439
            } else {
 
1440
                // Delimiter is one or more space characters, so just trim the
 
1441
                // value.
 
1442
                newVal = Lang.trimRight(newVal);
 
1443
            }
 
1444
 
 
1445
            if (newVal !== value) {
 
1446
                this.set(VALUE, newVal);
 
1447
            }
 
1448
        }
 
1449
    },
 
1450
 
 
1451
    /**
 
1452
     * Handles <code>valueChange</code> events on the input node and fires a
 
1453
     * <code>query</code> event when the input value meets the configured
 
1454
     * criteria.
 
1455
     *
 
1456
     * @method _onInputValueChange
 
1457
     * @param {EventFacade} e
 
1458
     * @protected
 
1459
     */
 
1460
    _onInputValueChange: function (e) {
 
1461
        var newVal = e.newVal;
 
1462
 
 
1463
        // Don't query if the internal value is the same as the new value
 
1464
        // reported by valueChange.
 
1465
        if (newVal === this.get(VALUE)) {
 
1466
            return;
 
1467
        }
 
1468
 
 
1469
        this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
 
1470
    },
 
1471
 
 
1472
    /**
 
1473
     * Handles source responses and fires the <code>results</code> event.
 
1474
     *
 
1475
     * @method _onResponse
 
1476
     * @param {EventFacade} e
 
1477
     * @protected
 
1478
     */
 
1479
    _onResponse: function (query, e) {
 
1480
        // Ignore stale responses that aren't for the current query.
 
1481
        if (query === this.get(QUERY)) {
 
1482
            this._parseResponse(query, e.response, e.data);
 
1483
        }
 
1484
    },
 
1485
 
 
1486
    // -- Protected Default Event Handlers -------------------------------------
 
1487
 
 
1488
    /**
 
1489
     * Default <code>clear</code> event handler. Sets the <code>results</code>
 
1490
     * property to an empty array and <code>query</code> to null.
 
1491
     *
 
1492
     * @method _defClearFn
 
1493
     * @protected
 
1494
     */
 
1495
    _defClearFn: function () {
 
1496
        this._set(QUERY, null);
 
1497
        this._set(RESULTS, []);
 
1498
    },
 
1499
 
 
1500
    /**
 
1501
     * Default <code>query</code> event handler. Sets the <code>query</code>
 
1502
     * property and sends a request to the source if one is configured.
 
1503
     *
 
1504
     * @method _defQueryFn
 
1505
     * @param {EventFacade} e
 
1506
     * @protected
 
1507
     */
 
1508
    _defQueryFn: function (e) {
 
1509
        var query = e.query;
 
1510
 
 
1511
        Y.log('query: "' + query + '"; inputValue: "' + e.inputValue + '"', 'info', 'autocomplete-base');
 
1512
        this.sendRequest(query); // sendRequest will set the 'query' attribute
 
1513
    },
 
1514
 
 
1515
    /**
 
1516
     * Default <code>results</code> event handler. Sets the <code>results</code>
 
1517
     * property to the latest results.
 
1518
     *
 
1519
     * @method _defResultsFn
 
1520
     * @param {EventFacade} e
 
1521
     * @protected
 
1522
     */
 
1523
    _defResultsFn: function (e) {
 
1524
        Y.log('results: ' + Y.dump(e.results), 'info', 'autocomplete-base');
 
1525
        this._set(RESULTS, e[RESULTS]);
 
1526
    }
 
1527
};
 
1528
 
 
1529
Y.AutoCompleteBase = AutoCompleteBase;
 
1530
 
 
1531
 
 
1532
}, '3.3.0' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']});
 
1533
YUI.add('autocomplete-sources', function(Y) {
 
1534
 
 
1535
/**
 
1536
 * Mixes support for JSONP and YQL result sources into AutoCompleteBase.
 
1537
 *
 
1538
 * @module autocomplete
 
1539
 * @submodule autocomplete-sources
 
1540
 */
 
1541
 
 
1542
var Lang = Y.Lang,
 
1543
 
 
1544
    _SOURCE_SUCCESS = '_sourceSuccess',
 
1545
 
 
1546
    MAX_RESULTS         = 'maxResults',
 
1547
    REQUEST_TEMPLATE    = 'requestTemplate',
 
1548
    RESULT_LIST_LOCATOR = 'resultListLocator';
 
1549
 
 
1550
function ACSources() {}
 
1551
 
 
1552
ACSources.prototype = {
 
1553
    /**
 
1554
     * Regular expression used to determine whether a String source is a YQL
 
1555
     * query.
 
1556
     *
 
1557
     * @property _YQL_SOURCE_REGEX
 
1558
     * @type RegExp
 
1559
     * @protected
 
1560
     * @for AutoCompleteBase
 
1561
     */
 
1562
    _YQL_SOURCE_REGEX: /^(?:select|set|use)\s+/i,
 
1563
 
 
1564
    /**
 
1565
     * Creates a DataSource-like object that uses <code>Y.io</code> as a source.
 
1566
     * See the <code>source</code> attribute for more details.
 
1567
     *
 
1568
     * @method _createIOSource
 
1569
     * @param {String} source URL.
 
1570
     * @return {Object} DataSource-like object.
 
1571
     * @protected
 
1572
     * @for AutoCompleteBase
 
1573
     */
 
1574
    _createIOSource: function (source) {
 
1575
        var cache    = {},
 
1576
            ioSource = {},
 
1577
            that     = this,
 
1578
            ioRequest, lastRequest, loading;
 
1579
 
 
1580
        ioSource.sendRequest = function (request) {
 
1581
            var _sendRequest = function (request) {
 
1582
                var query = request.request,
 
1583
                    maxResults, requestTemplate, url;
 
1584
 
 
1585
                if (cache[query]) {
 
1586
                    that[_SOURCE_SUCCESS](cache[query], request);
 
1587
                } else {
 
1588
                    maxResults      = that.get(MAX_RESULTS);
 
1589
                    requestTemplate = that.get(REQUEST_TEMPLATE);
 
1590
                    url             = source;
 
1591
 
 
1592
                    if (requestTemplate) {
 
1593
                        url += requestTemplate(query);
 
1594
                    }
 
1595
 
 
1596
                    url = Lang.sub(url, {
 
1597
                        maxResults: maxResults > 0 ? maxResults : 1000,
 
1598
                        query     : encodeURIComponent(query)
 
1599
                    });
 
1600
 
 
1601
                    // Cancel any outstanding requests.
 
1602
                    if (ioRequest && ioRequest.isInProgress()) {
 
1603
                        ioRequest.abort();
 
1604
                    }
 
1605
 
 
1606
                    ioRequest = Y.io(url, {
 
1607
                        on: {
 
1608
                            success: function (tid, response) {
 
1609
                                var data;
 
1610
 
 
1611
                                try {
 
1612
                                    data = Y.JSON.parse(response.responseText);
 
1613
                                } catch (ex) {
 
1614
                                    Y.error('JSON parse error', ex);
 
1615
                                }
 
1616
 
 
1617
                                if (data) {
 
1618
                                    cache[query] = data;
 
1619
                                    that[_SOURCE_SUCCESS](data, request);
 
1620
                                }
 
1621
                            }
 
1622
                        }
 
1623
                    });
 
1624
                }
 
1625
            };
 
1626
 
 
1627
            // Keep track of the most recent request in case there are multiple
 
1628
            // requests while we're waiting for the IO module to load. Only the
 
1629
            // most recent request will be sent.
 
1630
            lastRequest = request;
 
1631
 
 
1632
            if (!loading) {
 
1633
                loading = true;
 
1634
 
 
1635
                // Lazy-load the io and json-parse modules if necessary, then
 
1636
                // overwrite the sendRequest method to bypass this check in the
 
1637
                // future.
 
1638
                Y.use('io-base', 'json-parse', function () {
 
1639
                    ioSource.sendRequest = _sendRequest;
 
1640
                    _sendRequest(lastRequest);
 
1641
                });
 
1642
            }
 
1643
        };
 
1644
 
 
1645
        return ioSource;
 
1646
    },
 
1647
 
 
1648
    /**
 
1649
     * Creates a DataSource-like object that uses the specified JSONPRequest
 
1650
     * instance as a source. See the <code>source</code> attribute for more
 
1651
     * details.
 
1652
     *
 
1653
     * @method _createJSONPSource
 
1654
     * @param {JSONPRequest|String} source URL string or JSONPRequest instance.
 
1655
     * @return {Object} DataSource-like object.
 
1656
     * @protected
 
1657
     * @for AutoCompleteBase
 
1658
     */
 
1659
    _createJSONPSource: function (source) {
 
1660
        var cache       = {},
 
1661
            jsonpSource = {},
 
1662
            that        = this,
 
1663
            lastRequest, loading;
 
1664
 
 
1665
        jsonpSource.sendRequest = function (request) {
 
1666
            var _sendRequest = function (request) {
 
1667
                var query = request.request;
 
1668
 
 
1669
                if (cache[query]) {
 
1670
                    that[_SOURCE_SUCCESS](cache[query], request);
 
1671
                } else {
 
1672
                    // Hack alert: JSONPRequest currently doesn't support
 
1673
                    // per-request callbacks, so we're reaching into the protected
 
1674
                    // _config object to make it happen.
 
1675
                    //
 
1676
                    // This limitation is mentioned in the following JSONP
 
1677
                    // enhancement ticket:
 
1678
                    //
 
1679
                    // http://yuilibrary.com/projects/yui3/ticket/2529371
 
1680
                    source._config.on.success = function (data) {
 
1681
                        cache[query] = data;
 
1682
                        that[_SOURCE_SUCCESS](data, request);
 
1683
                    };
 
1684
 
 
1685
                    source.send(query);
 
1686
                }
 
1687
            };
 
1688
 
 
1689
            // Keep track of the most recent request in case there are multiple
 
1690
            // requests while we're waiting for the JSONP module to load. Only
 
1691
            // the most recent request will be sent.
 
1692
            lastRequest = request;
 
1693
 
 
1694
            if (!loading) {
 
1695
                loading = true;
 
1696
 
 
1697
                // Lazy-load the JSONP module if necessary, then overwrite the
 
1698
                // sendRequest method to bypass this check in the future.
 
1699
                Y.use('jsonp', function () {
 
1700
                    // Turn the source into a JSONPRequest instance if it isn't
 
1701
                    // one already.
 
1702
                    if (!(source instanceof Y.JSONPRequest)) {
 
1703
                        source = new Y.JSONPRequest(source, {
 
1704
                            format: Y.bind(that._jsonpFormatter, that)
 
1705
                        });
 
1706
                    }
 
1707
 
 
1708
                    jsonpSource.sendRequest = _sendRequest;
 
1709
                    _sendRequest(lastRequest);
 
1710
                });
 
1711
            }
 
1712
        };
 
1713
 
 
1714
        return jsonpSource;
 
1715
    },
 
1716
 
 
1717
    /**
 
1718
     * Creates a DataSource-like object that calls the specified  URL or
 
1719
     * executes the specified YQL query for results. If the string starts
 
1720
     * with "select ", "use ", or "set " (case-insensitive), it's assumed to be
 
1721
     * a YQL query; otherwise, it's assumed to be a URL (which may be absolute
 
1722
     * or relative). URLs containing a "{callback}" placeholder are assumed to
 
1723
     * be JSONP URLs; all others will use XHR. See the <code>source</code>
 
1724
     * attribute for more details.
 
1725
     *
 
1726
     * @method _createStringSource
 
1727
     * @param {String} source URL or YQL query.
 
1728
     * @return {Object} DataSource-like object.
 
1729
     * @protected
 
1730
     * @for AutoCompleteBase
 
1731
     */
 
1732
    _createStringSource: function (source) {
 
1733
        if (this._YQL_SOURCE_REGEX.test(source)) {
 
1734
            // Looks like a YQL query.
 
1735
            return this._createYQLSource(source);
 
1736
        } else if (source.indexOf('{callback}') !== -1) {
 
1737
            // Contains a {callback} param and isn't a YQL query, so it must be
 
1738
            // JSONP.
 
1739
            return this._createJSONPSource(source);
 
1740
        } else {
 
1741
            // Not a YQL query or JSONP, so we'll assume it's an XHR URL.
 
1742
            return this._createIOSource(source);
 
1743
        }
 
1744
    },
 
1745
 
 
1746
    /**
 
1747
     * Creates a DataSource-like object that uses the specified YQL query string
 
1748
     * to create a YQL-based source. See the <code>source</code> attribute for
 
1749
     * details. If no <code>resultListLocator</code> is defined, this method
 
1750
     * will set a best-guess locator that might work for many typical YQL
 
1751
     * queries.
 
1752
     *
 
1753
     * @method _createYQLSource
 
1754
     * @param {String} source YQL query.
 
1755
     * @return {Object} DataSource-like object.
 
1756
     * @protected
 
1757
     * @for AutoCompleteBase
 
1758
     */
 
1759
    _createYQLSource: function (source) {
 
1760
        var cache     = {},
 
1761
            yqlSource = {},
 
1762
            that      = this,
 
1763
            lastRequest, loading;
 
1764
 
 
1765
        if (!this.get(RESULT_LIST_LOCATOR)) {
 
1766
            this.set(RESULT_LIST_LOCATOR, this._defaultYQLLocator);
 
1767
        }
 
1768
 
 
1769
        yqlSource.sendRequest = function (request) {
 
1770
            var yqlRequest,
 
1771
 
 
1772
            _sendRequest = function (request) {
 
1773
                var query = request.request,
 
1774
                    callback, env, maxResults, opts, yqlQuery;
 
1775
 
 
1776
                if (cache[query]) {
 
1777
                    that[_SOURCE_SUCCESS](cache[query], request);
 
1778
                } else {
 
1779
                    callback = function (data) {
 
1780
                        cache[query] = data;
 
1781
                        that[_SOURCE_SUCCESS](data, request);
 
1782
                    };
 
1783
 
 
1784
                    env        = that.get('yqlEnv');
 
1785
                    maxResults = that.get(MAX_RESULTS);
 
1786
 
 
1787
                    opts = {proto: that.get('yqlProtocol')};
 
1788
 
 
1789
                    yqlQuery = Lang.sub(source, {
 
1790
                        maxResults: maxResults > 0 ? maxResults : 1000,
 
1791
                        query     : query
 
1792
                    });
 
1793
 
 
1794
                    // Only create a new YQLRequest instance if this is the
 
1795
                    // first request. For subsequent requests, we'll reuse the
 
1796
                    // original instance.
 
1797
                    if (yqlRequest) {
 
1798
                        yqlRequest._callback   = callback;
 
1799
                        yqlRequest._opts       = opts;
 
1800
                        yqlRequest._params.q   = yqlQuery;
 
1801
 
 
1802
                        if (env) {
 
1803
                            yqlRequest._params.env = env;
 
1804
                        }
 
1805
                    } else {
 
1806
                        yqlRequest = new Y.YQLRequest(yqlQuery, {
 
1807
                            on: {success: callback},
 
1808
                            allowCache: false // temp workaround until JSONP has per-URL callback proxies
 
1809
                        }, env ? {env: env} : null, opts);
 
1810
                    }
 
1811
 
 
1812
                    yqlRequest.send();
 
1813
                }
 
1814
            };
 
1815
 
 
1816
            // Keep track of the most recent request in case there are multiple
 
1817
            // requests while we're waiting for the YQL module to load. Only the
 
1818
            // most recent request will be sent.
 
1819
            lastRequest = request;
 
1820
 
 
1821
            if (!loading) {
 
1822
                // Lazy-load the YQL module if necessary, then overwrite the
 
1823
                // sendRequest method to bypass this check in the future.
 
1824
                loading = true;
 
1825
 
 
1826
                Y.use('yql', function () {
 
1827
                    yqlSource.sendRequest = _sendRequest;
 
1828
                    _sendRequest(lastRequest);
 
1829
                });
 
1830
            }
 
1831
        };
 
1832
 
 
1833
        return yqlSource;
 
1834
    },
 
1835
 
 
1836
    /**
 
1837
     * Default resultListLocator used when a string-based YQL source is set and
 
1838
     * the implementer hasn't already specified one.
 
1839
     *
 
1840
     * @method _defaultYQLLocator
 
1841
     * @param {Object} response YQL response object.
 
1842
     * @return {Array}
 
1843
     * @protected
 
1844
     * @for AutoCompleteBase
 
1845
     */
 
1846
    _defaultYQLLocator: function (response) {
 
1847
        var results = response && response.query && response.query.results,
 
1848
            values;
 
1849
 
 
1850
        if (results && Lang.isObject(results)) {
 
1851
            // If there's only a single value on YQL's results object, that
 
1852
            // value almost certainly contains the array of results we want. If
 
1853
            // there are 0 or 2+ values, then the values themselves are most
 
1854
            // likely the results we want.
 
1855
            values  = Y.Object.values(results) || [];
 
1856
            results = values.length === 1 ? values[0] : values;
 
1857
 
 
1858
            if (!Lang.isArray(results)) {
 
1859
                results = [results];
 
1860
            }
 
1861
        } else {
 
1862
            results = [];
 
1863
        }
 
1864
 
 
1865
        return results;
 
1866
    },
 
1867
 
 
1868
    /**
 
1869
     * URL formatter passed to <code>JSONPRequest</code> instances.
 
1870
     *
 
1871
     * @method _jsonpFormatter
 
1872
     * @param {String} url
 
1873
     * @param {String} proxy
 
1874
     * @param {String} query
 
1875
     * @return {String} Formatted URL
 
1876
     * @protected
 
1877
     * @for AutoCompleteBase
 
1878
     */
 
1879
    _jsonpFormatter: function (url, proxy, query) {
 
1880
        var maxResults      = this.get(MAX_RESULTS),
 
1881
            requestTemplate = this.get(REQUEST_TEMPLATE);
 
1882
 
 
1883
        if (requestTemplate) {
 
1884
            url += requestTemplate(query);
 
1885
        }
 
1886
 
 
1887
        return Lang.sub(url, {
 
1888
            callback  : proxy,
 
1889
            maxResults: maxResults > 0 ? maxResults : 1000,
 
1890
            query     : encodeURIComponent(query)
 
1891
        });
 
1892
    }
 
1893
};
 
1894
 
 
1895
ACSources.ATTRS = {
 
1896
    /**
 
1897
     * YQL environment file URL to load when the <code>source</code> is set to
 
1898
     * a YQL query. Set this to <code>null</code> to use the default Open Data
 
1899
     * Tables environment file (http://datatables.org/alltables.env).
 
1900
     *
 
1901
     * @attribute yqlEnv
 
1902
     * @type String
 
1903
     * @default null
 
1904
     * @for AutoCompleteBase
 
1905
     */
 
1906
    yqlEnv: {
 
1907
        value: null
 
1908
    },
 
1909
 
 
1910
    /**
 
1911
     * URL protocol to use when the <code>source</code> is set to a YQL query.
 
1912
     *
 
1913
     * @attribute yqlProtocol
 
1914
     * @type String
 
1915
     * @default 'http'
 
1916
     * @for AutoCompleteBase
 
1917
     */
 
1918
    yqlProtocol: {
 
1919
        value: 'http'
 
1920
    }
 
1921
};
 
1922
 
 
1923
Y.Base.mix(Y.AutoCompleteBase, [ACSources]);
 
1924
 
 
1925
 
 
1926
}, '3.3.0' ,{optional:['io-base', 'json-parse', 'jsonp', 'yql'], requires:['autocomplete-base']});
 
1927
YUI.add('autocomplete-list', function(Y) {
 
1928
 
 
1929
/**
 
1930
 * Traditional autocomplete dropdown list widget, just like Mom used to make.
 
1931
 *
 
1932
 * @module autocomplete
 
1933
 * @submodule autocomplete-list
 
1934
 * @class AutoCompleteList
 
1935
 * @extends Widget
 
1936
 * @uses AutoCompleteBase
 
1937
 * @uses WidgetPosition
 
1938
 * @uses WidgetPositionAlign
 
1939
 * @uses WidgetStack
 
1940
 * @constructor
 
1941
 * @param {Object} config Configuration object.
 
1942
 */
 
1943
 
 
1944
var Lang   = Y.Lang,
 
1945
    Node   = Y.Node,
 
1946
    YArray = Y.Array,
 
1947
 
 
1948
    // keyCode constants.
 
1949
    KEY_TAB = 9,
 
1950
 
 
1951
    // String shorthand.
 
1952
    _CLASS_ITEM        = '_CLASS_ITEM',
 
1953
    _CLASS_ITEM_ACTIVE = '_CLASS_ITEM_ACTIVE',
 
1954
    _CLASS_ITEM_HOVER  = '_CLASS_ITEM_HOVER',
 
1955
    _SELECTOR_ITEM     = '_SELECTOR_ITEM',
 
1956
 
 
1957
    ACTIVE_ITEM      = 'activeItem',
 
1958
    ALWAYS_SHOW_LIST = 'alwaysShowList',
 
1959
    CIRCULAR         = 'circular',
 
1960
    HOVERED_ITEM     = 'hoveredItem',
 
1961
    ID               = 'id',
 
1962
    ITEM             = 'item',
 
1963
    LIST             = 'list',
 
1964
    RESULT           = 'result',
 
1965
    RESULTS          = 'results',
 
1966
    VISIBLE          = 'visible',
 
1967
    WIDTH            = 'width',
 
1968
 
 
1969
    // Event names.
 
1970
    EVT_SELECT = 'select',
 
1971
 
 
1972
List = Y.Base.create('autocompleteList', Y.Widget, [
 
1973
    Y.AutoCompleteBase,
 
1974
    Y.WidgetPosition,
 
1975
    Y.WidgetPositionAlign,
 
1976
    Y.WidgetStack
 
1977
], {
 
1978
    // -- Prototype Properties -------------------------------------------------
 
1979
    ARIA_TEMPLATE: '<div/>',
 
1980
    ITEM_TEMPLATE: '<li/>',
 
1981
    LIST_TEMPLATE: '<ul/>',
 
1982
 
 
1983
    // -- Lifecycle Prototype Methods ------------------------------------------
 
1984
    initializer: function () {
 
1985
        var inputNode = this.get('inputNode');
 
1986
 
 
1987
        if (!inputNode) {
 
1988
            Y.error('No inputNode specified.');
 
1989
            return;
 
1990
        }
 
1991
 
 
1992
        this._inputNode  = inputNode;
 
1993
        this._listEvents = [];
 
1994
 
 
1995
        // This ensures that the list is rendered inside the same parent as the
 
1996
        // input node by default, which is necessary for proper ARIA support.
 
1997
        this.DEF_PARENT_NODE = inputNode.get('parentNode');
 
1998
 
 
1999
        // Cache commonly used classnames and selectors for performance.
 
2000
        this[_CLASS_ITEM]        = this.getClassName(ITEM);
 
2001
        this[_CLASS_ITEM_ACTIVE] = this.getClassName(ITEM, 'active');
 
2002
        this[_CLASS_ITEM_HOVER]  = this.getClassName(ITEM, 'hover');
 
2003
        this[_SELECTOR_ITEM]     = '.' + this[_CLASS_ITEM];
 
2004
 
 
2005
        /**
 
2006
         * Fires when an autocomplete suggestion is selected from the list,
 
2007
         * typically via a keyboard action or mouse click.
 
2008
         *
 
2009
         * @event select
 
2010
         * @param {EventFacade} e Event facade with the following additional
 
2011
         *   properties:
 
2012
         *
 
2013
         * <dl>
 
2014
         *   <dt>itemNode (Node)</dt>
 
2015
         *   <dd>
 
2016
         *     List item node that was selected.
 
2017
         *   </dd>
 
2018
         *
 
2019
         *   <dt>result (Object)</dt>
 
2020
         *   <dd>
 
2021
         *     AutoComplete result object.
 
2022
         *   </dd>
 
2023
         * </dl>
 
2024
         *
 
2025
         * @preventable _defSelectFn
 
2026
         */
 
2027
        this.publish(EVT_SELECT, {
 
2028
            defaultFn: this._defSelectFn
 
2029
        });
 
2030
    },
 
2031
 
 
2032
    destructor: function () {
 
2033
        while (this._listEvents.length) {
 
2034
            this._listEvents.pop().detach();
 
2035
        }
 
2036
    },
 
2037
 
 
2038
    bindUI: function () {
 
2039
        this._bindInput();
 
2040
        this._bindList();
 
2041
    },
 
2042
 
 
2043
    renderUI: function () {
 
2044
        var ariaNode   = this._createAriaNode(),
 
2045
            contentBox = this.get('contentBox'),
 
2046
            inputNode  = this._inputNode,
 
2047
            listNode,
 
2048
            parentNode = inputNode.get('parentNode');
 
2049
 
 
2050
        listNode = this._createListNode();
 
2051
        this._set('listNode', listNode);
 
2052
        contentBox.append(listNode);
 
2053
 
 
2054
        inputNode.addClass(this.getClassName('input')).setAttrs({
 
2055
            'aria-autocomplete': LIST,
 
2056
            'aria-expanded'    : false,
 
2057
            'aria-owns'        : listNode.get('id'),
 
2058
            role               : 'combobox'
 
2059
        });
 
2060
 
 
2061
        // ARIA node must be outside the widget or announcements won't be made
 
2062
        // when the widget is hidden.
 
2063
        parentNode.append(ariaNode);
 
2064
 
 
2065
        this._ariaNode    = ariaNode;
 
2066
        this._boundingBox = this.get('boundingBox');
 
2067
        this._contentBox  = contentBox;
 
2068
        this._listNode    = listNode;
 
2069
        this._parentNode  = parentNode;
 
2070
    },
 
2071
 
 
2072
    syncUI: function () {
 
2073
        this._syncResults();
 
2074
        this._syncVisibility();
 
2075
    },
 
2076
 
 
2077
    // -- Public Prototype Methods ---------------------------------------------
 
2078
 
 
2079
    /**
 
2080
     * Hides the list, unless the <code>alwaysShowList</code> attribute is
 
2081
     * <code>true</code>.
 
2082
     *
 
2083
     * @method hide
 
2084
     * @see show
 
2085
     * @chainable
 
2086
     */
 
2087
    hide: function () {
 
2088
        return this.get(ALWAYS_SHOW_LIST) ? this : this.set(VISIBLE, false);
 
2089
    },
 
2090
 
 
2091
    /**
 
2092
     * Selects the specified <i>itemNode</i>, or the current
 
2093
     * <code>activeItem</code> if <i>itemNode</i> is not specified.
 
2094
     *
 
2095
     * @method selectItem
 
2096
     * @param {Node} itemNode (optional) Item node to select.
 
2097
     * @chainable
 
2098
     */
 
2099
    selectItem: function (itemNode) {
 
2100
        if (itemNode) {
 
2101
            if (!itemNode.hasClass(this[_CLASS_ITEM])) {
 
2102
                return this;
 
2103
            }
 
2104
        } else {
 
2105
            itemNode = this.get(ACTIVE_ITEM);
 
2106
 
 
2107
            if (!itemNode) {
 
2108
                return this;
 
2109
            }
 
2110
        }
 
2111
 
 
2112
        this.fire(EVT_SELECT, {
 
2113
            itemNode: itemNode,
 
2114
            result  : itemNode.getData(RESULT)
 
2115
        });
 
2116
 
 
2117
        return this;
 
2118
    },
 
2119
 
 
2120
    // -- Protected Prototype Methods ------------------------------------------
 
2121
 
 
2122
    /**
 
2123
     * Activates the next item after the currently active item. If there is no
 
2124
     * next item and the <code>circular</code> attribute is <code>true</code>,
 
2125
     * focus will wrap back to the input node.
 
2126
     *
 
2127
     * @method _activateNextItem
 
2128
     * @chainable
 
2129
     * @protected
 
2130
     */
 
2131
    _activateNextItem: function () {
 
2132
        var item = this.get(ACTIVE_ITEM),
 
2133
            nextItem;
 
2134
 
 
2135
        if (item) {
 
2136
            nextItem = item.next(this[_SELECTOR_ITEM]) ||
 
2137
                    (this.get(CIRCULAR) ? null : item);
 
2138
        } else {
 
2139
            nextItem = this._getFirstItemNode();
 
2140
        }
 
2141
 
 
2142
        this.set(ACTIVE_ITEM, nextItem);
 
2143
 
 
2144
        return this;
 
2145
    },
 
2146
 
 
2147
    /**
 
2148
     * Activates the item previous to the currently active item. If there is no
 
2149
     * previous item and the <code>circular</code> attribute is
 
2150
     * <code>true</code>, focus will wrap back to the input node.
 
2151
     *
 
2152
     * @method _activatePrevItem
 
2153
     * @chainable
 
2154
     * @protected
 
2155
     */
 
2156
    _activatePrevItem: function () {
 
2157
        var item     = this.get(ACTIVE_ITEM),
 
2158
            prevItem = item ? item.previous(this[_SELECTOR_ITEM]) :
 
2159
                    this.get(CIRCULAR) && this._getLastItemNode();
 
2160
 
 
2161
        this.set(ACTIVE_ITEM, prevItem || null);
 
2162
 
 
2163
        return this;
 
2164
    },
 
2165
 
 
2166
    /**
 
2167
     * Appends the specified result <i>items</i> to the list inside a new item
 
2168
     * node.
 
2169
     *
 
2170
     * @method _add
 
2171
     * @param {Array|Node|HTMLElement|String} items Result item or array of
 
2172
     *   result items.
 
2173
     * @return {NodeList} Added nodes.
 
2174
     * @protected
 
2175
     */
 
2176
    _add: function (items) {
 
2177
        var itemNodes = [];
 
2178
 
 
2179
        YArray.each(Lang.isArray(items) ? items : [items], function (item) {
 
2180
            itemNodes.push(this._createItemNode(item).setData(RESULT, item));
 
2181
        }, this);
 
2182
 
 
2183
        itemNodes = Y.all(itemNodes);
 
2184
        this._listNode.append(itemNodes.toFrag());
 
2185
 
 
2186
        return itemNodes;
 
2187
    },
 
2188
 
 
2189
    /**
 
2190
     * Updates the ARIA live region with the specified message.
 
2191
     *
 
2192
     * @method _ariaSay
 
2193
     * @param {String} stringId String id (from the <code>strings</code>
 
2194
     *   attribute) of the message to speak.
 
2195
     * @param {Object} subs (optional) Substitutions for placeholders in the
 
2196
     *   string.
 
2197
     * @protected
 
2198
     */
 
2199
    _ariaSay: function (stringId, subs) {
 
2200
        var message = this.get('strings.' + stringId);
 
2201
        this._ariaNode.setContent(subs ? Lang.sub(message, subs) : message);
 
2202
    },
 
2203
 
 
2204
    /**
 
2205
     * Binds <code>inputNode</code> events and behavior.
 
2206
     *
 
2207
     * @method _bindInput
 
2208
     * @protected
 
2209
     */
 
2210
    _bindInput: function () {
 
2211
        var inputNode = this._inputNode,
 
2212
            alignNode, alignWidth, tokenInput;
 
2213
 
 
2214
        // Null align means we can auto-align. Set align to false to prevent
 
2215
        // auto-alignment, or a valid alignment config to customize the
 
2216
        // alignment.
 
2217
        if (this.get('align') === null) {
 
2218
            // If this is a tokenInput, align with its bounding box.
 
2219
            // Otherwise, align with the inputNode. Bit of a cheat.
 
2220
            tokenInput = this.get('tokenInput');
 
2221
            alignNode  = (tokenInput && tokenInput.get('boundingBox')) || inputNode;
 
2222
 
 
2223
            this.set('align', {
 
2224
                node  : alignNode,
 
2225
                points: ['tl', 'bl']
 
2226
            });
 
2227
 
 
2228
            // If no width config is set, attempt to set the list's width to the
 
2229
            // width of the alignment node. If the alignment node's width is
 
2230
            // falsy, do nothing.
 
2231
            if (!this.get(WIDTH) && (alignWidth = alignNode.get('offsetWidth'))) {
 
2232
                this.set(WIDTH, alignWidth);
 
2233
            }
 
2234
        }
 
2235
 
 
2236
        // Attach inputNode events.
 
2237
        this._listEvents.push(inputNode.on('blur', this._onListInputBlur, this));
 
2238
    },
 
2239
 
 
2240
    /**
 
2241
     * Binds list events.
 
2242
     *
 
2243
     * @method _bindList
 
2244
     * @protected
 
2245
     */
 
2246
    _bindList: function () {
 
2247
        this._listEvents.concat([
 
2248
            this.after({
 
2249
              mouseover: this._afterMouseOver,
 
2250
              mouseout : this._afterMouseOut,
 
2251
 
 
2252
              activeItemChange    : this._afterActiveItemChange,
 
2253
              alwaysShowListChange: this._afterAlwaysShowListChange,
 
2254
              hoveredItemChange   : this._afterHoveredItemChange,
 
2255
              resultsChange       : this._afterResultsChange,
 
2256
              visibleChange       : this._afterVisibleChange
 
2257
            }),
 
2258
 
 
2259
            this._listNode.delegate('click', this._onItemClick, this[_SELECTOR_ITEM], this)
 
2260
        ]);
 
2261
    },
 
2262
 
 
2263
    /**
 
2264
     * Clears the contents of the tray.
 
2265
     *
 
2266
     * @method _clear
 
2267
     * @protected
 
2268
     */
 
2269
    _clear: function () {
 
2270
        this.set(ACTIVE_ITEM, null);
 
2271
        this._set(HOVERED_ITEM, null);
 
2272
 
 
2273
        this._listNode.get('children').remove(true);
 
2274
    },
 
2275
 
 
2276
    /**
 
2277
     * Creates and returns an ARIA live region node.
 
2278
     *
 
2279
     * @method _createAriaNode
 
2280
     * @return {Node} ARIA node.
 
2281
     * @protected
 
2282
     */
 
2283
    _createAriaNode: function () {
 
2284
        var ariaNode = Node.create(this.ARIA_TEMPLATE);
 
2285
 
 
2286
        return ariaNode.addClass(this.getClassName('aria')).setAttrs({
 
2287
            'aria-live': 'polite',
 
2288
            role       : 'status'
 
2289
        });
 
2290
    },
 
2291
 
 
2292
    /**
 
2293
     * Creates and returns an item node with the specified <i>content</i>.
 
2294
     *
 
2295
     * @method _createItemNode
 
2296
     * @param {Object} result Result object.
 
2297
     * @return {Node} Item node.
 
2298
     * @protected
 
2299
     */
 
2300
    _createItemNode: function (result) {
 
2301
        var itemNode = Node.create(this.ITEM_TEMPLATE);
 
2302
 
 
2303
        return itemNode.addClass(this[_CLASS_ITEM]).setAttrs({
 
2304
            id  : Y.stamp(itemNode),
 
2305
            role: 'option'
 
2306
        }).setAttribute('data-text', result.text).append(result.display);
 
2307
    },
 
2308
 
 
2309
    /**
 
2310
     * Creates and returns a list node.
 
2311
     *
 
2312
     * @method _createListNode
 
2313
     * @return {Node} List node.
 
2314
     * @protected
 
2315
     */
 
2316
    _createListNode: function () {
 
2317
        var listNode = Node.create(this.LIST_TEMPLATE);
 
2318
 
 
2319
        return listNode.addClass(this.getClassName(LIST)).setAttrs({
 
2320
            id  : Y.stamp(listNode),
 
2321
            role: 'listbox'
 
2322
        });
 
2323
    },
 
2324
 
 
2325
    /**
 
2326
     * Gets the first item node in the list, or <code>null</code> if the list is
 
2327
     * empty.
 
2328
     *
 
2329
     * @method _getFirstItemNode
 
2330
     * @return {Node|null}
 
2331
     * @protected
 
2332
     */
 
2333
    _getFirstItemNode: function () {
 
2334
        return this._listNode.one(this[_SELECTOR_ITEM]);
 
2335
    },
 
2336
 
 
2337
    /**
 
2338
     * Gets the last item node in the list, or <code>null</code> if the list is
 
2339
     * empty.
 
2340
     *
 
2341
     * @method _getLastItemNode
 
2342
     * @return {Node|null}
 
2343
     * @protected
 
2344
     */
 
2345
    _getLastItemNode: function () {
 
2346
        return this._listNode.one(this[_SELECTOR_ITEM] + ':last-child');
 
2347
    },
 
2348
 
 
2349
    /**
 
2350
     * Synchronizes the results displayed in the list with those in the
 
2351
     * <i>results</i> argument, or with the <code>results</code> attribute if an
 
2352
     * argument is not provided.
 
2353
     *
 
2354
     * @method _syncResults
 
2355
     * @param {Array} results (optional) Results.
 
2356
     * @protected
 
2357
     */
 
2358
    _syncResults: function (results) {
 
2359
        var items;
 
2360
 
 
2361
        if (!results) {
 
2362
            results = this.get(RESULTS);
 
2363
        }
 
2364
 
 
2365
        this._clear();
 
2366
 
 
2367
        if (results.length) {
 
2368
            items = this._add(results);
 
2369
            this._ariaSay('items_available');
 
2370
        }
 
2371
 
 
2372
        if (this.get('activateFirstItem') && !this.get(ACTIVE_ITEM)) {
 
2373
            this.set(ACTIVE_ITEM, this._getFirstItemNode());
 
2374
        }
 
2375
    },
 
2376
 
 
2377
    /**
 
2378
     * Synchronizes the visibility of the tray with the <i>visible</i> argument,
 
2379
     * or with the <code>visible</code> attribute if an argument is not
 
2380
     * provided.
 
2381
     *
 
2382
     * @method _syncVisibility
 
2383
     * @param {Boolean} visible (optional) Visibility.
 
2384
     * @protected
 
2385
     */
 
2386
    _syncVisibility: function (visible) {
 
2387
        if (this.get(ALWAYS_SHOW_LIST)) {
 
2388
            visible = true;
 
2389
            this.set(VISIBLE, visible);
 
2390
        }
 
2391
 
 
2392
        if (typeof visible === 'undefined') {
 
2393
            visible = this.get(VISIBLE);
 
2394
        }
 
2395
 
 
2396
        this._inputNode.set('aria-expanded', visible);
 
2397
        this._boundingBox.set('aria-hidden', !visible);
 
2398
 
 
2399
        if (visible) {
 
2400
            // Force WidgetPositionAlign to refresh its alignment.
 
2401
            this._syncUIPosAlign();
 
2402
        } else {
 
2403
            this.set(ACTIVE_ITEM, null);
 
2404
            this._set(HOVERED_ITEM, null);
 
2405
 
 
2406
            // Force a reflow to work around a glitch in IE6 and 7 where some of
 
2407
            // the contents of the list will sometimes remain visible after the
 
2408
            // container is hidden.
 
2409
            this._boundingBox.get('offsetWidth');
 
2410
        }
 
2411
    },
 
2412
 
 
2413
    // -- Protected Event Handlers ---------------------------------------------
 
2414
 
 
2415
    /**
 
2416
     * Handles <code>activeItemChange</code> events.
 
2417
     *
 
2418
     * @method _afterActiveItemChange
 
2419
     * @param {EventTarget} e
 
2420
     * @protected
 
2421
     */
 
2422
    _afterActiveItemChange: function (e) {
 
2423
        var inputNode = this._inputNode,
 
2424
            newVal    = e.newVal,
 
2425
            prevVal   = e.prevVal;
 
2426
 
 
2427
        // The previous item may have disappeared by the time this handler runs,
 
2428
        // so we need to be careful.
 
2429
        if (prevVal && prevVal._node) {
 
2430
            prevVal.removeClass(this[_CLASS_ITEM_ACTIVE]);
 
2431
        }
 
2432
 
 
2433
        if (newVal) {
 
2434
            newVal.addClass(this[_CLASS_ITEM_ACTIVE]);
 
2435
            inputNode.set('aria-activedescendant', newVal.get(ID));
 
2436
        } else {
 
2437
            inputNode.removeAttribute('aria-activedescendant');
 
2438
        }
 
2439
 
 
2440
        if (this.get('scrollIntoView')) {
 
2441
            (newVal || inputNode).scrollIntoView();
 
2442
        }
 
2443
    },
 
2444
 
 
2445
    /**
 
2446
     * Handles <code>alwaysShowListChange</code> events.
 
2447
     *
 
2448
     * @method _afterAlwaysShowListChange
 
2449
     * @param {EventTarget} e
 
2450
     * @protected
 
2451
     */
 
2452
    _afterAlwaysShowListChange: function (e) {
 
2453
        this.set(VISIBLE, e.newVal || this.get(RESULTS).length > 0);
 
2454
    },
 
2455
 
 
2456
    /**
 
2457
     * Handles <code>hoveredItemChange</code> events.
 
2458
     *
 
2459
     * @method _afterHoveredItemChange
 
2460
     * @param {EventTarget} e
 
2461
     * @protected
 
2462
     */
 
2463
    _afterHoveredItemChange: function (e) {
 
2464
        var newVal  = e.newVal,
 
2465
            prevVal = e.prevVal;
 
2466
 
 
2467
        if (prevVal) {
 
2468
            prevVal.removeClass(this[_CLASS_ITEM_HOVER]);
 
2469
        }
 
2470
 
 
2471
        if (newVal) {
 
2472
            newVal.addClass(this[_CLASS_ITEM_HOVER]);
 
2473
        }
 
2474
    },
 
2475
 
 
2476
    /**
 
2477
     * Handles <code>mouseover</code> events.
 
2478
     *
 
2479
     * @method _afterMouseOver
 
2480
     * @param {EventTarget} e
 
2481
     * @protected
 
2482
     */
 
2483
    _afterMouseOver: function (e) {
 
2484
        var itemNode = e.domEvent.target.ancestor(this[_SELECTOR_ITEM], true);
 
2485
 
 
2486
        this._mouseOverList = true;
 
2487
 
 
2488
        if (itemNode) {
 
2489
            this._set(HOVERED_ITEM, itemNode);
 
2490
        }
 
2491
    },
 
2492
 
 
2493
    /**
 
2494
     * Handles <code>mouseout</code> events.
 
2495
     *
 
2496
     * @method _afterMouseOut
 
2497
     * @param {EventTarget} e
 
2498
     * @protected
 
2499
     */
 
2500
    _afterMouseOut: function () {
 
2501
        this._mouseOverList = false;
 
2502
        this._set(HOVERED_ITEM, null);
 
2503
    },
 
2504
 
 
2505
    /**
 
2506
     * Handles <code>resultsChange</code> events.
 
2507
     *
 
2508
     * @method _afterResultsChange
 
2509
     * @param {EventFacade} e
 
2510
     * @protected
 
2511
     */
 
2512
    _afterResultsChange: function (e) {
 
2513
        this._syncResults(e.newVal);
 
2514
 
 
2515
        if (!this.get(ALWAYS_SHOW_LIST)) {
 
2516
            this.set(VISIBLE, !!e.newVal.length);
 
2517
        }
 
2518
    },
 
2519
 
 
2520
    /**
 
2521
     * Handles <code>visibleChange</code> events.
 
2522
     *
 
2523
     * @method _afterVisibleChange
 
2524
     * @param {EventFacade} e
 
2525
     * @protected
 
2526
     */
 
2527
    _afterVisibleChange: function (e) {
 
2528
        this._syncVisibility(!!e.newVal);
 
2529
    },
 
2530
 
 
2531
    /**
 
2532
     * Handles <code>inputNode</code> <code>blur</code> events.
 
2533
     *
 
2534
     * @method _onListInputBlur
 
2535
     * @param {EventTarget} e
 
2536
     * @protected
 
2537
     */
 
2538
    _onListInputBlur: function (e) {
 
2539
        // Hide the list on inputNode blur events, unless the mouse is currently
 
2540
        // over the list (which indicates that the user is probably interacting
 
2541
        // with it). The _lastInputKey property comes from the
 
2542
        // autocomplete-list-keys module.
 
2543
        if (!this._mouseOverList || this._lastInputKey === KEY_TAB) {
 
2544
            this.hide();
 
2545
        }
 
2546
    },
 
2547
 
 
2548
    /**
 
2549
     * Delegated event handler for item <code>click</code> events.
 
2550
     *
 
2551
     * @method _onItemClick
 
2552
     * @param {EventTarget} e
 
2553
     * @protected
 
2554
     */
 
2555
    _onItemClick: function (e) {
 
2556
        var itemNode = e.currentTarget;
 
2557
 
 
2558
        this.set(ACTIVE_ITEM, itemNode);
 
2559
        this.selectItem(itemNode);
 
2560
    },
 
2561
 
 
2562
    // -- Protected Default Event Handlers -------------------------------------
 
2563
 
 
2564
    /**
 
2565
     * Default <code>select</code> event handler.
 
2566
     *
 
2567
     * @method _defSelectFn
 
2568
     * @param {EventTarget} e
 
2569
     * @protected
 
2570
     */
 
2571
    _defSelectFn: function (e) {
 
2572
        var text = e.result.text;
 
2573
 
 
2574
        // TODO: support typeahead completion, etc.
 
2575
        this._inputNode.focus();
 
2576
        this._updateValue(text);
 
2577
        this._ariaSay('item_selected', {item: text});
 
2578
        this.hide();
 
2579
    }
 
2580
}, {
 
2581
    ATTRS: {
 
2582
        /**
 
2583
         * If <code>true</code>, the first item in the list will be activated by
 
2584
         * default when the list is initially displayed and when results change.
 
2585
         *
 
2586
         * @attribute activateFirstItem
 
2587
         * @type Boolean
 
2588
         * @default false
 
2589
         */
 
2590
        activateFirstItem: {
 
2591
            value: false
 
2592
        },
 
2593
 
 
2594
        /**
 
2595
         * Item that's currently active, if any. When the user presses enter,
 
2596
         * this is the item that will be selected.
 
2597
         *
 
2598
         * @attribute activeItem
 
2599
         * @type Node
 
2600
         */
 
2601
        activeItem: {
 
2602
            setter: Y.one,
 
2603
            value: null
 
2604
        },
 
2605
 
 
2606
        /**
 
2607
         * If <code>true</code>, the list will remain visible even when there
 
2608
         * are no results to display.
 
2609
         *
 
2610
         * @attribute alwaysShowList
 
2611
         * @type Boolean
 
2612
         * @default false
 
2613
         */
 
2614
        alwaysShowList: {
 
2615
            value: false
 
2616
        },
 
2617
 
 
2618
        /**
 
2619
         * If <code>true</code>, keyboard navigation will wrap around to the
 
2620
         * opposite end of the list when navigating past the first or last item.
 
2621
         *
 
2622
         * @attribute circular
 
2623
         * @type Boolean
 
2624
         * @default true
 
2625
         */
 
2626
        circular: {
 
2627
            value: true
 
2628
        },
 
2629
 
 
2630
        /**
 
2631
         * Item currently being hovered over by the mouse, if any.
 
2632
         *
 
2633
         * @attribute hoveredItem
 
2634
         * @type Node|null
 
2635
         * @readonly
 
2636
         */
 
2637
        hoveredItem: {
 
2638
            readOnly: true,
 
2639
            value: null
 
2640
        },
 
2641
 
 
2642
        /**
 
2643
         * Node that will contain result items.
 
2644
         *
 
2645
         * @attribute listNode
 
2646
         * @type Node|null
 
2647
         * @readonly
 
2648
         */
 
2649
        listNode: {
 
2650
            readOnly: true,
 
2651
            value: null
 
2652
        },
 
2653
 
 
2654
        /**
 
2655
         * If <code>true</code>, the viewport will be scrolled to ensure that
 
2656
         * the active list item is visible when necessary.
 
2657
         *
 
2658
         * @attribute scrollIntoView
 
2659
         * @type Boolean
 
2660
         * @default false
 
2661
         */
 
2662
        scrollIntoView: {
 
2663
            value: false
 
2664
        },
 
2665
 
 
2666
        /**
 
2667
         * Translatable strings used by the AutoCompleteList widget.
 
2668
         *
 
2669
         * @attribute strings
 
2670
         * @type Object
 
2671
         */
 
2672
        strings: {
 
2673
            valueFn: function () {
 
2674
                return Y.Intl.get('autocomplete-list');
 
2675
            }
 
2676
        },
 
2677
 
 
2678
        /**
 
2679
         * If <code>true</code>, pressing the tab key while the list is visible
 
2680
         * will select the active item, if any.
 
2681
         *
 
2682
         * @attribute tabSelect
 
2683
         * @type Boolean
 
2684
         * @default true
 
2685
         */
 
2686
        tabSelect: {
 
2687
            value: true
 
2688
        },
 
2689
 
 
2690
        // The "visible" attribute is documented in Widget.
 
2691
        visible: {
 
2692
            value: false
 
2693
        }
 
2694
    },
 
2695
 
 
2696
    CSS_PREFIX: Y.ClassNameManager.getClassName('aclist')
 
2697
});
 
2698
 
 
2699
Y.AutoCompleteList = List;
 
2700
 
 
2701
/**
 
2702
 * Alias for <a href="AutoCompleteList.html"><code>AutoCompleteList</code></a>.
 
2703
 * See that class for API docs.
 
2704
 *
 
2705
 * @class AutoComplete
 
2706
 */
 
2707
 
 
2708
Y.AutoComplete = List;
 
2709
 
 
2710
 
 
2711
}, '3.3.0' ,{lang:['en'], requires:['autocomplete-base', 'selector-css3', 'widget', 'widget-position', 'widget-position-align', 'widget-stack'], after:['autocomplete-sources'], skinnable:true});
 
2712
YUI.add('autocomplete-plugin', function(Y) {
 
2713
 
 
2714
/**
 
2715
 * Binds an AutoCompleteList instance to a Node instance.
 
2716
 *
 
2717
 * @module autocomplete
 
2718
 * @submodule autocomplete-plugin
 
2719
 */
 
2720
 
 
2721
/**
 
2722
 * <p>
 
2723
 * Binds an AutoCompleteList instance to a Node instance.
 
2724
 * </p>
 
2725
 *
 
2726
 * <p>
 
2727
 * Example:
 
2728
 * </p>
 
2729
 *
 
2730
 * <pre>
 
2731
 * Y.one('#my-input').plug(Y.Plugin.AutoComplete, {
 
2732
 * &nbsp;&nbsp;source: 'select * from search.suggest where query="{query}"'
 
2733
 * });
 
2734
 * &nbsp;
 
2735
 * // You can now access the AutoCompleteList instance at Y.one('#my-input').ac
 
2736
 * </pre>
 
2737
 *
 
2738
 * @class Plugin.AutoComplete
 
2739
 * @extends AutoCompleteList
 
2740
 */
 
2741
 
 
2742
var Plugin = Y.Plugin;
 
2743
 
 
2744
function ACListPlugin(config) {
 
2745
    config.inputNode = config.host;
 
2746
 
 
2747
    // Render by default.
 
2748
    if (!config.render && config.render !== false) {
 
2749
      config.render = true;
 
2750
    }
 
2751
 
 
2752
    ACListPlugin.superclass.constructor.apply(this, arguments);
 
2753
}
 
2754
 
 
2755
Y.extend(ACListPlugin, Y.AutoCompleteList, {}, {
 
2756
    NAME      : 'autocompleteListPlugin',
 
2757
    NS        : 'ac',
 
2758
    CSS_PREFIX: Y.ClassNameManager.getClassName('aclist')
 
2759
});
 
2760
 
 
2761
Plugin.AutoComplete     = ACListPlugin;
 
2762
Plugin.AutoCompleteList = ACListPlugin;
 
2763
 
 
2764
 
 
2765
}, '3.3.0' ,{requires:['autocomplete-list', 'node-pluginhost']});
 
2766
 
 
2767
 
 
2768
YUI.add('autocomplete', function(Y){}, '3.3.0' ,{use:['autocomplete-base', 'autocomplete-sources', 'autocomplete-list', 'autocomplete-plugin']});
 
2769