3
Copyright 2011 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('autocomplete-base', function(Y) {
10
* Provides automatic input completion or suggestions for text input fields and
13
* @module autocomplete
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.
23
* @submodule autocomplete-base
28
* Extension that provides core autocomplete logic (but no UI implementation)
29
* for a text input field or textarea.
33
* The <code>AutoCompleteBase</code> class provides events and attributes that
34
* abstract away core autocomplete logic and configuration, but does not provide
35
* a widget implementation or suggestion UI. For a prepackaged autocomplete
36
* widget, see <code>AutoCompleteList</code>.
40
* This extension cannot be instantiated directly, since it doesn't provide an
41
* actual implementation. It's intended to be mixed into a
42
* <code>Y.Base</code>-based class or widget.
46
* <code>Y.Widget</code>-based example:
50
* YUI().use('autocomplete-base', 'widget', function (Y) {
51
* var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
52
* // Custom prototype methods and properties.
54
* // Custom static methods and properties.
57
* // Custom implementation code.
62
* <code>Y.Base</code>-based example:
66
* YUI().use('autocomplete-base', function (Y) {
67
* var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
68
* initializer: function () {
69
* this._bindUIACBase();
70
* this._syncUIACBase();
71
* },
73
* // Custom prototype methods and properties.
75
* // Custom static methods and properties.
78
* // Custom implementation code.
82
* @class AutoCompleteBase
85
var Escape = Y.Escape,
90
isFunction = Lang.isFunction,
91
isString = Lang.isString,
94
INVALID_VALUE = Y.Attribute.INVALID_VALUE,
96
_FUNCTION_VALIDATOR = '_functionValidator',
97
_SOURCE_SUCCESS = '_sourceSuccess',
99
ALLOW_BROWSER_AC = 'allowBrowserAutocomplete',
100
INPUT_NODE = 'inputNode',
102
QUERY_DELIMITER = 'queryDelimiter',
103
REQUEST_TEMPLATE = 'requestTemplate',
105
RESULT_LIST_LOCATOR = 'resultListLocator',
107
VALUE_CHANGE = 'valueChange',
111
EVT_RESULTS = RESULTS;
113
function AutoCompleteBase() {
115
Y.before(this._bindUIACBase, this, 'bindUI');
116
Y.before(this._destructorACBase, this, 'destructor');
117
Y.before(this._syncUIACBase, this, 'syncUI');
119
// -- Public Events --------------------------------------------------------
122
* Fires after the query has been completely cleared or no longer meets the
123
* minimum query length requirement.
126
* @param {EventFacade} e Event facade with the following additional
130
* <dt>prevVal (String)</dt>
132
* Value of the query before it was cleared.
136
* @preventable _defClearFn
138
this.publish(EVT_CLEAR, {
139
defaultFn: this._defClearFn
143
* Fires when the contents of the input field have changed and the input
144
* value meets the criteria necessary to generate an autocomplete query.
147
* @param {EventFacade} e Event facade with the following additional
151
* <dt>inputValue (String)</dt>
153
* Full contents of the text input field or textarea that generated
157
* <dt>query (String)</dt>
159
* Autocomplete query. This is the string that will be used to
160
* request completion results. It may or may not be the same as
161
* <code>inputValue</code>.
165
* @preventable _defQueryFn
167
this.publish(EVT_QUERY, {
168
defaultFn: this._defQueryFn
172
* Fires after query results are received from the <code>source</code>. If
173
* no source has been set, this event will not fire.
176
* @param {EventFacade} e Event facade with the following additional
180
* <dt>data (Array|Object)</dt>
182
* Raw, unfiltered result data (if available).
185
* <dt>query (String)</dt>
187
* Query that generated these results.
190
* <dt>results (Array)</dt>
192
* Array of filtered, formatted, and highlighted results. Each item in
193
* the array is an object with the following properties:
196
* <dt>display (Node|HTMLElement|String)</dt>
198
* Formatted result HTML suitable for display to the user. If no
199
* custom formatter is set, this will be an HTML-escaped version of
200
* the string in the <code>text</code> property.
203
* <dt>highlighted (String)</dt>
205
* Highlighted (but not formatted) result text. This property will
206
* only be set if a highlighter is in use.
209
* <dt>raw (mixed)</dt>
211
* Raw, unformatted result in whatever form it was provided by the
212
* <code>source</code>.
215
* <dt>text (String)</dt>
217
* Plain text version of the result, suitable for being inserted
218
* into the value of a text input field or textarea when the result
219
* is selected by a user. This value is not HTML-escaped and should
220
* not be inserted into the page using innerHTML.
226
* @preventable _defResultsFn
228
this.publish(EVT_RESULTS, {
229
defaultFn: this._defResultsFn
233
// -- Public Static Properties -------------------------------------------------
234
AutoCompleteBase.ATTRS = {
236
* Whether or not to enable the browser's built-in autocomplete
237
* functionality for input fields.
239
* @attribute allowBrowserAutocomplete
243
allowBrowserAutocomplete: {
248
* When a <code>queryDelimiter</code> is set, trailing delimiters will
249
* automatically be stripped from the input value by default when the
250
* input node loses focus. Set this to <code>true</code> to allow trailing
253
* @attribute allowTrailingDelimiter
257
allowTrailingDelimiter: {
262
* Node to monitor for changes, which will generate <code>query</code>
263
* events when appropriate. May be either an input field or a textarea.
265
* @attribute inputNode
266
* @type Node|HTMLElement|String
271
writeOnce: 'initOnly'
275
* Maximum number of results to return. A value of <code>0</code> or less
276
* will allow an unlimited number of results.
278
* @attribute maxResults
287
* Minimum number of characters that must be entered before a
288
* <code>query</code> event will be fired. A value of <code>0</code>
289
* allows empty queries; a negative value will effectively disable all
290
* <code>query</code> events.
292
* @attribute minQueryLength
302
* Current query, or <code>null</code> if there is no current query.
306
* The query might not be the same as the current value of the input
307
* node, both for timing reasons (due to <code>queryDelay</code>) and
308
* because when one or more <code>queryDelimiter</code> separators are
309
* in use, only the last portion of the delimited input string will be
310
* used as the query value.
325
* Number of milliseconds to delay after input before triggering a
326
* <code>query</code> event. If new input occurs before this delay is
327
* over, the previous input event will be ignored and a new delay will
332
* This can be useful both to throttle queries to a remote data source
333
* and to avoid distracting the user by showing them less relevant
334
* results before they've paused their typing.
337
* @attribute queryDelay
346
* Query delimiter string. When a delimiter is configured, the input value
347
* will be split on the delimiter, and only the last portion will be used in
348
* autocomplete queries and updated when the <code>query</code> attribute is
351
* @attribute queryDelimiter
361
* Source request template. This can be a function that accepts a query as a
362
* parameter and returns a request string, or it can be a string containing
363
* the placeholder "{query}", which will be replaced with the actual
364
* URI-encoded query. In either case, the resulting string will be appended
365
* to the request URL when the <code>source</code> attribute is set to a
366
* remote DataSource, JSONP URL, or XHR URL (it will not be appended to YQL
371
* While <code>requestTemplate</code> may be set to either a function or
372
* a string, it will always be returned as a function that accepts a
373
* query argument and returns a string.
376
* @attribute requestTemplate
377
* @type Function|String|null
381
setter: '_setRequestTemplate',
387
* Array of local result filter functions. If provided, each filter
388
* will be called with two arguments when results are received: the query
389
* and an array of result objects. See the documentation for the
390
* <code>results</code> event for a list of the properties available on each
395
* Each filter is expected to return a filtered or modified version of the
396
* results array, which will then be passed on to subsequent filters, then
397
* the <code>resultHighlighter</code> function (if set), then the
398
* <code>resultFormatter</code> function (if set), and finally to
399
* subscribers to the <code>results</code> event.
403
* If no <code>source</code> is set, result filters will not be called.
407
* Prepackaged result filters provided by the autocomplete-filters and
408
* autocomplete-filters-accentfold modules can be used by specifying the
409
* filter name as a string, such as <code>'phraseMatch'</code> (assuming
410
* the necessary filters module is loaded).
413
* @attribute resultFilters
418
setter: '_setResultFilters',
424
* Function which will be used to format results. If provided, this function
425
* will be called with two arguments after results have been received and
426
* filtered: the query and an array of result objects. The formatter is
427
* expected to return an array of HTML strings or Node instances containing
428
* the desired HTML for each result.
432
* See the documentation for the <code>results</code> event for a list of
433
* the properties available on each result object.
437
* If no <code>source</code> is set, the formatter will not be called.
440
* @attribute resultFormatter
441
* @type Function|null
444
validator: _FUNCTION_VALIDATOR
449
* Function which will be used to highlight results. If provided, this
450
* function will be called with two arguments after results have been
451
* received and filtered: the query and an array of filtered result objects.
452
* The highlighter is expected to return an array of highlighted result
453
* text in the form of HTML strings.
457
* See the documentation for the <code>results</code> event for a list of
458
* the properties available on each result object.
462
* If no <code>source</code> is set, the highlighter will not be called.
465
* @attribute resultHighlighter
466
* @type Function|null
469
setter: '_setResultHighlighter'
474
* Locator that should be used to extract an array of results from a
475
* non-array response.
479
* By default, no locator is applied, and all responses are assumed to be
480
* arrays by default. If all responses are already arrays, you don't need to
485
* The locator may be either a function (which will receive the raw response
486
* as an argument and must return an array) or a string representing an
487
* object path, such as "foo.bar.baz" (which would return the value of
488
* <code>result.foo.bar.baz</code> if the response is an object).
492
* While <code>resultListLocator</code> may be set to either a function or a
493
* string, it will always be returned as a function that accepts a response
494
* argument and returns an array.
497
* @attribute resultListLocator
498
* @type Function|String|null
501
setter: '_setLocator'
505
* Current results, or an empty array if there are no results.
519
* Locator that should be used to extract a plain text string from a
520
* non-string result item. The resulting text value will typically be the
521
* value that ends up being inserted into an input field or textarea when
522
* the user of an autocomplete implementation selects a result.
526
* By default, no locator is applied, and all results are assumed to be
527
* plain text strings. If all results are already plain text strings, you
528
* don't need to define a locator.
532
* The locator may be either a function (which will receive the raw result
533
* as an argument and must return a string) or a string representing an
534
* object path, such as "foo.bar.baz" (which would return the value of
535
* <code>result.foo.bar.baz</code> if the result is an object).
539
* While <code>resultTextLocator</code> may be set to either a function or a
540
* string, it will always be returned as a function that accepts a result
541
* argument and returns a string.
544
* @attribute resultTextLocator
545
* @type Function|String|null
548
setter: '_setLocator'
553
* Source for autocomplete results. The following source types are
561
* <i>Example:</i> <code>['first result', 'second result', 'etc']</code>
565
* The full array will be provided to any configured filters for each
566
* query. This is an easy way to create a fully client-side autocomplete
571
* <dt>DataSource</dt>
574
* A <code>DataSource</code> instance or other object that provides a
575
* DataSource-like <code>sendRequest</code> method. See the
576
* <code>DataSource</code> documentation for details.
583
* <i>Example (synchronous):</i> <code>function (query) { return ['foo', 'bar']; }</code><br>
584
<i>Example (async):</i> <code>function (query, callback) { callback(['foo', 'bar']); }</code>
588
* A function source will be called with the current query and a
589
* callback function as parameters, and should either return an array of
590
* results (for synchronous operation) or return nothing and pass an
591
* array of results to the provided callback (for asynchronous
599
* <i>Example:</i> <code>{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}</code>
603
* An object will be treated as a query hashmap. If a property on the
604
* object matches the current query, the value of that property will be
605
* used as the response.
609
* The response is assumed to be an array of results by default. If the
610
* response is not an array, provide a <code>resultListLocator</code> to
611
* process the response and return an array.
617
* If the optional <code>autocomplete-sources</code> module is loaded, then
618
* the following additional source types will be supported as well:
622
* <dt><select> Node</dt>
625
* You may provide a YUI Node instance wrapping a <select>
626
* element, and the options in the list will be used as results. You
627
* will also need to specify a <code>resultTextLocator</code> of 'text'
628
* or 'value', depending on what you want to use as the text of the
633
* Each result will be an object with the following properties:
637
* <dt>html (String)</dt>
639
* <p>HTML content of the <option> element.</p>
642
* <dt>index (Number)</dt>
644
* <p>Index of the <option> element in the list.</p>
647
* <dt>node (Y.Node)</dt>
649
* <p>Node instance referring to the original <option> element.</p>
652
* <dt>selected (Boolean)</dt>
654
* <p>Whether or not this item is currently selected in the
655
* <select> list.</p>
658
* <dt>text (String)</dt>
660
* <p>Text content of the <option> element.</p>
663
* <dt>value (String)</dt>
665
* <p>Value of the <option> element.</p>
670
* <dt>String (JSONP URL)</dt>
673
* <i>Example:</i> <code>'http://example.com/search?q={query}&callback={callback}'</code>
677
* If a URL with a <code>{callback}</code> placeholder is provided, it
678
* will be used to make a JSONP request. The <code>{query}</code>
679
* placeholder will be replaced with the current query, and the
680
* <code>{callback}</code> placeholder will be replaced with an
681
* internally-generated JSONP callback name. Both placeholders must
682
* appear in the URL, or the request will fail. An optional
683
* <code>{maxResults}</code> placeholder may also be provided, and will
684
* be replaced with the value of the maxResults attribute (or 1000 if
685
* the maxResults attribute is 0 or less).
689
* The response is assumed to be an array of results by default. If the
690
* response is not an array, provide a <code>resultListLocator</code> to
691
* process the response and return an array.
695
* <strong>The <code>jsonp</code> module must be loaded in order for
696
* JSONP URL sources to work.</strong> If the <code>jsonp</code> module
697
* is not already loaded, it will be loaded on demand if possible.
701
* <dt>String (XHR URL)</dt>
704
* <i>Example:</i> <code>'http://example.com/search?q={query}'</code>
708
* If a URL without a <code>{callback}</code> placeholder is provided,
709
* it will be used to make a same-origin XHR request. The
710
* <code>{query}</code> placeholder will be replaced with the current
711
* query. An optional <code>{maxResults}</code> placeholder may also be
712
* provided, and will be replaced with the value of the maxResults
713
* attribute (or 1000 if the maxResults attribute is 0 or less).
717
* The response is assumed to be a JSON array of results by default. If
718
* the response is a JSON object and not an array, provide a
719
* <code>resultListLocator</code> to process the response and return an
720
* array. If the response is in some form other than JSON, you will
721
* need to use a custom DataSource instance as the source.
725
* <strong>The <code>io-base</code> and <code>json-parse</code> modules
726
* must be loaded in order for XHR URL sources to work.</strong> If
727
* these modules are not already loaded, they will be loaded on demand
732
* <dt>String (YQL query)</dt>
735
* <i>Example:</i> <code>'select * from search.suggest where query="{query}"'</code>
739
* If a YQL query is provided, it will be used to make a YQL request.
740
* The <code>{query}</code> placeholder will be replaced with the
741
* current autocomplete query. This placeholder must appear in the YQL
742
* query, or the request will fail. An optional
743
* <code>{maxResults}</code> placeholder may also be provided, and will
744
* be replaced with the value of the maxResults attribute (or 1000 if
745
* the maxResults attribute is 0 or less).
749
* <strong>The <code>yql</code> module must be loaded in order for YQL
750
* sources to work.</strong> If the <code>yql</code> module is not
751
* already loaded, it will be loaded on demand if possible.
757
* As an alternative to providing a source, you could simply listen for
758
* <code>query</code> events and handle them any way you see fit. Providing
759
* a source is optional, but will usually be simpler.
763
* @type Array|DataSource|Function|Node|Object|String|null
771
* May be used to force a specific source type, overriding the automatic
772
* source type detection. It should almost never be necessary to do this,
773
* but as they taught us in the Boy Scouts, one should always be prepared,
774
* so it's here if you need it. Be warned that if you set this attribute and
775
* something breaks, it's your own fault.
779
* Supported <code>sourceType</code> values are: 'array', 'datasource',
780
* 'function', and 'object'.
784
* If the <code>autocomplete-sources</code> module is loaded, the following
785
* additional source types are supported: 'io', 'jsonp', 'select',
789
* @attribute sourceType
797
* If the <code>inputNode</code> specified at instantiation time has a
798
* <code>node-tokeninput</code> plugin attached to it, this attribute will
799
* be a reference to the <code>Y.Plugin.TokenInput</code> instance.
801
* @attribute tokenInput
802
* @type Plugin.TokenInput
810
* Current value of the input node.
817
// Why duplicate this._inputNode.get('value')? Because we need a
818
// reliable way to track the source of value changes. We want to perform
819
// completion when the user changes the value, but not when we change
825
AutoCompleteBase.CSS_PREFIX = 'ac';
826
AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
829
* Mapping of built-in source types to their setter functions. DataSource
830
* instances and DataSource-like objects are handled natively, so are not
833
* @property SOURCE_TYPES
837
AutoCompleteBase.SOURCE_TYPES = {
838
array : '_createArraySource',
839
'function': '_createFunctionSource',
840
object : '_createObjectSource'
843
AutoCompleteBase.prototype = {
844
// -- Public Prototype Methods ---------------------------------------------
848
* Sends a request to the configured source. If no source is configured,
849
* this method won't do anything.
853
* Usually there's no reason to call this method manually; it will be
854
* called automatically when user input causes a <code>query</code> event to
855
* be fired. The only time you'll need to call this method manually is if
856
* you want to force a request to be sent when no user input has occurred.
859
* @method sendRequest
860
* @param {String} query (optional) Query to send. If specified, the
861
* <code>query</code> attribute will be set to this query. If not
862
* specified, the current value of the <code>query</code> attribute will
864
* @param {Function} requestTemplate (optional) Request template function.
865
* If not specified, the current value of the <code>requestTemplate</code>
866
* attribute will be used.
869
sendRequest: function (query, requestTemplate) {
871
source = this.get('source');
873
if (query || query === '') {
874
this._set(QUERY, query);
876
query = this.get(QUERY) || '';
880
if (!requestTemplate) {
881
requestTemplate = this.get(REQUEST_TEMPLATE);
884
request = requestTemplate ?
885
requestTemplate.call(this, query) : query;
893
success: Y.bind(this._onResponse, this, query)
901
// -- Protected Lifecycle Methods ------------------------------------------
904
* Attaches event listeners and behaviors.
906
* @method _bindUIACBase
909
_bindUIACBase: function () {
910
var inputNode = this.get(INPUT_NODE),
911
tokenInput = inputNode && inputNode.tokenInput;
913
// If the inputNode has a node-tokeninput plugin attached, bind to the
914
// plugin's inputNode instead.
916
inputNode = tokenInput.get(INPUT_NODE);
917
this._set('tokenInput', tokenInput);
921
Y.error('No inputNode specified.');
925
this._inputNode = inputNode;
927
this._acBaseEvents = new Y.EventHandle([
928
// This is the valueChange event on the inputNode, provided by the
929
// event-valuechange module, not our own valueChange.
930
inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
931
inputNode.on('blur', this._onInputBlur, this),
933
this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
934
this.after('sourceTypeChange', this._afterSourceTypeChange),
935
this.after(VALUE_CHANGE, this._afterValueChange)
940
* Detaches AutoCompleteBase event listeners.
942
* @method _destructorACBase
945
_destructorACBase: function () {
946
this._acBaseEvents.detach();
950
* Synchronizes the UI state of the <code>inputNode</code>.
952
* @method _syncUIACBase
955
_syncUIACBase: function () {
956
this._syncBrowserAutocomplete();
957
this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
960
// -- Protected Prototype Methods ------------------------------------------
963
* Creates a DataSource-like object that simply returns the specified array
964
* as a response. See the <code>source</code> attribute for more details.
966
* @method _createArraySource
967
* @param {Array} source
968
* @return {Object} DataSource-like object.
971
_createArraySource: function (source) {
976
sendRequest: function (request) {
977
that[_SOURCE_SUCCESS](source.concat(), request);
983
* Creates a DataSource-like object that passes the query to a
984
* custom-defined function, which is expected to call the provided callback
985
* with an array of results. See the <code>source</code> attribute for more
988
* @method _createFunctionSource
989
* @param {Function} source Function that accepts a query and a callback as
990
* parameters, and calls the callback with an array of results.
991
* @return {Object} DataSource-like object.
994
_createFunctionSource: function (source) {
999
sendRequest: function (request) {
1002
function afterResults(results) {
1003
that[_SOURCE_SUCCESS](results || [], request);
1006
// Allow both synchronous and asynchronous functions. If we get
1007
// a truthy return value, assume the function is synchronous.
1008
if ((value = source(request.query, afterResults))) {
1009
afterResults(value);
1016
* Creates a DataSource-like object that looks up queries as properties on
1017
* the specified object, and returns the found value (if any) as a response.
1018
* See the <code>source</code> attribute for more details.
1020
* @method _createObjectSource
1021
* @param {Object} source
1022
* @return {Object} DataSource-like object.
1025
_createObjectSource: function (source) {
1030
sendRequest: function (request) {
1031
var query = request.query;
1033
that[_SOURCE_SUCCESS](
1034
YObject.owns(source, query) ? source[query] : [],
1042
* Returns <code>true</code> if <i>value</i> is either a function or
1043
* <code>null</code>.
1045
* @method _functionValidator
1046
* @param {Function|null} value Value to validate.
1049
_functionValidator: function (value) {
1050
return value === null || isFunction(value);
1054
* Faster and safer alternative to Y.Object.getValue(). Doesn't bother
1055
* casting the path to an array (since we already know it's an array) and
1056
* doesn't throw an error if a value in the middle of the object hierarchy
1057
* is neither <code>undefined</code> nor an object.
1059
* @method _getObjectValue
1060
* @param {Object} obj
1061
* @param {Array} path
1062
* @return {mixed} Located value, or <code>undefined</code> if the value was
1063
* not found at the specified path.
1066
_getObjectValue: function (obj, path) {
1071
for (var i = 0, len = path.length; obj && i < len; i++) {
1079
* Parses result responses, performs filtering and highlighting, and fires
1080
* the <code>results</code> event.
1082
* @method _parseResponse
1083
* @param {String} query Query that generated these results.
1084
* @param {Object} response Response containing results.
1085
* @param {Object} data Raw response data.
1088
_parseResponse: function (query, response, data) {
1095
listLocator = this.get(RESULT_LIST_LOCATOR),
1097
unfiltered = response && response.results,
1111
if (unfiltered && listLocator) {
1112
unfiltered = listLocator.call(this, unfiltered);
1115
if (unfiltered && unfiltered.length) {
1116
filters = this.get('resultFilters');
1117
textLocator = this.get('resultTextLocator');
1119
// Create a lightweight result object for each result to make them
1120
// easier to work with. The various properties on the object
1121
// represent different formats of the result, and will be populated
1123
for (i = 0, len = unfiltered.length; i < len; ++i) {
1124
result = unfiltered[i];
1126
text = textLocator ?
1127
textLocator.call(this, result) :
1131
display: Escape.html(text),
1137
// Run the results through all configured result filters. Each
1138
// filter returns an array of (potentially fewer) result objects,
1139
// which is then passed to the next filter, and so on.
1140
for (i = 0, len = filters.length; i < len; ++i) {
1141
results = filters[i].call(this, query, results.concat());
1147
if (!results.length) {
1152
if (results.length) {
1153
formatter = this.get('resultFormatter');
1154
highlighter = this.get('resultHighlighter');
1155
maxResults = this.get('maxResults');
1157
// If maxResults is set and greater than 0, limit the number of
1159
if (maxResults && maxResults > 0 &&
1160
results.length > maxResults) {
1161
results.length = maxResults;
1164
// Run the results through the configured highlighter (if any).
1165
// The highlighter returns an array of highlighted strings (not
1166
// an array of result objects), and these strings are then added
1167
// to each result object.
1169
highlighted = highlighter.call(this, query,
1176
for (i = 0, len = highlighted.length; i < len; ++i) {
1177
result = results[i];
1178
result.highlighted = highlighted[i];
1179
result.display = result.highlighted;
1183
// Run the results through the configured formatter (if any) to
1184
// produce the final formatted results. The formatter returns an
1185
// array of strings or Node instances (not an array of result
1186
// objects), and these strings/Nodes are then added to each
1189
formatted = formatter.call(this, query, results.concat());
1195
for (i = 0, len = formatted.length; i < len; ++i) {
1196
results[i].display = formatted[i];
1202
facade.results = results;
1203
this.fire(EVT_RESULTS, facade);
1208
* Returns the query portion of the specified input value, or
1209
* <code>null</code> if there is no suitable query within the input value.
1213
* If a query delimiter is defined, the query will be the last delimited
1214
* part of of the string.
1217
* @method _parseValue
1218
* @param {String} value Input value from which to extract the query.
1219
* @return {String|null} query
1222
_parseValue: function (value) {
1223
var delim = this.get(QUERY_DELIMITER);
1226
value = value.split(delim);
1227
value = value[value.length - 1];
1230
return Lang.trimLeft(value);
1234
* Setter for locator attributes.
1236
* @method _setLocator
1237
* @param {Function|String|null} locator
1238
* @return {Function|null}
1241
_setLocator: function (locator) {
1242
if (this[_FUNCTION_VALIDATOR](locator)) {
1248
locator = locator.toString().split('.');
1250
return function (result) {
1251
return result && that._getObjectValue(result, locator);
1256
* Setter for the <code>requestTemplate</code> attribute.
1258
* @method _setRequestTemplate
1259
* @param {Function|String|null} template
1260
* @return {Function|null}
1263
_setRequestTemplate: function (template) {
1264
if (this[_FUNCTION_VALIDATOR](template)) {
1268
template = template.toString();
1270
return function (query) {
1271
return Lang.sub(template, {query: encodeURIComponent(query)});
1276
* Setter for the <code>resultFilters</code> attribute.
1278
* @method _setResultFilters
1279
* @param {Array|Function|String|null} filters <code>null</code>, a filter
1280
* function, an array of filter functions, or a string or array of strings
1281
* representing the names of methods on
1282
* <code>Y.AutoCompleteFilters</code>.
1283
* @return {Array} Array of filter functions (empty if <i>filters</i> is
1284
* <code>null</code>).
1287
_setResultFilters: function (filters) {
1288
var acFilters, getFilterFunction;
1290
if (filters === null) {
1294
acFilters = Y.AutoCompleteFilters;
1296
getFilterFunction = function (filter) {
1297
if (isFunction(filter)) {
1301
if (isString(filter) && acFilters &&
1302
isFunction(acFilters[filter])) {
1303
return acFilters[filter];
1309
if (Lang.isArray(filters)) {
1310
filters = YArray.map(filters, getFilterFunction);
1311
return YArray.every(filters, function (f) { return !!f; }) ?
1312
filters : INVALID_VALUE;
1314
filters = getFilterFunction(filters);
1315
return filters ? [filters] : INVALID_VALUE;
1320
* Setter for the <code>resultHighlighter</code> attribute.
1322
* @method _setResultHighlighter
1323
* @param {Function|String|null} highlighter <code>null</code>, a
1324
* highlighter function, or a string representing the name of a method on
1325
* <code>Y.AutoCompleteHighlighters</code>.
1326
* @return {Function|null}
1329
_setResultHighlighter: function (highlighter) {
1332
if (this._functionValidator(highlighter)) {
1336
acHighlighters = Y.AutoCompleteHighlighters;
1338
if (isString(highlighter) && acHighlighters &&
1339
isFunction(acHighlighters[highlighter])) {
1340
return acHighlighters[highlighter];
1343
return INVALID_VALUE;
1347
* Setter for the <code>source</code> attribute. Returns a DataSource or
1348
* a DataSource-like object depending on the type of <i>source</i> and/or
1349
* the value of the <code>sourceType</code> attribute.
1351
* @method _setSource
1352
* @param {mixed} source AutoComplete source. See the <code>source</code>
1353
* attribute for details.
1354
* @return {DataSource|Object}
1357
_setSource: function (source) {
1358
var sourceType = this.get('sourceType') || Lang.type(source),
1361
if ((source && isFunction(source.sendRequest))
1363
|| sourceType === 'datasource') {
1365
// Quacks like a DataSource instance (or null). Make it so!
1366
this._rawSource = source;
1370
// See if there's a registered setter for this source type.
1371
if ((sourceSetter = AutoCompleteBase.SOURCE_TYPES[sourceType])) {
1372
this._rawSource = source;
1373
return Lang.isString(sourceSetter) ?
1374
this[sourceSetter](source) : sourceSetter(source);
1377
Y.error("Unsupported source type '" + sourceType + "'. Maybe autocomplete-sources isn't loaded?");
1378
return INVALID_VALUE;
1382
* Shared success callback for non-DataSource sources.
1384
* @method _sourceSuccess
1385
* @param {mixed} data Response data.
1386
* @param {Object} request Request object.
1389
_sourceSuccess: function (data, request) {
1390
request.callback.success({
1392
response: {results: data}
1397
* Synchronizes the UI state of the <code>allowBrowserAutocomplete</code>
1400
* @method _syncBrowserAutocomplete
1403
_syncBrowserAutocomplete: function () {
1404
var inputNode = this.get(INPUT_NODE);
1406
if (inputNode.get('nodeName').toLowerCase() === 'input') {
1407
inputNode.setAttribute('autocomplete',
1408
this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
1414
* Updates the query portion of the <code>value</code> attribute.
1418
* If a query delimiter is defined, the last delimited portion of the input
1419
* value will be replaced with the specified <i>value</i>.
1422
* @method _updateValue
1423
* @param {String} newVal New value.
1426
_updateValue: function (newVal) {
1427
var delim = this.get(QUERY_DELIMITER),
1432
newVal = Lang.trimLeft(newVal);
1435
insertDelim = trim(delim); // so we don't double up on spaces
1436
prevVal = YArray.map(trim(this.get(VALUE)).split(delim), trim);
1437
len = prevVal.length;
1440
prevVal[len - 1] = newVal;
1441
newVal = prevVal.join(insertDelim + ' ');
1444
newVal = newVal + insertDelim + ' ';
1447
this.set(VALUE, newVal);
1450
// -- Protected Event Handlers ---------------------------------------------
1453
* Updates the current <code>source</code> based on the new
1454
* <code>sourceType</code> to ensure that the two attributes don't get out
1455
* of sync when they're changed separately.
1457
* @method _afterSourceTypeChange
1458
* @param {EventFacade} e
1461
_afterSourceTypeChange: function (e) {
1462
if (this._rawSource) {
1463
this.set('source', this._rawSource);
1468
* Handles change events for the <code>value</code> attribute.
1470
* @method _afterValueChange
1471
* @param {EventFacade} e
1474
_afterValueChange: function (e) {
1482
// Don't query on value changes that didn't come from the user.
1483
if (e.src !== AutoCompleteBase.UI_SRC) {
1484
this._inputNode.set(VALUE, newVal);
1489
minQueryLength = this.get('minQueryLength');
1490
query = this._parseValue(newVal) || '';
1492
if (minQueryLength >= 0 && query.length >= minQueryLength) {
1493
delay = this.get('queryDelay');
1496
fire = function () {
1497
that.fire(EVT_QUERY, {
1504
clearTimeout(this._delay);
1505
this._delay = setTimeout(fire, delay);
1510
clearTimeout(this._delay);
1512
this.fire(EVT_CLEAR, {
1513
prevVal: e.prevVal ? this._parseValue(e.prevVal) : null
1519
* Handles <code>blur</code> events on the input node.
1521
* @method _onInputBlur
1522
* @param {EventFacade} e
1525
_onInputBlur: function (e) {
1526
var delim = this.get(QUERY_DELIMITER),
1531
// If a query delimiter is set and the input's value contains one or
1532
// more trailing delimiters, strip them.
1533
if (delim && !this.get('allowTrailingDelimiter')) {
1534
delim = Lang.trimRight(delim);
1535
value = newVal = this._inputNode.get(VALUE);
1538
while ((newVal = Lang.trimRight(newVal)) &&
1539
(delimPos = newVal.length - delim.length) &&
1540
newVal.lastIndexOf(delim) === delimPos) {
1542
newVal = newVal.substring(0, delimPos);
1545
// Delimiter is one or more space characters, so just trim the
1547
newVal = Lang.trimRight(newVal);
1550
if (newVal !== value) {
1551
this.set(VALUE, newVal);
1557
* Handles <code>valueChange</code> events on the input node and fires a
1558
* <code>query</code> event when the input value meets the configured
1561
* @method _onInputValueChange
1562
* @param {EventFacade} e
1565
_onInputValueChange: function (e) {
1566
var newVal = e.newVal;
1568
// Don't query if the internal value is the same as the new value
1569
// reported by valueChange.
1570
if (newVal === this.get(VALUE)) {
1574
this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
1578
* Handles source responses and fires the <code>results</code> event.
1580
* @method _onResponse
1581
* @param {EventFacade} e
1584
_onResponse: function (query, e) {
1585
// Ignore stale responses that aren't for the current query.
1586
if (query === (this.get(QUERY) || '')) {
1587
this._parseResponse(query || '', e.response, e.data);
1591
// -- Protected Default Event Handlers -------------------------------------
1594
* Default <code>clear</code> event handler. Sets the <code>results</code>
1595
* property to an empty array and <code>query</code> to null.
1597
* @method _defClearFn
1600
_defClearFn: function () {
1601
this._set(QUERY, null);
1602
this._set(RESULTS, []);
1606
* Default <code>query</code> event handler. Sets the <code>query</code>
1607
* property and sends a request to the source if one is configured.
1609
* @method _defQueryFn
1610
* @param {EventFacade} e
1613
_defQueryFn: function (e) {
1614
var query = e.query;
1616
this.sendRequest(query); // sendRequest will set the 'query' attribute
1620
* Default <code>results</code> event handler. Sets the <code>results</code>
1621
* property to the latest results.
1623
* @method _defResultsFn
1624
* @param {EventFacade} e
1627
_defResultsFn: function (e) {
1628
this._set(RESULTS, e[RESULTS]);
1632
Y.AutoCompleteBase = AutoCompleteBase;
1635
}, '3.4.1' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']});