NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / autocomplete-base / autocomplete-base-debug.js
blob3d60638a915f7f4702141d8e603d86fe81de78ff
1 /*
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/
6 */
8 YUI.add('autocomplete-base', function (Y, NAME) {
10 /**
11 Provides automatic input completion or suggestions for text input fields and
12 textareas.
14 @module autocomplete
15 @main autocomplete
16 @since 3.3.0
17 **/
19 /**
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.
24 @module autocomplete
25 @submodule autocomplete-base
26 **/
28 /**
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
35 `AutoCompleteList`.
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
39 widget.
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.
46         }, {
47             // Custom static methods and properties.
48         });
50         // Custom implementation code.
51     });
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 () {
58                 this._bindUIACBase();
59                 this._syncUIACBase();
60             },
62             // Custom prototype methods and properties.
63         }, {
64             // Custom static methods and properties.
65         });
67         // Custom implementation code.
68     });
70 @class AutoCompleteBase
71 **/
73 var Escape  = Y.Escape,
74     Lang    = Y.Lang,
75     YArray  = Y.Array,
76     YObject = Y.Object,
78     isFunction = Lang.isFunction,
79     isString   = Lang.isString,
80     trim       = Lang.trim,
82     INVALID_VALUE = Y.Attribute.INVALID_VALUE,
84     _FUNCTION_VALIDATOR = '_functionValidator',
85     _SOURCE_SUCCESS     = '_sourceSuccess',
87     ALLOW_BROWSER_AC    = 'allowBrowserAutocomplete',
88     INPUT_NODE          = 'inputNode',
89     QUERY               = 'query',
90     QUERY_DELIMITER     = 'queryDelimiter',
91     REQUEST_TEMPLATE    = 'requestTemplate',
92     RESULTS             = 'results',
93     RESULT_LIST_LOCATOR = 'resultListLocator',
94     VALUE               = 'value',
95     VALUE_CHANGE        = 'valueChange',
97     EVT_CLEAR   = 'clear',
98     EVT_QUERY   = QUERY,
99     EVT_RESULTS = RESULTS;
101 function AutoCompleteBase() {}
103 AutoCompleteBase.prototype = {
104     // -- Lifecycle Methods ----------------------------------------------------
105     initializer: function () {
106         // AOP bindings.
107         Y.before(this._bindUIACBase, this, 'bindUI');
108         Y.before(this._syncUIACBase, this, 'syncUI');
110         // -- Public Events ----------------------------------------------------
112         /**
113         Fires after the query has been completely cleared or no longer meets the
114         minimum query length requirement.
116         @event clear
117         @param {String} prevVal Value of the query before it was cleared.
118         @param {String} src Source of the event.
119         @preventable _defClearFn
120         **/
121         this.publish(EVT_CLEAR, {
122             defaultFn: this._defClearFn
123         });
125         /**
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.
129         @event 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
134             as `inputValue`.
135         @param {String} src Source of the event.
136         @preventable _defQueryFn
137         **/
138         this.publish(EVT_QUERY, {
139             defaultFn: this._defQueryFn
140         });
142         /**
143         Fires after query results are received from the source. If no source has
144         been set, this event will not fire.
146         @event results
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
151             properties:
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
156                 `text` property.
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
169         **/
170         this.publish(EVT_RESULTS, {
171             defaultFn: this._defResultsFn
172         });
173     },
175     destructor: function () {
176         this._acBaseEvents && this._acBaseEvents.detach();
178         delete this._acBaseEvents;
179         delete this._cache;
180         delete this._inputNode;
181         delete this._rawSource;
182     },
184     // -- Public Prototype Methods ---------------------------------------------
186     /**
187     Clears the result cache.
189     @method clearCache
190     @chainable
191     @since 3.5.0
192     **/
193     clearCache: function () {
194         this._cache && (this._cache = {});
195         return this;
196     },
198     /**
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.
207     @method sendRequest
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
213         used.
214     @chainable
215     **/
216     sendRequest: function (query, requestTemplate) {
217         var request,
218             source = this.get('source');
220         if (query || query === '') {
221             this._set(QUERY, query);
222         } else {
223             query = this.get(QUERY) || '';
224         }
226         if (source) {
227             if (!requestTemplate) {
228                 requestTemplate = this.get(REQUEST_TEMPLATE);
229             }
231             request = requestTemplate ?
232                 requestTemplate.call(this, query) : query;
234             Y.log('sendRequest: ' + request, 'info', 'autocomplete-base');
236             source.sendRequest({
237                 query  : query,
238                 request: request,
240                 callback: {
241                     success: Y.bind(this._onResponse, this, query)
242                 }
243             });
244         }
246         return this;
247     },
249     // -- Protected Lifecycle Methods ------------------------------------------
251     /**
252     Attaches event listeners and behaviors.
254     @method _bindUIACBase
255     @protected
256     **/
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.
263         if (tokenInput) {
264             inputNode = tokenInput.get(INPUT_NODE);
265             this._set('tokenInput', tokenInput);
266         }
268         if (!inputNode) {
269             Y.error('No inputNode specified.');
270             return;
271         }
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)
284         ]);
285     },
287     /**
288     Synchronizes the UI state of the `inputNode`.
290     @method _syncUIACBase
291     @protected
292     **/
293     _syncUIACBase: function () {
294         this._syncBrowserAutocomplete();
295         this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
296     },
298     // -- Protected Prototype Methods ------------------------------------------
300     /**
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.
307     @protected
308     **/
309     _createArraySource: function (source) {
310         var that = this;
312         return {
313             type: 'array',
314             sendRequest: function (request) {
315                 that[_SOURCE_SUCCESS](source.concat(), request);
316             }
317         };
318     },
320     /**
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.
329     @protected
330     **/
331     _createFunctionSource: function (source) {
332         var that = this;
334         return {
335             type: 'function',
336             sendRequest: function (request) {
337                 var value;
339                 function afterResults(results) {
340                     that[_SOURCE_SUCCESS](results || [], request);
341                 }
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))) {
346                     afterResults(value);
347                 }
348             }
349         };
350     },
352     /**
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.
360     @protected
361     **/
362     _createObjectSource: function (source) {
363         var that = this;
365         return {
366             type: 'object',
367             sendRequest: function (request) {
368                 var query = request.query;
370                 that[_SOURCE_SUCCESS](
371                     YObject.owns(source, query) ? source[query] : [],
372                     request
373                 );
374             }
375         };
376     },
378     /**
379     Returns `true` if _value_ is either a function or `null`.
381     @method _functionValidator
382     @param {Function|null} value Value to validate.
383     @protected
384     **/
385     _functionValidator: function (value) {
386         return value === null || isFunction(value);
387     },
389     /**
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
396     @param {Object} obj
397     @param {Array} path
398     @return {Any} Located value, or `undefined` if the value was
399         not found at the specified path.
400     @protected
401     **/
402     _getObjectValue: function (obj, path) {
403         if (!obj) {
404             return;
405         }
407         for (var i = 0, len = path.length; obj && i < len; i++) {
408             obj = obj[path[i]];
409         }
411         return obj;
412     },
414     /**
415     Parses result responses, performs filtering and highlighting, and fires the
416     `results` event.
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.
422     @protected
423     **/
424     _parseResponse: function (query, response, data) {
425         var facade = {
426                 data   : data,
427                 query  : query,
428                 results: []
429             },
431             listLocator = this.get(RESULT_LIST_LOCATOR),
432             results     = [],
433             unfiltered  = response && response.results,
435             filters,
436             formatted,
437             formatter,
438             highlighted,
439             highlighter,
440             i,
441             len,
442             maxResults,
443             result,
444             text,
445             textLocator;
447         if (unfiltered && listLocator) {
448             unfiltered = listLocator.call(this, unfiltered);
449         }
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
458             // as we go.
459             for (i = 0, len = unfiltered.length; i < len; ++i) {
460                 result = unfiltered[i];
462                 text = textLocator ?
463                         textLocator.call(this, result) :
464                         result.toString();
466                 results.push({
467                     display: Escape.html(text),
468                     raw    : result,
469                     text   : text
470                 });
471             }
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());
479                 if (!results) {
480                     Y.log("Filter didn't return anything.", 'warn', 'autocomplete-base');
481                     return;
482                 }
484                 if (!results.length) {
485                     break;
486                 }
487             }
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
495                 // results.
496                 if (maxResults && maxResults > 0 &&
497                         results.length > maxResults) {
498                     results.length = maxResults;
499                 }
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.
505                 if (highlighter) {
506                     highlighted = highlighter.call(this, query,
507                             results.concat());
509                     if (!highlighted) {
510                         Y.log("Highlighter didn't return anything.", 'warn', 'autocomplete-base');
511                         return;
512                     }
514                     for (i = 0, len = highlighted.length; i < len; ++i) {
515                         result = results[i];
516                         result.highlighted = highlighted[i];
517                         result.display     = result.highlighted;
518                     }
519                 }
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
525                 // result object.
526                 if (formatter) {
527                     formatted = formatter.call(this, query, results.concat());
529                     if (!formatted) {
530                         Y.log("Formatter didn't return anything.", 'warn', 'autocomplete-base');
531                         return;
532                     }
534                     for (i = 0, len = formatted.length; i < len; ++i) {
535                         results[i].display = formatted[i];
536                     }
537                 }
538             }
539         }
541         facade.results = results;
542         this.fire(EVT_RESULTS, facade);
543     },
545     /**
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
550     of of the string.
552     @method _parseValue
553     @param {String} value Input value from which to extract the query.
554     @return {String|null} query
555     @protected
556     **/
557     _parseValue: function (value) {
558         var delim = this.get(QUERY_DELIMITER);
560         if (delim) {
561             value = value.split(delim);
562             value = value[value.length - 1];
563         }
565         return Lang.trimLeft(value);
566     },
568     /**
569     Setter for the `enableCache` attribute.
571     @method _setEnableCache
572     @param {Boolean} value
573     @protected
574     @since 3.5.0
575     **/
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');
583     },
585     /**
586     Setter for locator attributes.
588     @method _setLocator
589     @param {Function|String|null} locator
590     @return {Function|null}
591     @protected
592     **/
593     _setLocator: function (locator) {
594         if (this[_FUNCTION_VALIDATOR](locator)) {
595             return locator;
596         }
598         var that = this;
600         locator = locator.toString().split('.');
602         return function (result) {
603             return result && that._getObjectValue(result, locator);
604         };
605     },
607     /**
608     Setter for the `requestTemplate` attribute.
610     @method _setRequestTemplate
611     @param {Function|String|null} template
612     @return {Function|null}
613     @protected
614     **/
615     _setRequestTemplate: function (template) {
616         if (this[_FUNCTION_VALIDATOR](template)) {
617             return template;
618         }
620         template = template.toString();
622         return function (query) {
623             return Lang.sub(template, {query: encodeURIComponent(query)});
624         };
625     },
627     /**
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
635         `null`).
636     @protected
637     **/
638     _setResultFilters: function (filters) {
639         var acFilters, getFilterFunction;
641         if (filters === null) {
642             return [];
643         }
645         acFilters = Y.AutoCompleteFilters;
647         getFilterFunction = function (filter) {
648             if (isFunction(filter)) {
649                 return filter;
650             }
652             if (isString(filter) && acFilters &&
653                     isFunction(acFilters[filter])) {
654                 return acFilters[filter];
655             }
657             return false;
658         };
660         if (Lang.isArray(filters)) {
661             filters = YArray.map(filters, getFilterFunction);
662             return YArray.every(filters, function (f) { return !!f; }) ?
663                     filters : INVALID_VALUE;
664         } else {
665             filters = getFilterFunction(filters);
666             return filters ? [filters] : INVALID_VALUE;
667         }
668     },
670     /**
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}
678     @protected
679     **/
680     _setResultHighlighter: function (highlighter) {
681         var acHighlighters;
683         if (this[_FUNCTION_VALIDATOR](highlighter)) {
684             return highlighter;
685         }
687         acHighlighters = Y.AutoCompleteHighlighters;
689         if (isString(highlighter) && acHighlighters &&
690                 isFunction(acHighlighters[highlighter])) {
691             return acHighlighters[highlighter];
692         }
694         return INVALID_VALUE;
695     },
697     /**
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.
702     @method _setSource
703     @param {Any} source AutoComplete source. See the `source` attribute for
704         details.
705     @return {DataSource|Object}
706     @protected
707     **/
708     _setSource: function (source) {
709         var sourceType = this.get('sourceType') || Lang.type(source),
710             sourceSetter;
712         if ((source && isFunction(source.sendRequest))
713                 || source === null
714                 || sourceType === 'datasource') {
716             // Quacks like a DataSource instance (or null). Make it so!
717             this._rawSource = source;
718             return source;
719         }
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);
726         }
728         Y.error("Unsupported source type '" + sourceType + "'. Maybe autocomplete-sources isn't loaded?");
729         return INVALID_VALUE;
730     },
732     /**
733     Shared success callback for non-DataSource sources.
735     @method _sourceSuccess
736     @param {Any} data Response data.
737     @param {Object} request Request object.
738     @protected
739     **/
740     _sourceSuccess: function (data, request) {
741         request.callback.success({
742             data: data,
743             response: {results: data}
744         });
745     },
747     /**
748     Synchronizes the UI state of the `allowBrowserAutocomplete` attribute.
750     @method _syncBrowserAutocomplete
751     @protected
752     **/
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');
759         }
760     },
762     /**
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_.
768     @method _updateValue
769     @param {String} newVal New value.
770     @protected
771     **/
772     _updateValue: function (newVal) {
773         var delim = this.get(QUERY_DELIMITER),
774             insertDelim,
775             len,
776             prevVal;
778         newVal = Lang.trimLeft(newVal);
780         if (delim) {
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;
785             if (len > 1) {
786                 prevVal[len - 1] = newVal;
787                 newVal = prevVal.join(insertDelim + ' ');
788             }
790             newVal = newVal + insertDelim + ' ';
791         }
793         this.set(VALUE, newVal);
794     },
796     // -- Protected Event Handlers ---------------------------------------------
798     /**
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
804     @protected
805     **/
806     _afterSourceTypeChange: function (e) {
807         if (this._rawSource) {
808             this.set('source', this._rawSource);
809         }
810     },
812     /**
813     Handles change events for the `value` attribute.
815     @method _afterValueChange
816     @param {EventFacade} e
817     @protected
818     **/
819     _afterValueChange: function (e) {
820         var newVal   = e.newVal,
821             self     = this,
822             uiChange = e.src === AutoCompleteBase.UI_SRC,
823             delay, fire, minQueryLength, query;
825         // Update the UI if the value was changed programmatically.
826         if (!uiChange) {
827             self._inputNode.set(VALUE, newVal);
828         }
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.
837             if (uiChange) {
838                 delay = self.get('queryDelay');
840                 fire = function () {
841                     self.fire(EVT_QUERY, {
842                         inputValue: newVal,
843                         query     : query,
844                         src       : e.src
845                     });
846                 };
848                 if (delay) {
849                     clearTimeout(self._delay);
850                     self._delay = setTimeout(fire, delay);
851                 } else {
852                     fire();
853                 }
854             } else {
855                 // For programmatic value changes, just update the query
856                 // attribute without sending a query.
857                 self._set(QUERY, query);
858             }
859         } else {
860             clearTimeout(self._delay);
862             self.fire(EVT_CLEAR, {
863                 prevVal: e.prevVal ? self._parseValue(e.prevVal) : null,
864                 src    : e.src
865             });
866         }
867     },
869     /**
870     Handles `blur` events on the input node.
872     @method _onInputBlur
873     @param {EventFacade} e
874     @protected
875     **/
876     _onInputBlur: function (e) {
877         var delim = this.get(QUERY_DELIMITER),
878             delimPos,
879             newVal,
880             value;
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);
888             if (delim) {
889                 while ((newVal = Lang.trimRight(newVal)) &&
890                         (delimPos = newVal.length - delim.length) &&
891                         newVal.lastIndexOf(delim) === delimPos) {
893                     newVal = newVal.substring(0, delimPos);
894                 }
895             } else {
896                 // Delimiter is one or more space characters, so just trim the
897                 // value.
898                 newVal = Lang.trimRight(newVal);
899             }
901             if (newVal !== value) {
902                 this.set(VALUE, newVal);
903             }
904         }
905     },
907     /**
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
913     @protected
914     **/
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});
922         }
923     },
925     /**
926     Handles source responses and fires the `results` event.
928     @method _onResponse
929     @param {EventFacade} e
930     @protected
931     **/
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);
936         }
937     },
939     // -- Protected Default Event Handlers -------------------------------------
941     /**
942     Default `clear` event handler. Sets the `results` attribute to an empty
943     array and `query` to null.
945     @method _defClearFn
946     @protected
947     **/
948     _defClearFn: function () {
949         this._set(QUERY, null);
950         this._set(RESULTS, []);
951     },
953     /**
954     Default `query` event handler. Sets the `query` attribute and sends a
955     request to the source if one is configured.
957     @method _defQueryFn
958     @param {EventFacade} e
959     @protected
960     **/
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
964     },
966     /**
967     Default `results` event handler. Sets the `results` attribute to the latest
968     results.
970     @method _defResultsFn
971     @param {EventFacade} e
972     @protected
973     **/
974     _defResultsFn: function (e) {
975         Y.log('results: ' + Y.dump(e.results), 'info', 'autocomplete-base');
976         this._set(RESULTS, e[RESULTS]);
977     }
980 AutoCompleteBase.ATTRS = {
981     /**
982     Whether or not to enable the browser's built-in autocomplete functionality
983     for input fields.
985     @attribute allowBrowserAutocomplete
986     @type Boolean
987     @default false
988     **/
989     allowBrowserAutocomplete: {
990         value: false
991     },
993     /**
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
999     @type Boolean
1000     @default false
1001     **/
1002     allowTrailingDelimiter: {
1003         value: false
1004     },
1006     /**
1007     Whether or not to enable in-memory caching in result sources that support
1008     it.
1010     @attribute enableCache
1011     @type Boolean
1012     @default true
1013     @since 3.5.0
1014     **/
1015     enableCache: {
1016         lazyAdd: false, // we need the setter to run on init
1017         setter: '_setEnableCache',
1018         value: true
1019     },
1021     /**
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
1027     @initOnly
1028     **/
1029     inputNode: {
1030         setter: Y.one,
1031         writeOnce: 'initOnly'
1032     },
1034     /**
1035     Maximum number of results to return. A value of `0` or less will allow an
1036     unlimited number of results.
1038     @attribute maxResults
1039     @type Number
1040     @default 0
1041     **/
1042     maxResults: {
1043         value: 0
1044     },
1046     /**
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
1052     @type Number
1053     @default 1
1054     **/
1055     minQueryLength: {
1056         value: 1
1057     },
1059     /**
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.
1067     @attribute query
1068     @type String|null
1069     @default null
1070     @readonly
1071     **/
1072     query: {
1073         readOnly: true,
1074         value: null
1075     },
1077     /**
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
1087     @type Number
1088     @default 100
1089     **/
1090     queryDelay: {
1091         value: 100
1092     },
1094     /**
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
1098     modified.
1100     @attribute queryDelimiter
1101     @type String|null
1102     @default null
1103     **/
1104     queryDelimiter: {
1105         value: null
1106     },
1108     /**
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
1118     string.
1120     @attribute requestTemplate
1121     @type Function|String|null
1122     @default null
1123     **/
1124     requestTemplate: {
1125         setter: '_setRequestTemplate',
1126         value: null
1127     },
1129     /**
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
1145     module is loaded).
1147     @attribute resultFilters
1148     @type Array
1149     @default []
1150     **/
1151     resultFilters: {
1152         setter: '_setResultFilters',
1153         value: []
1154     },
1156     /**
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
1169     @type Function|null
1170     **/
1171     resultFormatter: {
1172         validator: _FUNCTION_VALIDATOR,
1173         value: null
1174     },
1176     /**
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
1181     HTML strings.
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
1189     @type Function|null
1190     **/
1191     resultHighlighter: {
1192         setter: '_setResultHighlighter',
1193         value: null
1194     },
1196     /**
1197     Locator that should be used to extract an array of results from a non-array
1198     response.
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
1202     define a locator.
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
1211     returns an array.
1213     @attribute resultListLocator
1214     @type Function|String|null
1215     **/
1216     resultListLocator: {
1217         setter: '_setLocator',
1218         value: null
1219     },
1221     /**
1222     Current results, or an empty array if there are no results.
1224     @attribute results
1225     @type Array
1226     @default []
1227     @readonly
1228     **/
1229     results: {
1230         readOnly: true,
1231         value: []
1232     },
1234     /**
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
1251     returns a string.
1253     @attribute resultTextLocator
1254     @type Function|String|null
1255     **/
1256     resultTextLocator: {
1257         setter: '_setLocator',
1258         value: null
1259     },
1261     /**
1262     Source for autocomplete results. The following source types are supported:
1264     <dl>
1265       <dt>Array</dt>
1266       <dd>
1267         <p>
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
1270         implementation.
1271         </p>
1273         <p>
1274         Example: `['first result', 'second result', 'etc']`
1275         </p>
1276       </dd>
1278       <dt>DataSource</dt>
1279       <dd>
1280         A `DataSource` instance or other object that provides a DataSource-like
1281         `sendRequest` method. See the `DataSource` documentation for details.
1282       </dd>
1284       <dt>Function</dt>
1285       <dd>
1286         <p>
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
1291         operation).
1292         </p>
1294         <p>
1295         Example (synchronous):
1296         </p>
1298         <pre>
1299         function (query) {
1300             return ['foo', 'bar'];
1301         }
1302         </pre>
1304         <p>
1305         Example (async):
1306         </p>
1308         <pre>
1309         function (query, callback) {
1310             callback(['foo', 'bar']);
1311         }
1312         </pre>
1313       </dd>
1315       <dt>Object</dt>
1316       <dd>
1317         <p>
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.
1321         </p>
1323         <p>
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.
1327         </p>
1329         <p>
1330         Example: `{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}`
1331         </p>
1332       </dd>
1333     </dl>
1335     If the optional `autocomplete-sources` module is loaded, then
1336     the following additional source types will be supported as well:
1338     <dl>
1339       <dt>&lt;select&gt; Node</dt>
1340       <dd>
1341         You may provide a YUI Node instance wrapping a &lt;select&gt;
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
1345         result.
1347         Each result will be an object with the following properties:
1349         <dl>
1350           <dt>html (String)</dt>
1351           <dd>
1352             <p>HTML content of the &lt;option&gt; element.</p>
1353           </dd>
1355           <dt>index (Number)</dt>
1356           <dd>
1357             <p>Index of the &lt;option&gt; element in the list.</p>
1358           </dd>
1360           <dt>node (Y.Node)</dt>
1361           <dd>
1362             <p>Node instance referring to the original &lt;option&gt; element.</p>
1363           </dd>
1365           <dt>selected (Boolean)</dt>
1366           <dd>
1367             <p>Whether or not this item is currently selected in the
1368             &lt;select&gt; list.</p>
1369           </dd>
1371           <dt>text (String)</dt>
1372           <dd>
1373             <p>Text content of the &lt;option&gt; element.</p>
1374           </dd>
1376           <dt>value (String)</dt>
1377           <dd>
1378             <p>Value of the &lt;option&gt; element.</p>
1379           </dd>
1380         </dl>
1381       </dd>
1383       <dt>String (JSONP URL)</dt>
1384       <dd>
1385         <p>
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
1393         less).
1394         </p>
1396         <p>
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.
1400         </p>
1402         <p>
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.
1406         </p>
1408         <p>
1409         Example: `'http://example.com/search?q={query}&callback={callback}'`
1410         </p>
1411       </dd>
1413       <dt>String (XHR URL)</dt>
1414       <dd>
1415         <p>
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).
1421         </p>
1423         <p>
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.
1429         </p>
1431         <p>
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
1435         if possible.
1436         </p>
1438         <p>
1439         Example: `'http://example.com/search?q={query}'`
1440         </p>
1441       </dd>
1443       <dt>String (YQL query)</dt>
1444       <dd>
1445         <p>
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).
1452         </p>
1454         <p>
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.
1458         </p>
1460         <p>
1461         Example: `'select * from search.suggest where query="{query}"'`
1462         </p>
1463       </dd>
1464     </dl>
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.
1470     @attribute source
1471     @type Array|DataSource|Function|Node|Object|String|null
1472     **/
1473     source: {
1474         setter: '_setSource',
1475         value: null
1476     },
1478     /**
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
1486     'object'.
1488     If the `autocomplete-sources` module is loaded, the following additional
1489     source types are supported: 'io', 'jsonp', 'select', 'string', 'yql'
1491     @attribute sourceType
1492     @type String
1493     **/
1494     sourceType: {
1495         value: null
1496     },
1498     /**
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
1505     @readonly
1506     **/
1507     tokenInput: {
1508         readOnly: true
1509     },
1511     /**
1512     Current value of the input node.
1514     @attribute value
1515     @type String
1516     @default ''
1517     **/
1518     value: {
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
1522         // the value.
1523         value: ''
1524     }
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
1539 @type {Object}
1540 @static
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;
1553 }, '3.13.0', {
1554     "optional": [
1555         "autocomplete-sources"
1556     ],
1557     "requires": [
1558         "array-extras",
1559         "base-build",
1560         "escape",
1561         "event-valuechange",
1562         "node-base"
1563     ]