~veebers/sloecode/jquery-changeover

« back to all changes in this revision

Viewing changes to sloecode/public/yui/3.3.0/build/autocomplete/autocomplete-base-debug.js

  • Committer: Christopher Lee
  • Date: 2012-03-03 05:04:34 UTC
  • Revision ID: veebers@gmail.com-20120303050434-smeo0c6n7gz53thy
Removed unused YUI javascript (still using css libraries)

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']});