2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('autocomplete-base', function (Y, NAME) {
11 Provides automatic input completion or suggestions for text input fields and
20 `Y.Base` extension that provides core autocomplete logic (but no UI
21 implementation) for a text input field or textarea. Must be mixed into a
22 `Y.Base`-derived class to be useful.
25 @submodule autocomplete-base
29 Extension that provides core autocomplete logic (but no UI implementation) for a
30 text input field or textarea.
32 The `AutoCompleteBase` class provides events and attributes that abstract away
33 core autocomplete logic and configuration, but does not provide a widget
34 implementation or suggestion UI. For a prepackaged autocomplete widget, see
37 This extension cannot be instantiated directly, since it doesn't provide an
38 actual implementation. It's intended to be mixed into a `Y.Base`-based class or
41 `Y.Widget`-based example:
43 YUI().use('autocomplete-base', 'widget', function (Y) {
44 var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
45 // Custom prototype methods and properties.
47 // Custom static methods and properties.
50 // Custom implementation code.
53 `Y.Base`-based example:
55 YUI().use('autocomplete-base', function (Y) {
56 var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
57 initializer: function () {
62 // Custom prototype methods and properties.
64 // Custom static methods and properties.
67 // Custom implementation code.
70 @class AutoCompleteBase
73 var Escape = Y.Escape,
78 isFunction = Lang.isFunction,
79 isString = Lang.isString,
82 INVALID_VALUE = Y.Attribute.INVALID_VALUE,
84 _FUNCTION_VALIDATOR = '_functionValidator',
85 _SOURCE_SUCCESS = '_sourceSuccess',
87 ALLOW_BROWSER_AC = 'allowBrowserAutocomplete',
88 INPUT_NODE = 'inputNode',
90 QUERY_DELIMITER = 'queryDelimiter',
91 REQUEST_TEMPLATE = 'requestTemplate',
93 RESULT_LIST_LOCATOR = 'resultListLocator',
95 VALUE_CHANGE = 'valueChange',
99 EVT_RESULTS = RESULTS;
101 function AutoCompleteBase() {}
103 AutoCompleteBase.prototype = {
104 // -- Lifecycle Methods ----------------------------------------------------
105 initializer: function () {
107 Y.before(this._bindUIACBase, this, 'bindUI');
108 Y.before(this._syncUIACBase, this, 'syncUI');
110 // -- Public Events ----------------------------------------------------
113 Fires after the query has been completely cleared or no longer meets the
114 minimum query length requirement.
117 @param {String} prevVal Value of the query before it was cleared.
118 @param {String} src Source of the event.
119 @preventable _defClearFn
121 this.publish(EVT_CLEAR, {
122 defaultFn: this._defClearFn
126 Fires when the contents of the input field have changed and the input
127 value meets the criteria necessary to generate an autocomplete query.
130 @param {String} inputValue Full contents of the text input field or
131 textarea that generated the query.
132 @param {String} query AutoComplete query. This is the string that will
133 be used to request completion results. It may or may not be the same
135 @param {String} src Source of the event.
136 @preventable _defQueryFn
138 this.publish(EVT_QUERY, {
139 defaultFn: this._defQueryFn
143 Fires after query results are received from the source. If no source has
144 been set, this event will not fire.
147 @param {Array|Object} data Raw, unfiltered result data (if available).
148 @param {String} query Query that generated these results.
149 @param {Object[]} results Array of filtered, formatted, and highlighted
150 results. Each item in the array is an object with the following
153 @param {Node|HTMLElement|String} results.display Formatted result
154 HTML suitable for display to the user. If no custom formatter is
155 set, this will be an HTML-escaped version of the string in the
157 @param {String} [results.highlighted] Highlighted (but not
158 formatted) result text. This property will only be set if a
159 highlighter is in use.
160 @param {Any} results.raw Raw, unformatted result in whatever form it
161 was provided by the source.
162 @param {String} results.text Plain text version of the result,
163 suitable for being inserted into the value of a text input field
164 or textarea when the result is selected by a user. This value is
165 not HTML-escaped and should not be inserted into the page using
166 `innerHTML` or `Node#setContent()`.
168 @preventable _defResultsFn
170 this.publish(EVT_RESULTS, {
171 defaultFn: this._defResultsFn
175 destructor: function () {
176 this._acBaseEvents && this._acBaseEvents.detach();
178 delete this._acBaseEvents;
180 delete this._inputNode;
181 delete this._rawSource;
184 // -- Public Prototype Methods ---------------------------------------------
187 Clears the result cache.
193 clearCache: function () {
194 this._cache && (this._cache = {});
199 Sends a request to the configured source. If no source is configured, this
200 method won't do anything.
202 Usually there's no reason to call this method manually; it will be called
203 automatically when user input causes a `query` event to be fired. The only
204 time you'll need to call this method manually is if you want to force a
205 request to be sent when no user input has occurred.
208 @param {String} [query] Query to send. If specified, the `query` attribute
209 will be set to this query. If not specified, the current value of the
210 `query` attribute will be used.
211 @param {Function} [requestTemplate] Request template function. If not
212 specified, the current value of the `requestTemplate` attribute will be
216 sendRequest: function (query, requestTemplate) {
218 source = this.get('source');
220 if (query || query === '') {
221 this._set(QUERY, query);
223 query = this.get(QUERY) || '';
227 if (!requestTemplate) {
228 requestTemplate = this.get(REQUEST_TEMPLATE);
231 request = requestTemplate ?
232 requestTemplate.call(this, query) : query;
234 Y.log('sendRequest: ' + request, 'info', 'autocomplete-base');
241 success: Y.bind(this._onResponse, this, query)
249 // -- Protected Lifecycle Methods ------------------------------------------
252 Attaches event listeners and behaviors.
254 @method _bindUIACBase
257 _bindUIACBase: function () {
258 var inputNode = this.get(INPUT_NODE),
259 tokenInput = inputNode && inputNode.tokenInput;
261 // If the inputNode has a node-tokeninput plugin attached, bind to the
262 // plugin's inputNode instead.
264 inputNode = tokenInput.get(INPUT_NODE);
265 this._set('tokenInput', tokenInput);
269 Y.error('No inputNode specified.');
273 this._inputNode = inputNode;
275 this._acBaseEvents = new Y.EventHandle([
276 // This is the valueChange event on the inputNode, provided by the
277 // event-valuechange module, not our own valueChange.
278 inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
279 inputNode.on('blur', this._onInputBlur, this),
281 this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
282 this.after('sourceTypeChange', this._afterSourceTypeChange),
283 this.after(VALUE_CHANGE, this._afterValueChange)
288 Synchronizes the UI state of the `inputNode`.
290 @method _syncUIACBase
293 _syncUIACBase: function () {
294 this._syncBrowserAutocomplete();
295 this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
298 // -- Protected Prototype Methods ------------------------------------------
301 Creates a DataSource-like object that simply returns the specified array as
302 a response. See the `source` attribute for more details.
304 @method _createArraySource
305 @param {Array} source
306 @return {Object} DataSource-like object.
309 _createArraySource: function (source) {
314 sendRequest: function (request) {
315 that[_SOURCE_SUCCESS](source.concat(), request);
321 Creates a DataSource-like object that passes the query to a custom-defined
322 function, which is expected to call the provided callback with an array of
323 results. See the `source` attribute for more details.
325 @method _createFunctionSource
326 @param {Function} source Function that accepts a query and a callback as
327 parameters, and calls the callback with an array of results.
328 @return {Object} DataSource-like object.
331 _createFunctionSource: function (source) {
336 sendRequest: function (request) {
339 function afterResults(results) {
340 that[_SOURCE_SUCCESS](results || [], request);
343 // Allow both synchronous and asynchronous functions. If we get
344 // a truthy return value, assume the function is synchronous.
345 if ((value = source(request.query, afterResults))) {
353 Creates a DataSource-like object that looks up queries as properties on the
354 specified object, and returns the found value (if any) as a response. See
355 the `source` attribute for more details.
357 @method _createObjectSource
358 @param {Object} source
359 @return {Object} DataSource-like object.
362 _createObjectSource: function (source) {
367 sendRequest: function (request) {
368 var query = request.query;
370 that[_SOURCE_SUCCESS](
371 YObject.owns(source, query) ? source[query] : [],
379 Returns `true` if _value_ is either a function or `null`.
381 @method _functionValidator
382 @param {Function|null} value Value to validate.
385 _functionValidator: function (value) {
386 return value === null || isFunction(value);
390 Faster and safer alternative to `Y.Object.getValue()`. Doesn't bother
391 casting the path to an array (since we already know it's an array) and
392 doesn't throw an error if a value in the middle of the object hierarchy is
393 neither `undefined` nor an object.
395 @method _getObjectValue
398 @return {Any} Located value, or `undefined` if the value was
399 not found at the specified path.
402 _getObjectValue: function (obj, path) {
407 for (var i = 0, len = path.length; obj && i < len; i++) {
415 Parses result responses, performs filtering and highlighting, and fires the
418 @method _parseResponse
419 @param {String} query Query that generated these results.
420 @param {Object} response Response containing results.
421 @param {Object} data Raw response data.
424 _parseResponse: function (query, response, data) {
431 listLocator = this.get(RESULT_LIST_LOCATOR),
433 unfiltered = response && response.results,
447 if (unfiltered && listLocator) {
448 unfiltered = listLocator.call(this, unfiltered);
451 if (unfiltered && unfiltered.length) {
452 filters = this.get('resultFilters');
453 textLocator = this.get('resultTextLocator');
455 // Create a lightweight result object for each result to make them
456 // easier to work with. The various properties on the object
457 // represent different formats of the result, and will be populated
459 for (i = 0, len = unfiltered.length; i < len; ++i) {
460 result = unfiltered[i];
463 textLocator.call(this, result) :
467 display: Escape.html(text),
473 // Run the results through all configured result filters. Each
474 // filter returns an array of (potentially fewer) result objects,
475 // which is then passed to the next filter, and so on.
476 for (i = 0, len = filters.length; i < len; ++i) {
477 results = filters[i].call(this, query, results.concat());
480 Y.log("Filter didn't return anything.", 'warn', 'autocomplete-base');
484 if (!results.length) {
489 if (results.length) {
490 formatter = this.get('resultFormatter');
491 highlighter = this.get('resultHighlighter');
492 maxResults = this.get('maxResults');
494 // If maxResults is set and greater than 0, limit the number of
496 if (maxResults && maxResults > 0 &&
497 results.length > maxResults) {
498 results.length = maxResults;
501 // Run the results through the configured highlighter (if any).
502 // The highlighter returns an array of highlighted strings (not
503 // an array of result objects), and these strings are then added
504 // to each result object.
506 highlighted = highlighter.call(this, query,
510 Y.log("Highlighter didn't return anything.", 'warn', 'autocomplete-base');
514 for (i = 0, len = highlighted.length; i < len; ++i) {
516 result.highlighted = highlighted[i];
517 result.display = result.highlighted;
521 // Run the results through the configured formatter (if any) to
522 // produce the final formatted results. The formatter returns an
523 // array of strings or Node instances (not an array of result
524 // objects), and these strings/Nodes are then added to each
527 formatted = formatter.call(this, query, results.concat());
530 Y.log("Formatter didn't return anything.", 'warn', 'autocomplete-base');
534 for (i = 0, len = formatted.length; i < len; ++i) {
535 results[i].display = formatted[i];
541 facade.results = results;
542 this.fire(EVT_RESULTS, facade);
546 Returns the query portion of the specified input value, or `null` if there
547 is no suitable query within the input value.
549 If a query delimiter is defined, the query will be the last delimited part
553 @param {String} value Input value from which to extract the query.
554 @return {String|null} query
557 _parseValue: function (value) {
558 var delim = this.get(QUERY_DELIMITER);
561 value = value.split(delim);
562 value = value[value.length - 1];
565 return Lang.trimLeft(value);
569 Setter for the `enableCache` attribute.
571 @method _setEnableCache
572 @param {Boolean} value
576 _setEnableCache: function (value) {
577 // When `this._cache` is an object, result sources will store cached
578 // results in it. When it's falsy, they won't. This way result sources
579 // don't need to get the value of the `enableCache` attribute on every
580 // request, which would be sloooow.
581 this._cache = value ? {} : null;
582 Y.log('Cache ' + (value ? 'enabled' : 'disabled'), 'debug', 'autocomplete-base');
586 Setter for locator attributes.
589 @param {Function|String|null} locator
590 @return {Function|null}
593 _setLocator: function (locator) {
594 if (this[_FUNCTION_VALIDATOR](locator)) {
600 locator = locator.toString().split('.');
602 return function (result) {
603 return result && that._getObjectValue(result, locator);
608 Setter for the `requestTemplate` attribute.
610 @method _setRequestTemplate
611 @param {Function|String|null} template
612 @return {Function|null}
615 _setRequestTemplate: function (template) {
616 if (this[_FUNCTION_VALIDATOR](template)) {
620 template = template.toString();
622 return function (query) {
623 return Lang.sub(template, {query: encodeURIComponent(query)});
628 Setter for the `resultFilters` attribute.
630 @method _setResultFilters
631 @param {Array|Function|String|null} filters `null`, a filter
632 function, an array of filter functions, or a string or array of strings
633 representing the names of methods on `Y.AutoCompleteFilters`.
634 @return {Function[]} Array of filter functions (empty if <i>filters</i> is
638 _setResultFilters: function (filters) {
639 var acFilters, getFilterFunction;
641 if (filters === null) {
645 acFilters = Y.AutoCompleteFilters;
647 getFilterFunction = function (filter) {
648 if (isFunction(filter)) {
652 if (isString(filter) && acFilters &&
653 isFunction(acFilters[filter])) {
654 return acFilters[filter];
660 if (Lang.isArray(filters)) {
661 filters = YArray.map(filters, getFilterFunction);
662 return YArray.every(filters, function (f) { return !!f; }) ?
663 filters : INVALID_VALUE;
665 filters = getFilterFunction(filters);
666 return filters ? [filters] : INVALID_VALUE;
671 Setter for the `resultHighlighter` attribute.
673 @method _setResultHighlighter
674 @param {Function|String|null} highlighter `null`, a highlighter function, or
675 a string representing the name of a method on
676 `Y.AutoCompleteHighlighters`.
677 @return {Function|null}
680 _setResultHighlighter: function (highlighter) {
683 if (this[_FUNCTION_VALIDATOR](highlighter)) {
687 acHighlighters = Y.AutoCompleteHighlighters;
689 if (isString(highlighter) && acHighlighters &&
690 isFunction(acHighlighters[highlighter])) {
691 return acHighlighters[highlighter];
694 return INVALID_VALUE;
698 Setter for the `source` attribute. Returns a DataSource or a DataSource-like
699 object depending on the type of _source_ and/or the value of the
700 `sourceType` attribute.
703 @param {Any} source AutoComplete source. See the `source` attribute for
705 @return {DataSource|Object}
708 _setSource: function (source) {
709 var sourceType = this.get('sourceType') || Lang.type(source),
712 if ((source && isFunction(source.sendRequest))
714 || sourceType === 'datasource') {
716 // Quacks like a DataSource instance (or null). Make it so!
717 this._rawSource = source;
721 // See if there's a registered setter for this source type.
722 if ((sourceSetter = AutoCompleteBase.SOURCE_TYPES[sourceType])) {
723 this._rawSource = source;
724 return Lang.isString(sourceSetter) ?
725 this[sourceSetter](source) : sourceSetter(source);
728 Y.error("Unsupported source type '" + sourceType + "'. Maybe autocomplete-sources isn't loaded?");
729 return INVALID_VALUE;
733 Shared success callback for non-DataSource sources.
735 @method _sourceSuccess
736 @param {Any} data Response data.
737 @param {Object} request Request object.
740 _sourceSuccess: function (data, request) {
741 request.callback.success({
743 response: {results: data}
748 Synchronizes the UI state of the `allowBrowserAutocomplete` attribute.
750 @method _syncBrowserAutocomplete
753 _syncBrowserAutocomplete: function () {
754 var inputNode = this.get(INPUT_NODE);
756 if (inputNode.get('nodeName').toLowerCase() === 'input') {
757 inputNode.setAttribute('autocomplete',
758 this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
763 Updates the query portion of the `value` attribute.
765 If a query delimiter is defined, the last delimited portion of the input
766 value will be replaced with the specified _value_.
769 @param {String} newVal New value.
772 _updateValue: function (newVal) {
773 var delim = this.get(QUERY_DELIMITER),
778 newVal = Lang.trimLeft(newVal);
781 insertDelim = trim(delim); // so we don't double up on spaces
782 prevVal = YArray.map(trim(this.get(VALUE)).split(delim), trim);
783 len = prevVal.length;
786 prevVal[len - 1] = newVal;
787 newVal = prevVal.join(insertDelim + ' ');
790 newVal = newVal + insertDelim + ' ';
793 this.set(VALUE, newVal);
796 // -- Protected Event Handlers ---------------------------------------------
799 Updates the current `source` based on the new `sourceType` to ensure that
800 the two attributes don't get out of sync when they're changed separately.
802 @method _afterSourceTypeChange
803 @param {EventFacade} e
806 _afterSourceTypeChange: function (e) {
807 if (this._rawSource) {
808 this.set('source', this._rawSource);
813 Handles change events for the `value` attribute.
815 @method _afterValueChange
816 @param {EventFacade} e
819 _afterValueChange: function (e) {
820 var newVal = e.newVal,
822 uiChange = e.src === AutoCompleteBase.UI_SRC,
823 delay, fire, minQueryLength, query;
825 // Update the UI if the value was changed programmatically.
827 self._inputNode.set(VALUE, newVal);
830 Y.log('valueChange: new: "' + newVal + '"; old: "' + e.prevVal + '"', 'info', 'autocomplete-base');
832 minQueryLength = self.get('minQueryLength');
833 query = self._parseValue(newVal) || '';
835 if (minQueryLength >= 0 && query.length >= minQueryLength) {
836 // Only query on changes that originate from the UI.
838 delay = self.get('queryDelay');
841 self.fire(EVT_QUERY, {
849 clearTimeout(self._delay);
850 self._delay = setTimeout(fire, delay);
855 // For programmatic value changes, just update the query
856 // attribute without sending a query.
857 self._set(QUERY, query);
860 clearTimeout(self._delay);
862 self.fire(EVT_CLEAR, {
863 prevVal: e.prevVal ? self._parseValue(e.prevVal) : null,
870 Handles `blur` events on the input node.
873 @param {EventFacade} e
876 _onInputBlur: function (e) {
877 var delim = this.get(QUERY_DELIMITER),
882 // If a query delimiter is set and the input's value contains one or
883 // more trailing delimiters, strip them.
884 if (delim && !this.get('allowTrailingDelimiter')) {
885 delim = Lang.trimRight(delim);
886 value = newVal = this._inputNode.get(VALUE);
889 while ((newVal = Lang.trimRight(newVal)) &&
890 (delimPos = newVal.length - delim.length) &&
891 newVal.lastIndexOf(delim) === delimPos) {
893 newVal = newVal.substring(0, delimPos);
896 // Delimiter is one or more space characters, so just trim the
898 newVal = Lang.trimRight(newVal);
901 if (newVal !== value) {
902 this.set(VALUE, newVal);
908 Handles `valueChange` events on the input node and fires a `query` event
909 when the input value meets the configured criteria.
911 @method _onInputValueChange
912 @param {EventFacade} e
915 _onInputValueChange: function (e) {
916 var newVal = e.newVal;
918 // Don't query if the internal value is the same as the new value
919 // reported by valueChange.
920 if (newVal !== this.get(VALUE)) {
921 this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
926 Handles source responses and fires the `results` event.
929 @param {EventFacade} e
932 _onResponse: function (query, e) {
933 // Ignore stale responses that aren't for the current query.
934 if (query === (this.get(QUERY) || '')) {
935 this._parseResponse(query || '', e.response, e.data);
939 // -- Protected Default Event Handlers -------------------------------------
942 Default `clear` event handler. Sets the `results` attribute to an empty
943 array and `query` to null.
948 _defClearFn: function () {
949 this._set(QUERY, null);
950 this._set(RESULTS, []);
954 Default `query` event handler. Sets the `query` attribute and sends a
955 request to the source if one is configured.
958 @param {EventFacade} e
961 _defQueryFn: function (e) {
962 Y.log('query: "' + e.query + '"; inputValue: "' + e.inputValue + '"', 'info', 'autocomplete-base');
963 this.sendRequest(e.query); // sendRequest will set the 'query' attribute
967 Default `results` event handler. Sets the `results` attribute to the latest
970 @method _defResultsFn
971 @param {EventFacade} e
974 _defResultsFn: function (e) {
975 Y.log('results: ' + Y.dump(e.results), 'info', 'autocomplete-base');
976 this._set(RESULTS, e[RESULTS]);
980 AutoCompleteBase.ATTRS = {
982 Whether or not to enable the browser's built-in autocomplete functionality
985 @attribute allowBrowserAutocomplete
989 allowBrowserAutocomplete: {
994 When a `queryDelimiter` is set, trailing delimiters will automatically be
995 stripped from the input value by default when the input node loses focus.
996 Set this to `true` to allow trailing delimiters.
998 @attribute allowTrailingDelimiter
1002 allowTrailingDelimiter: {
1007 Whether or not to enable in-memory caching in result sources that support
1010 @attribute enableCache
1016 lazyAdd: false, // we need the setter to run on init
1017 setter: '_setEnableCache',
1022 Node to monitor for changes, which will generate `query` events when
1023 appropriate. May be either an `<input>` or a `<textarea>`.
1025 @attribute inputNode
1026 @type Node|HTMLElement|String
1031 writeOnce: 'initOnly'
1035 Maximum number of results to return. A value of `0` or less will allow an
1036 unlimited number of results.
1038 @attribute maxResults
1047 Minimum number of characters that must be entered before a `query` event
1048 will be fired. A value of `0` allows empty queries; a negative value will
1049 effectively disable all `query` events.
1051 @attribute minQueryLength
1060 Current query, or `null` if there is no current query.
1062 The query might not be the same as the current value of the input node, both
1063 for timing reasons (due to `queryDelay`) and because when one or more
1064 `queryDelimiter` separators are in use, only the last portion of the
1065 delimited input string will be used as the query value.
1078 Number of milliseconds to delay after input before triggering a `query`
1079 event. If new input occurs before this delay is over, the previous input
1080 event will be ignored and a new delay will begin.
1082 This can be useful both to throttle queries to a remote data source and to
1083 avoid distracting the user by showing them less relevant results before
1084 they've paused their typing.
1086 @attribute queryDelay
1095 Query delimiter string. When a delimiter is configured, the input value
1096 will be split on the delimiter, and only the last portion will be used in
1097 autocomplete queries and updated when the `query` attribute is
1100 @attribute queryDelimiter
1109 Source request template. This can be a function that accepts a query as a
1110 parameter and returns a request string, or it can be a string containing the
1111 placeholder "{query}", which will be replaced with the actual URI-encoded
1112 query. In either case, the resulting string will be appended to the request
1113 URL when the `source` attribute is set to a remote DataSource, JSONP URL, or
1114 XHR URL (it will not be appended to YQL URLs).
1116 While `requestTemplate` may be set to either a function or a string, it will
1117 always be returned as a function that accepts a query argument and returns a
1120 @attribute requestTemplate
1121 @type Function|String|null
1125 setter: '_setRequestTemplate',
1130 Array of local result filter functions. If provided, each filter will be
1131 called with two arguments when results are received: the query and an array
1132 of result objects. See the documentation for the `results` event for a list
1133 of the properties available on each result object.
1135 Each filter is expected to return a filtered or modified version of the
1136 results array, which will then be passed on to subsequent filters, then the
1137 `resultHighlighter` function (if set), then the `resultFormatter` function
1138 (if set), and finally to subscribers to the `results` event.
1140 If no `source` is set, result filters will not be called.
1142 Prepackaged result filters provided by the autocomplete-filters and
1143 autocomplete-filters-accentfold modules can be used by specifying the filter
1144 name as a string, such as `'phraseMatch'` (assuming the necessary filters
1147 @attribute resultFilters
1152 setter: '_setResultFilters',
1157 Function which will be used to format results. If provided, this function
1158 will be called with two arguments after results have been received and
1159 filtered: the query and an array of result objects. The formatter is
1160 expected to return an array of HTML strings or Node instances containing the
1161 desired HTML for each result.
1163 See the documentation for the `results` event for a list of the properties
1164 available on each result object.
1166 If no `source` is set, the formatter will not be called.
1168 @attribute resultFormatter
1172 validator: _FUNCTION_VALIDATOR,
1177 Function which will be used to highlight results. If provided, this function
1178 will be called with two arguments after results have been received and
1179 filtered: the query and an array of filtered result objects. The highlighter
1180 is expected to return an array of highlighted result text in the form of
1183 See the documentation for the `results` event for a list of the properties
1184 available on each result object.
1186 If no `source` is set, the highlighter will not be called.
1188 @attribute resultHighlighter
1191 resultHighlighter: {
1192 setter: '_setResultHighlighter',
1197 Locator that should be used to extract an array of results from a non-array
1200 By default, no locator is applied, and all responses are assumed to be
1201 arrays by default. If all responses are already arrays, you don't need to
1204 The locator may be either a function (which will receive the raw response as
1205 an argument and must return an array) or a string representing an object
1206 path, such as "foo.bar.baz" (which would return the value of
1207 `result.foo.bar.baz` if the response is an object).
1209 While `resultListLocator` may be set to either a function or a string, it
1210 will always be returned as a function that accepts a response argument and
1213 @attribute resultListLocator
1214 @type Function|String|null
1216 resultListLocator: {
1217 setter: '_setLocator',
1222 Current results, or an empty array if there are no results.
1235 Locator that should be used to extract a plain text string from a non-string
1236 result item. The resulting text value will typically be the value that ends
1237 up being inserted into an input field or textarea when the user of an
1238 autocomplete implementation selects a result.
1240 By default, no locator is applied, and all results are assumed to be plain
1241 text strings. If all results are already plain text strings, you don't need
1242 to define a locator.
1244 The locator may be either a function (which will receive the raw result as
1245 an argument and must return a string) or a string representing an object
1246 path, such as "foo.bar.baz" (which would return the value of
1247 `result.foo.bar.baz` if the result is an object).
1249 While `resultTextLocator` may be set to either a function or a string, it
1250 will always be returned as a function that accepts a result argument and
1253 @attribute resultTextLocator
1254 @type Function|String|null
1256 resultTextLocator: {
1257 setter: '_setLocator',
1262 Source for autocomplete results. The following source types are supported:
1268 The full array will be provided to any configured filters for each
1269 query. This is an easy way to create a fully client-side autocomplete
1274 Example: `['first result', 'second result', 'etc']`
1280 A `DataSource` instance or other object that provides a DataSource-like
1281 `sendRequest` method. See the `DataSource` documentation for details.
1287 A function source will be called with the current query and a
1288 callback function as parameters, and should either return an array of
1289 results (for synchronous operation) or return nothing and pass an
1290 array of results to the provided callback (for asynchronous
1295 Example (synchronous):
1300 return ['foo', 'bar'];
1309 function (query, callback) {
1310 callback(['foo', 'bar']);
1318 An object will be treated as a query hashmap. If a property on the
1319 object matches the current query, the value of that property will be
1320 used as the response.
1324 The response is assumed to be an array of results by default. If the
1325 response is not an array, provide a `resultListLocator` to
1326 process the response and return an array.
1330 Example: `{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}`
1335 If the optional `autocomplete-sources` module is loaded, then
1336 the following additional source types will be supported as well:
1339 <dt><select> Node</dt>
1341 You may provide a YUI Node instance wrapping a <select>
1342 element, and the options in the list will be used as results. You
1343 will also need to specify a `resultTextLocator` of 'text'
1344 or 'value', depending on what you want to use as the text of the
1347 Each result will be an object with the following properties:
1350 <dt>html (String)</dt>
1352 <p>HTML content of the <option> element.</p>
1355 <dt>index (Number)</dt>
1357 <p>Index of the <option> element in the list.</p>
1360 <dt>node (Y.Node)</dt>
1362 <p>Node instance referring to the original <option> element.</p>
1365 <dt>selected (Boolean)</dt>
1367 <p>Whether or not this item is currently selected in the
1368 <select> list.</p>
1371 <dt>text (String)</dt>
1373 <p>Text content of the <option> element.</p>
1376 <dt>value (String)</dt>
1378 <p>Value of the <option> element.</p>
1383 <dt>String (JSONP URL)</dt>
1386 If a URL with a `{callback}` placeholder is provided, it will be used to
1387 make a JSONP request. The `{query}` placeholder will be replaced with
1388 the current query, and the `{callback}` placeholder will be replaced
1389 with an internally-generated JSONP callback name. Both placeholders must
1390 appear in the URL, or the request will fail. An optional `{maxResults}`
1391 placeholder may also be provided, and will be replaced with the value of
1392 the maxResults attribute (or 1000 if the maxResults attribute is 0 or
1397 The response is assumed to be an array of results by default. If the
1398 response is not an array, provide a `resultListLocator` to process the
1399 response and return an array.
1403 <strong>The `jsonp` module must be loaded in order for
1404 JSONP URL sources to work.</strong> If the `jsonp` module
1405 is not already loaded, it will be loaded on demand if possible.
1409 Example: `'http://example.com/search?q={query}&callback={callback}'`
1413 <dt>String (XHR URL)</dt>
1416 If a URL without a `{callback}` placeholder is provided, it will be used
1417 to make a same-origin XHR request. The `{query}` placeholder will be
1418 replaced with the current query. An optional `{maxResults}` placeholder
1419 may also be provided, and will be replaced with the value of the
1420 maxResults attribute (or 1000 if the maxResults attribute is 0 or less).
1424 The response is assumed to be a JSON array of results by default. If the
1425 response is a JSON object and not an array, provide a
1426 `resultListLocator` to process the response and return an array. If the
1427 response is in some form other than JSON, you will need to use a custom
1428 DataSource instance as the source.
1432 <strong>The `io-base` and `json-parse` modules
1433 must be loaded in order for XHR URL sources to work.</strong> If
1434 these modules are not already loaded, they will be loaded on demand
1439 Example: `'http://example.com/search?q={query}'`
1443 <dt>String (YQL query)</dt>
1446 If a YQL query is provided, it will be used to make a YQL request. The
1447 `{query}` placeholder will be replaced with the current autocomplete
1448 query. This placeholder must appear in the YQL query, or the request
1449 will fail. An optional `{maxResults}` placeholder may also be provided,
1450 and will be replaced with the value of the maxResults attribute (or 1000
1451 if the maxResults attribute is 0 or less).
1455 <strong>The `yql` module must be loaded in order for YQL
1456 sources to work.</strong> If the `yql` module is not
1457 already loaded, it will be loaded on demand if possible.
1461 Example: `'select * from search.suggest where query="{query}"'`
1466 As an alternative to providing a source, you could simply listen for `query`
1467 events and handle them any way you see fit. Providing a source is optional,
1468 but will usually be simpler.
1471 @type Array|DataSource|Function|Node|Object|String|null
1474 setter: '_setSource',
1479 May be used to force a specific source type, overriding the automatic source
1480 type detection. It should almost never be necessary to do this, but as they
1481 taught us in the Boy Scouts, one should always be prepared, so it's here if
1482 you need it. Be warned that if you set this attribute and something breaks,
1483 it's your own fault.
1485 Supported `sourceType` values are: 'array', 'datasource', 'function', and
1488 If the `autocomplete-sources` module is loaded, the following additional
1489 source types are supported: 'io', 'jsonp', 'select', 'string', 'yql'
1491 @attribute sourceType
1499 If the `inputNode` specified at instantiation time has a `node-tokeninput`
1500 plugin attached to it, this attribute will be a reference to the
1501 `Y.Plugin.TokenInput` instance.
1503 @attribute tokenInput
1504 @type Plugin.TokenInput
1512 Current value of the input node.
1519 // Why duplicate this._inputNode.get('value')? Because we need a
1520 // reliable way to track the source of value changes. We want to perform
1521 // completion when the user changes the value, but not when we change
1527 // This tells Y.Base.create() to copy these static properties to any class
1528 // AutoCompleteBase is mixed into.
1529 AutoCompleteBase._buildCfg = {
1530 aggregates: ['SOURCE_TYPES'],
1531 statics : ['UI_SRC']
1535 Mapping of built-in source types to their setter functions. DataSource instances
1536 and DataSource-like objects are handled natively, so are not mapped here.
1538 @property SOURCE_TYPES
1542 AutoCompleteBase.SOURCE_TYPES = {
1543 array : '_createArraySource',
1544 'function': '_createFunctionSource',
1545 object : '_createObjectSource'
1548 AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
1550 Y.AutoCompleteBase = AutoCompleteBase;
1555 "autocomplete-sources"
1561 "event-valuechange",