~bjornt/lazr-js/prefetch-yui-3.5

« back to all changes in this revision

Viewing changes to src-js/lazrjs/yui/autocomplete/autocomplete-base-debug.js

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-01-14 23:32:29 UTC
  • mfrom: (197.1.7 yui-3.3.0)
  • Revision ID: launchpad@pqm.canonical.com-20110114233229-r6i4cazdiiw18o7p
Upgrade to YUI 3.3.0 [r=mars]

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