API Docs for: 3.17.2
Show:

File: autocomplete/js/autocomplete-base.js

  1. /**
  2. Provides automatic input completion or suggestions for text input fields and
  3. textareas.
  4.  
  5. @module autocomplete
  6. @main autocomplete
  7. @since 3.3.0
  8. **/
  9.  
  10. /**
  11. `Y.Base` extension that provides core autocomplete logic (but no UI
  12. implementation) for a text input field or textarea. Must be mixed into a
  13. `Y.Base`-derived class to be useful.
  14.  
  15. @module autocomplete
  16. @submodule autocomplete-base
  17. **/
  18.  
  19. /**
  20. Extension that provides core autocomplete logic (but no UI implementation) for a
  21. text input field or textarea.
  22.  
  23. The `AutoCompleteBase` class provides events and attributes that abstract away
  24. core autocomplete logic and configuration, but does not provide a widget
  25. implementation or suggestion UI. For a prepackaged autocomplete widget, see
  26. `AutoCompleteList`.
  27.  
  28. This extension cannot be instantiated directly, since it doesn't provide an
  29. actual implementation. It's intended to be mixed into a `Y.Base`-based class or
  30. widget.
  31.  
  32. `Y.Widget`-based example:
  33.  
  34. YUI().use('autocomplete-base', 'widget', function (Y) {
  35. var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
  36. // Custom prototype methods and properties.
  37. }, {
  38. // Custom static methods and properties.
  39. });
  40.  
  41. // Custom implementation code.
  42. });
  43.  
  44. `Y.Base`-based example:
  45.  
  46. YUI().use('autocomplete-base', function (Y) {
  47. var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
  48. initializer: function () {
  49. this._bindUIACBase();
  50. this._syncUIACBase();
  51. },
  52.  
  53. // Custom prototype methods and properties.
  54. }, {
  55. // Custom static methods and properties.
  56. });
  57.  
  58. // Custom implementation code.
  59. });
  60.  
  61. @class AutoCompleteBase
  62. **/
  63.  
  64. var Escape = Y.Escape,
  65. Lang = Y.Lang,
  66. YArray = Y.Array,
  67. YObject = Y.Object,
  68.  
  69. isFunction = Lang.isFunction,
  70. isString = Lang.isString,
  71. trim = Lang.trim,
  72.  
  73. INVALID_VALUE = Y.Attribute.INVALID_VALUE,
  74.  
  75. _FUNCTION_VALIDATOR = '_functionValidator',
  76. _SOURCE_SUCCESS = '_sourceSuccess',
  77.  
  78. ALLOW_BROWSER_AC = 'allowBrowserAutocomplete',
  79. INPUT_NODE = 'inputNode',
  80. QUERY = 'query',
  81. QUERY_DELIMITER = 'queryDelimiter',
  82. REQUEST_TEMPLATE = 'requestTemplate',
  83. RESULTS = 'results',
  84. RESULT_LIST_LOCATOR = 'resultListLocator',
  85. VALUE = 'value',
  86. VALUE_CHANGE = 'valueChange',
  87.  
  88. EVT_CLEAR = 'clear',
  89. EVT_QUERY = QUERY,
  90. EVT_RESULTS = RESULTS;
  91.  
  92. function AutoCompleteBase() {}
  93.  
  94. AutoCompleteBase.prototype = {
  95. // -- Lifecycle Methods ----------------------------------------------------
  96. initializer: function () {
  97. // AOP bindings.
  98. Y.before(this._bindUIACBase, this, 'bindUI');
  99. Y.before(this._syncUIACBase, this, 'syncUI');
  100.  
  101. // -- Public Events ----------------------------------------------------
  102.  
  103. /**
  104. Fires after the query has been completely cleared or no longer meets the
  105. minimum query length requirement.
  106.  
  107. @event clear
  108. @param {String} prevVal Value of the query before it was cleared.
  109. @param {String} src Source of the event.
  110. @preventable _defClearFn
  111. **/
  112. this.publish(EVT_CLEAR, {
  113. defaultFn: this._defClearFn
  114. });
  115.  
  116. /**
  117. Fires when the contents of the input field have changed and the input
  118. value meets the criteria necessary to generate an autocomplete query.
  119.  
  120. @event query
  121. @param {String} inputValue Full contents of the text input field or
  122. textarea that generated the query.
  123. @param {String} query AutoComplete query. This is the string that will
  124. be used to request completion results. It may or may not be the same
  125. as `inputValue`.
  126. @param {String} src Source of the event.
  127. @preventable _defQueryFn
  128. **/
  129. this.publish(EVT_QUERY, {
  130. defaultFn: this._defQueryFn
  131. });
  132.  
  133. /**
  134. Fires after query results are received from the source. If no source has
  135. been set, this event will not fire.
  136.  
  137. @event results
  138. @param {Array|Object} data Raw, unfiltered result data (if available).
  139. @param {String} query Query that generated these results.
  140. @param {Object[]} results Array of filtered, formatted, and highlighted
  141. results. Each item in the array is an object with the following
  142. properties:
  143.  
  144. @param {Node|HTMLElement|String} results.display Formatted result
  145. HTML suitable for display to the user. If no custom formatter is
  146. set, this will be an HTML-escaped version of the string in the
  147. `text` property.
  148. @param {String} [results.highlighted] Highlighted (but not
  149. formatted) result text. This property will only be set if a
  150. highlighter is in use.
  151. @param {Any} results.raw Raw, unformatted result in whatever form it
  152. was provided by the source.
  153. @param {String} results.text Plain text version of the result,
  154. suitable for being inserted into the value of a text input field
  155. or textarea when the result is selected by a user. This value is
  156. not HTML-escaped and should not be inserted into the page using
  157. `innerHTML` or `Node#setContent()`.
  158.  
  159. @preventable _defResultsFn
  160. **/
  161. this.publish(EVT_RESULTS, {
  162. defaultFn: this._defResultsFn
  163. });
  164. },
  165.  
  166. destructor: function () {
  167. this._acBaseEvents && this._acBaseEvents.detach();
  168.  
  169. delete this._acBaseEvents;
  170. delete this._cache;
  171. delete this._inputNode;
  172. delete this._rawSource;
  173. },
  174.  
  175. // -- Public Prototype Methods ---------------------------------------------
  176.  
  177. /**
  178. Clears the result cache.
  179.  
  180. @method clearCache
  181. @chainable
  182. @since 3.5.0
  183. **/
  184. clearCache: function () {
  185. this._cache && (this._cache = {});
  186. return this;
  187. },
  188.  
  189. /**
  190. Sends a request to the configured source. If no source is configured, this
  191. method won't do anything.
  192.  
  193. Usually there's no reason to call this method manually; it will be called
  194. automatically when user input causes a `query` event to be fired. The only
  195. time you'll need to call this method manually is if you want to force a
  196. request to be sent when no user input has occurred.
  197.  
  198. @method sendRequest
  199. @param {String} [query] Query to send. If specified, the `query` attribute
  200. will be set to this query. If not specified, the current value of the
  201. `query` attribute will be used.
  202. @param {Function} [requestTemplate] Request template function. If not
  203. specified, the current value of the `requestTemplate` attribute will be
  204. used.
  205. @chainable
  206. **/
  207. sendRequest: function (query, requestTemplate) {
  208. var request,
  209. source = this.get('source');
  210.  
  211. if (query || query === '') {
  212. this._set(QUERY, query);
  213. } else {
  214. query = this.get(QUERY) || '';
  215. }
  216.  
  217. if (source) {
  218. if (!requestTemplate) {
  219. requestTemplate = this.get(REQUEST_TEMPLATE);
  220. }
  221.  
  222. request = requestTemplate ?
  223. requestTemplate.call(this, query) : query;
  224.  
  225. Y.log('sendRequest: ' + request, 'info', 'autocomplete-base');
  226.  
  227. source.sendRequest({
  228. query : query,
  229. request: request,
  230.  
  231. callback: {
  232. success: Y.bind(this._onResponse, this, query)
  233. }
  234. });
  235. }
  236.  
  237. return this;
  238. },
  239.  
  240. // -- Protected Lifecycle Methods ------------------------------------------
  241.  
  242. /**
  243. Attaches event listeners and behaviors.
  244.  
  245. @method _bindUIACBase
  246. @protected
  247. **/
  248. _bindUIACBase: function () {
  249. var inputNode = this.get(INPUT_NODE),
  250. tokenInput = inputNode && inputNode.tokenInput;
  251.  
  252. // If the inputNode has a node-tokeninput plugin attached, bind to the
  253. // plugin's inputNode instead.
  254. if (tokenInput) {
  255. inputNode = tokenInput.get(INPUT_NODE);
  256. this._set('tokenInput', tokenInput);
  257. }
  258.  
  259. if (!inputNode) {
  260. Y.error('No inputNode specified.');
  261. return;
  262. }
  263.  
  264. this._inputNode = inputNode;
  265.  
  266. this._acBaseEvents = new Y.EventHandle([
  267. // This is the valueChange event on the inputNode, provided by the
  268. // event-valuechange module, not our own valueChange.
  269. inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
  270. inputNode.on('blur', this._onInputBlur, this),
  271.  
  272. this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
  273. this.after('sourceTypeChange', this._afterSourceTypeChange),
  274. this.after(VALUE_CHANGE, this._afterValueChange)
  275. ]);
  276. },
  277.  
  278. /**
  279. Synchronizes the UI state of the `inputNode`.
  280.  
  281. @method _syncUIACBase
  282. @protected
  283. **/
  284. _syncUIACBase: function () {
  285. this._syncBrowserAutocomplete();
  286. this.set(VALUE, this.get(INPUT_NODE).get(VALUE));
  287. },
  288.  
  289. // -- Protected Prototype Methods ------------------------------------------
  290.  
  291. /**
  292. Creates a DataSource-like object that simply returns the specified array as
  293. a response. See the `source` attribute for more details.
  294.  
  295. @method _createArraySource
  296. @param {Array} source
  297. @return {Object} DataSource-like object.
  298. @protected
  299. **/
  300. _createArraySource: function (source) {
  301. var that = this;
  302.  
  303. return {
  304. type: 'array',
  305. sendRequest: function (request) {
  306. that[_SOURCE_SUCCESS](source.concat(), request);
  307. }
  308. };
  309. },
  310.  
  311. /**
  312. Creates a DataSource-like object that passes the query to a custom-defined
  313. function, which is expected to call the provided callback with an array of
  314. results. See the `source` attribute for more details.
  315.  
  316. @method _createFunctionSource
  317. @param {Function} source Function that accepts a query and a callback as
  318. parameters, and calls the callback with an array of results.
  319. @return {Object} DataSource-like object.
  320. @protected
  321. **/
  322. _createFunctionSource: function (source) {
  323. var that = this;
  324.  
  325. return {
  326. type: 'function',
  327. sendRequest: function (request) {
  328. var value;
  329.  
  330. function afterResults(results) {
  331. that[_SOURCE_SUCCESS](results || [], request);
  332. }
  333.  
  334. // Allow both synchronous and asynchronous functions. If we get
  335. // a truthy return value, assume the function is synchronous.
  336. if ((value = source(request.query, afterResults))) {
  337. afterResults(value);
  338. }
  339. }
  340. };
  341. },
  342.  
  343. /**
  344. Creates a DataSource-like object that looks up queries as properties on the
  345. specified object, and returns the found value (if any) as a response. See
  346. the `source` attribute for more details.
  347.  
  348. @method _createObjectSource
  349. @param {Object} source
  350. @return {Object} DataSource-like object.
  351. @protected
  352. **/
  353. _createObjectSource: function (source) {
  354. var that = this;
  355.  
  356. return {
  357. type: 'object',
  358. sendRequest: function (request) {
  359. var query = request.query;
  360.  
  361. that[_SOURCE_SUCCESS](
  362. YObject.owns(source, query) ? source[query] : [],
  363. request
  364. );
  365. }
  366. };
  367. },
  368.  
  369. /**
  370. Returns `true` if _value_ is either a function or `null`.
  371.  
  372. @method _functionValidator
  373. @param {Function|null} value Value to validate.
  374. @protected
  375. **/
  376. _functionValidator: function (value) {
  377. return value === null || isFunction(value);
  378. },
  379.  
  380. /**
  381. Faster and safer alternative to `Y.Object.getValue()`. Doesn't bother
  382. casting the path to an array (since we already know it's an array) and
  383. doesn't throw an error if a value in the middle of the object hierarchy is
  384. neither `undefined` nor an object.
  385.  
  386. @method _getObjectValue
  387. @param {Object} obj
  388. @param {Array} path
  389. @return {Any} Located value, or `undefined` if the value was
  390. not found at the specified path.
  391. @protected
  392. **/
  393. _getObjectValue: function (obj, path) {
  394. if (!obj) {
  395. return;
  396. }
  397.  
  398. for (var i = 0, len = path.length; obj && i < len; i++) {
  399. obj = obj[path[i]];
  400. }
  401.  
  402. return obj;
  403. },
  404.  
  405. /**
  406. Parses result responses, performs filtering and highlighting, and fires the
  407. `results` event.
  408.  
  409. @method _parseResponse
  410. @param {String} query Query that generated these results.
  411. @param {Object} response Response containing results.
  412. @param {Object} data Raw response data.
  413. @protected
  414. **/
  415. _parseResponse: function (query, response, data) {
  416. var facade = {
  417. data : data,
  418. query : query,
  419. results: []
  420. },
  421.  
  422. listLocator = this.get(RESULT_LIST_LOCATOR),
  423. results = [],
  424. unfiltered = response && response.results,
  425.  
  426. filters,
  427. formatted,
  428. formatter,
  429. highlighted,
  430. highlighter,
  431. i,
  432. len,
  433. maxResults,
  434. result,
  435. text,
  436. textLocator;
  437.  
  438. if (unfiltered && listLocator) {
  439. unfiltered = listLocator.call(this, unfiltered);
  440. }
  441.  
  442. if (unfiltered && unfiltered.length) {
  443. filters = this.get('resultFilters');
  444. textLocator = this.get('resultTextLocator');
  445.  
  446. // Create a lightweight result object for each result to make them
  447. // easier to work with. The various properties on the object
  448. // represent different formats of the result, and will be populated
  449. // as we go.
  450. for (i = 0, len = unfiltered.length; i < len; ++i) {
  451. result = unfiltered[i];
  452.  
  453. text = textLocator ?
  454. textLocator.call(this, result) :
  455. result.toString();
  456.  
  457. results.push({
  458. display: Escape.html(text),
  459. raw : result,
  460. text : text
  461. });
  462. }
  463.  
  464. // Run the results through all configured result filters. Each
  465. // filter returns an array of (potentially fewer) result objects,
  466. // which is then passed to the next filter, and so on.
  467. for (i = 0, len = filters.length; i < len; ++i) {
  468. results = filters[i].call(this, query, results.concat());
  469.  
  470. if (!results) {
  471. Y.log("Filter didn't return anything.", 'warn', 'autocomplete-base');
  472. return;
  473. }
  474.  
  475. if (!results.length) {
  476. break;
  477. }
  478. }
  479.  
  480. if (results.length) {
  481. formatter = this.get('resultFormatter');
  482. highlighter = this.get('resultHighlighter');
  483. maxResults = this.get('maxResults');
  484.  
  485. // If maxResults is set and greater than 0, limit the number of
  486. // results.
  487. if (maxResults && maxResults > 0 &&
  488. results.length > maxResults) {
  489. results.length = maxResults;
  490. }
  491.  
  492. // Run the results through the configured highlighter (if any).
  493. // The highlighter returns an array of highlighted strings (not
  494. // an array of result objects), and these strings are then added
  495. // to each result object.
  496. if (highlighter) {
  497. highlighted = highlighter.call(this, query,
  498. results.concat());
  499.  
  500. if (!highlighted) {
  501. Y.log("Highlighter didn't return anything.", 'warn', 'autocomplete-base');
  502. return;
  503. }
  504.  
  505. for (i = 0, len = highlighted.length; i < len; ++i) {
  506. result = results[i];
  507. result.highlighted = highlighted[i];
  508. result.display = result.highlighted;
  509. }
  510. }
  511.  
  512. // Run the results through the configured formatter (if any) to
  513. // produce the final formatted results. The formatter returns an
  514. // array of strings or Node instances (not an array of result
  515. // objects), and these strings/Nodes are then added to each
  516. // result object.
  517. if (formatter) {
  518. formatted = formatter.call(this, query, results.concat());
  519.  
  520. if (!formatted) {
  521. Y.log("Formatter didn't return anything.", 'warn', 'autocomplete-base');
  522. return;
  523. }
  524.  
  525. for (i = 0, len = formatted.length; i < len; ++i) {
  526. results[i].display = formatted[i];
  527. }
  528. }
  529. }
  530. }
  531.  
  532. facade.results = results;
  533. this.fire(EVT_RESULTS, facade);
  534. },
  535.  
  536. /**
  537. Returns the query portion of the specified input value, or `null` if there
  538. is no suitable query within the input value.
  539.  
  540. If a query delimiter is defined, the query will be the last delimited part
  541. of of the string.
  542.  
  543. @method _parseValue
  544. @param {String} value Input value from which to extract the query.
  545. @return {String|null} query
  546. @protected
  547. **/
  548. _parseValue: function (value) {
  549. var delim = this.get(QUERY_DELIMITER);
  550.  
  551. if (delim) {
  552. value = value.split(delim);
  553. value = value[value.length - 1];
  554. }
  555.  
  556. return Lang.trimLeft(value);
  557. },
  558.  
  559. /**
  560. Setter for the `enableCache` attribute.
  561.  
  562. @method _setEnableCache
  563. @param {Boolean} value
  564. @protected
  565. @since 3.5.0
  566. **/
  567. _setEnableCache: function (value) {
  568. // When `this._cache` is an object, result sources will store cached
  569. // results in it. When it's falsy, they won't. This way result sources
  570. // don't need to get the value of the `enableCache` attribute on every
  571. // request, which would be sloooow.
  572. this._cache = value ? {} : null;
  573. Y.log('Cache ' + (value ? 'enabled' : 'disabled'), 'debug', 'autocomplete-base');
  574. },
  575.  
  576. /**
  577. Setter for locator attributes.
  578.  
  579. @method _setLocator
  580. @param {Function|String|null} locator
  581. @return {Function|null}
  582. @protected
  583. **/
  584. _setLocator: function (locator) {
  585. if (this[_FUNCTION_VALIDATOR](locator)) {
  586. return locator;
  587. }
  588.  
  589. var that = this;
  590.  
  591. locator = locator.toString().split('.');
  592.  
  593. return function (result) {
  594. return result && that._getObjectValue(result, locator);
  595. };
  596. },
  597.  
  598. /**
  599. Setter for the `requestTemplate` attribute.
  600.  
  601. @method _setRequestTemplate
  602. @param {Function|String|null} template
  603. @return {Function|null}
  604. @protected
  605. **/
  606. _setRequestTemplate: function (template) {
  607. if (this[_FUNCTION_VALIDATOR](template)) {
  608. return template;
  609. }
  610.  
  611. template = template.toString();
  612.  
  613. return function (query) {
  614. return Lang.sub(template, {query: encodeURIComponent(query)});
  615. };
  616. },
  617.  
  618. /**
  619. Setter for the `resultFilters` attribute.
  620.  
  621. @method _setResultFilters
  622. @param {Array|Function|String|null} filters `null`, a filter
  623. function, an array of filter functions, or a string or array of strings
  624. representing the names of methods on `Y.AutoCompleteFilters`.
  625. @return {Function[]} Array of filter functions (empty if <i>filters</i> is
  626. `null`).
  627. @protected
  628. **/
  629. _setResultFilters: function (filters) {
  630. var acFilters, getFilterFunction;
  631.  
  632. if (filters === null) {
  633. return [];
  634. }
  635.  
  636. acFilters = Y.AutoCompleteFilters;
  637.  
  638. getFilterFunction = function (filter) {
  639. if (isFunction(filter)) {
  640. return filter;
  641. }
  642.  
  643. if (isString(filter) && acFilters &&
  644. isFunction(acFilters[filter])) {
  645. return acFilters[filter];
  646. }
  647.  
  648. return false;
  649. };
  650.  
  651. if (Lang.isArray(filters)) {
  652. filters = YArray.map(filters, getFilterFunction);
  653. return YArray.every(filters, function (f) { return !!f; }) ?
  654. filters : INVALID_VALUE;
  655. } else {
  656. filters = getFilterFunction(filters);
  657. return filters ? [filters] : INVALID_VALUE;
  658. }
  659. },
  660.  
  661. /**
  662. Setter for the `resultHighlighter` attribute.
  663.  
  664. @method _setResultHighlighter
  665. @param {Function|String|null} highlighter `null`, a highlighter function, or
  666. a string representing the name of a method on
  667. `Y.AutoCompleteHighlighters`.
  668. @return {Function|null}
  669. @protected
  670. **/
  671. _setResultHighlighter: function (highlighter) {
  672. var acHighlighters;
  673.  
  674. if (this[_FUNCTION_VALIDATOR](highlighter)) {
  675. return highlighter;
  676. }
  677.  
  678. acHighlighters = Y.AutoCompleteHighlighters;
  679.  
  680. if (isString(highlighter) && acHighlighters &&
  681. isFunction(acHighlighters[highlighter])) {
  682. return acHighlighters[highlighter];
  683. }
  684.  
  685. return INVALID_VALUE;
  686. },
  687.  
  688. /**
  689. Setter for the `source` attribute. Returns a DataSource or a DataSource-like
  690. object depending on the type of _source_ and/or the value of the
  691. `sourceType` attribute.
  692.  
  693. @method _setSource
  694. @param {Any} source AutoComplete source. See the `source` attribute for
  695. details.
  696. @return {DataSource|Object}
  697. @protected
  698. **/
  699. _setSource: function (source) {
  700. var sourceType = this.get('sourceType') || Lang.type(source),
  701. sourceSetter;
  702.  
  703. if ((source && isFunction(source.sendRequest))
  704. || source === null
  705. || sourceType === 'datasource') {
  706.  
  707. // Quacks like a DataSource instance (or null). Make it so!
  708. this._rawSource = source;
  709. return source;
  710. }
  711.  
  712. // See if there's a registered setter for this source type.
  713. if ((sourceSetter = AutoCompleteBase.SOURCE_TYPES[sourceType])) {
  714. this._rawSource = source;
  715. return Lang.isString(sourceSetter) ?
  716. this[sourceSetter](source) : sourceSetter(source);
  717. }
  718.  
  719. Y.error("Unsupported source type '" + sourceType + "'. Maybe autocomplete-sources isn't loaded?");
  720. return INVALID_VALUE;
  721. },
  722.  
  723. /**
  724. Shared success callback for non-DataSource sources.
  725.  
  726. @method _sourceSuccess
  727. @param {Any} data Response data.
  728. @param {Object} request Request object.
  729. @protected
  730. **/
  731. _sourceSuccess: function (data, request) {
  732. request.callback.success({
  733. data: data,
  734. response: {results: data}
  735. });
  736. },
  737.  
  738. /**
  739. Synchronizes the UI state of the `allowBrowserAutocomplete` attribute.
  740.  
  741. @method _syncBrowserAutocomplete
  742. @protected
  743. **/
  744. _syncBrowserAutocomplete: function () {
  745. var inputNode = this.get(INPUT_NODE);
  746.  
  747. if (inputNode.get('nodeName').toLowerCase() === 'input') {
  748. inputNode.setAttribute('autocomplete',
  749. this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
  750. }
  751. },
  752.  
  753. /**
  754. Updates the query portion of the `value` attribute.
  755.  
  756. If a query delimiter is defined, the last delimited portion of the input
  757. value will be replaced with the specified _value_.
  758.  
  759. @method _updateValue
  760. @param {String} newVal New value.
  761. @protected
  762. **/
  763. _updateValue: function (newVal) {
  764. var delim = this.get(QUERY_DELIMITER),
  765. insertDelim,
  766. len,
  767. prevVal;
  768.  
  769. newVal = Lang.trimLeft(newVal);
  770.  
  771. if (delim) {
  772. insertDelim = trim(delim); // so we don't double up on spaces
  773. prevVal = YArray.map(trim(this.get(VALUE)).split(delim), trim);
  774. len = prevVal.length;
  775.  
  776. if (len > 1) {
  777. prevVal[len - 1] = newVal;
  778. newVal = prevVal.join(insertDelim + ' ');
  779. }
  780.  
  781. newVal = newVal + insertDelim + ' ';
  782. }
  783.  
  784. this.set(VALUE, newVal);
  785. },
  786.  
  787. // -- Protected Event Handlers ---------------------------------------------
  788.  
  789. /**
  790. Updates the current `source` based on the new `sourceType` to ensure that
  791. the two attributes don't get out of sync when they're changed separately.
  792.  
  793. @method _afterSourceTypeChange
  794. @param {EventFacade} e
  795. @protected
  796. **/
  797. _afterSourceTypeChange: function (e) {
  798. if (this._rawSource) {
  799. this.set('source', this._rawSource);
  800. }
  801. },
  802.  
  803. /**
  804. Handles change events for the `value` attribute.
  805.  
  806. @method _afterValueChange
  807. @param {EventFacade} e
  808. @protected
  809. **/
  810. _afterValueChange: function (e) {
  811. var newVal = e.newVal,
  812. self = this,
  813. uiChange = e.src === AutoCompleteBase.UI_SRC,
  814. delay, fire, minQueryLength, query;
  815.  
  816. // Update the UI if the value was changed programmatically.
  817. if (!uiChange) {
  818. self._inputNode.set(VALUE, newVal);
  819. }
  820.  
  821. Y.log('valueChange: new: "' + newVal + '"; old: "' + e.prevVal + '"', 'info', 'autocomplete-base');
  822.  
  823. minQueryLength = self.get('minQueryLength');
  824. query = self._parseValue(newVal) || '';
  825.  
  826. if (minQueryLength >= 0 && query.length >= minQueryLength) {
  827. // Only query on changes that originate from the UI.
  828. if (uiChange) {
  829. delay = self.get('queryDelay');
  830.  
  831. fire = function () {
  832. self.fire(EVT_QUERY, {
  833. inputValue: newVal,
  834. query : query,
  835. src : e.src
  836. });
  837. };
  838.  
  839. if (delay) {
  840. clearTimeout(self._delay);
  841. self._delay = setTimeout(fire, delay);
  842. } else {
  843. fire();
  844. }
  845. } else {
  846. // For programmatic value changes, just update the query
  847. // attribute without sending a query.
  848. self._set(QUERY, query);
  849. }
  850. } else {
  851. clearTimeout(self._delay);
  852.  
  853. self.fire(EVT_CLEAR, {
  854. prevVal: e.prevVal ? self._parseValue(e.prevVal) : null,
  855. src : e.src
  856. });
  857. }
  858. },
  859.  
  860. /**
  861. Handles `blur` events on the input node.
  862.  
  863. @method _onInputBlur
  864. @param {EventFacade} e
  865. @protected
  866. **/
  867. _onInputBlur: function (e) {
  868. var delim = this.get(QUERY_DELIMITER),
  869. delimPos,
  870. newVal,
  871. value;
  872.  
  873. // If a query delimiter is set and the input's value contains one or
  874. // more trailing delimiters, strip them.
  875. if (delim && !this.get('allowTrailingDelimiter')) {
  876. delim = Lang.trimRight(delim);
  877. value = newVal = this._inputNode.get(VALUE);
  878.  
  879. if (delim) {
  880. while ((newVal = Lang.trimRight(newVal)) &&
  881. (delimPos = newVal.length - delim.length) &&
  882. newVal.lastIndexOf(delim) === delimPos) {
  883.  
  884. newVal = newVal.substring(0, delimPos);
  885. }
  886. } else {
  887. // Delimiter is one or more space characters, so just trim the
  888. // value.
  889. newVal = Lang.trimRight(newVal);
  890. }
  891.  
  892. if (newVal !== value) {
  893. this.set(VALUE, newVal);
  894. }
  895. }
  896. },
  897.  
  898. /**
  899. Handles `valueChange` events on the input node and fires a `query` event
  900. when the input value meets the configured criteria.
  901.  
  902. @method _onInputValueChange
  903. @param {EventFacade} e
  904. @protected
  905. **/
  906. _onInputValueChange: function (e) {
  907. var newVal = e.newVal;
  908.  
  909. // Don't query if the internal value is the same as the new value
  910. // reported by valueChange.
  911. if (newVal !== this.get(VALUE)) {
  912. this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
  913. }
  914. },
  915.  
  916. /**
  917. Handles source responses and fires the `results` event.
  918.  
  919. @method _onResponse
  920. @param {EventFacade} e
  921. @protected
  922. **/
  923. _onResponse: function (query, e) {
  924. // Ignore stale responses that aren't for the current query.
  925. if (query === (this.get(QUERY) || '')) {
  926. this._parseResponse(query || '', e.response, e.data);
  927. }
  928. },
  929.  
  930. // -- Protected Default Event Handlers -------------------------------------
  931.  
  932. /**
  933. Default `clear` event handler. Sets the `results` attribute to an empty
  934. array and `query` to null.
  935.  
  936. @method _defClearFn
  937. @protected
  938. **/
  939. _defClearFn: function () {
  940. this._set(QUERY, null);
  941. this._set(RESULTS, []);
  942. },
  943.  
  944. /**
  945. Default `query` event handler. Sets the `query` attribute and sends a
  946. request to the source if one is configured.
  947.  
  948. @method _defQueryFn
  949. @param {EventFacade} e
  950. @protected
  951. **/
  952. _defQueryFn: function (e) {
  953. Y.log('query: "' + e.query + '"; inputValue: "' + e.inputValue + '"', 'info', 'autocomplete-base');
  954. this.sendRequest(e.query); // sendRequest will set the 'query' attribute
  955. },
  956.  
  957. /**
  958. Default `results` event handler. Sets the `results` attribute to the latest
  959. results.
  960.  
  961. @method _defResultsFn
  962. @param {EventFacade} e
  963. @protected
  964. **/
  965. _defResultsFn: function (e) {
  966. Y.log('results: ' + Y.dump(e.results), 'info', 'autocomplete-base');
  967. this._set(RESULTS, e[RESULTS]);
  968. }
  969. };
  970.  
  971. AutoCompleteBase.ATTRS = {
  972. /**
  973. Whether or not to enable the browser's built-in autocomplete functionality
  974. for input fields.
  975.  
  976. @attribute allowBrowserAutocomplete
  977. @type Boolean
  978. @default false
  979. **/
  980. allowBrowserAutocomplete: {
  981. value: false
  982. },
  983.  
  984. /**
  985. When a `queryDelimiter` is set, trailing delimiters will automatically be
  986. stripped from the input value by default when the input node loses focus.
  987. Set this to `true` to allow trailing delimiters.
  988.  
  989. @attribute allowTrailingDelimiter
  990. @type Boolean
  991. @default false
  992. **/
  993. allowTrailingDelimiter: {
  994. value: false
  995. },
  996.  
  997. /**
  998. Whether or not to enable in-memory caching in result sources that support
  999. it.
  1000.  
  1001. @attribute enableCache
  1002. @type Boolean
  1003. @default true
  1004. @since 3.5.0
  1005. **/
  1006. enableCache: {
  1007. lazyAdd: false, // we need the setter to run on init
  1008. setter: '_setEnableCache',
  1009. value: true
  1010. },
  1011.  
  1012. /**
  1013. Node to monitor for changes, which will generate `query` events when
  1014. appropriate. May be either an `<input>` or a `<textarea>`.
  1015.  
  1016. @attribute inputNode
  1017. @type Node|HTMLElement|String
  1018. @initOnly
  1019. **/
  1020. inputNode: {
  1021. setter: Y.one,
  1022. writeOnce: 'initOnly'
  1023. },
  1024.  
  1025. /**
  1026. Maximum number of results to return. A value of `0` or less will allow an
  1027. unlimited number of results.
  1028.  
  1029. @attribute maxResults
  1030. @type Number
  1031. @default 0
  1032. **/
  1033. maxResults: {
  1034. value: 0
  1035. },
  1036.  
  1037. /**
  1038. Minimum number of characters that must be entered before a `query` event
  1039. will be fired. A value of `0` allows empty queries; a negative value will
  1040. effectively disable all `query` events.
  1041.  
  1042. @attribute minQueryLength
  1043. @type Number
  1044. @default 1
  1045. **/
  1046. minQueryLength: {
  1047. value: 1
  1048. },
  1049.  
  1050. /**
  1051. Current query, or `null` if there is no current query.
  1052.  
  1053. The query might not be the same as the current value of the input node, both
  1054. for timing reasons (due to `queryDelay`) and because when one or more
  1055. `queryDelimiter` separators are in use, only the last portion of the
  1056. delimited input string will be used as the query value.
  1057.  
  1058. @attribute query
  1059. @type String|null
  1060. @default null
  1061. @readonly
  1062. **/
  1063. query: {
  1064. readOnly: true,
  1065. value: null
  1066. },
  1067.  
  1068. /**
  1069. Number of milliseconds to delay after input before triggering a `query`
  1070. event. If new input occurs before this delay is over, the previous input
  1071. event will be ignored and a new delay will begin.
  1072.  
  1073. This can be useful both to throttle queries to a remote data source and to
  1074. avoid distracting the user by showing them less relevant results before
  1075. they've paused their typing.
  1076.  
  1077. @attribute queryDelay
  1078. @type Number
  1079. @default 100
  1080. **/
  1081. queryDelay: {
  1082. value: 100
  1083. },
  1084.  
  1085. /**
  1086. Query delimiter string. When a delimiter is configured, the input value
  1087. will be split on the delimiter, and only the last portion will be used in
  1088. autocomplete queries and updated when the `query` attribute is
  1089. modified.
  1090.  
  1091. @attribute queryDelimiter
  1092. @type String|null
  1093. @default null
  1094. **/
  1095. queryDelimiter: {
  1096. value: null
  1097. },
  1098.  
  1099. /**
  1100. Source request template. This can be a function that accepts a query as a
  1101. parameter and returns a request string, or it can be a string containing the
  1102. placeholder "{query}", which will be replaced with the actual URI-encoded
  1103. query. In either case, the resulting string will be appended to the request
  1104. URL when the `source` attribute is set to a remote DataSource, JSONP URL, or
  1105. XHR URL (it will not be appended to YQL URLs).
  1106.  
  1107. While `requestTemplate` may be set to either a function or a string, it will
  1108. always be returned as a function that accepts a query argument and returns a
  1109. string.
  1110.  
  1111. @attribute requestTemplate
  1112. @type Function|String|null
  1113. @default null
  1114. **/
  1115. requestTemplate: {
  1116. setter: '_setRequestTemplate',
  1117. value: null
  1118. },
  1119.  
  1120. /**
  1121. Array of local result filter functions. If provided, each filter will be
  1122. called with two arguments when results are received: the query and an array
  1123. of result objects. See the documentation for the `results` event for a list
  1124. of the properties available on each result object.
  1125.  
  1126. Each filter is expected to return a filtered or modified version of the
  1127. results array, which will then be passed on to subsequent filters, then the
  1128. `resultHighlighter` function (if set), then the `resultFormatter` function
  1129. (if set), and finally to subscribers to the `results` event.
  1130.  
  1131. If no `source` is set, result filters will not be called.
  1132.  
  1133. Prepackaged result filters provided by the autocomplete-filters and
  1134. autocomplete-filters-accentfold modules can be used by specifying the filter
  1135. name as a string, such as `'phraseMatch'` (assuming the necessary filters
  1136. module is loaded).
  1137.  
  1138. @attribute resultFilters
  1139. @type Array
  1140. @default []
  1141. **/
  1142. resultFilters: {
  1143. setter: '_setResultFilters',
  1144. value: []
  1145. },
  1146.  
  1147. /**
  1148. Function which will be used to format results. If provided, this function
  1149. will be called with two arguments after results have been received and
  1150. filtered: the query and an array of result objects. The formatter is
  1151. expected to return an array of HTML strings or Node instances containing the
  1152. desired HTML for each result.
  1153.  
  1154. See the documentation for the `results` event for a list of the properties
  1155. available on each result object.
  1156.  
  1157. If no `source` is set, the formatter will not be called.
  1158.  
  1159. @attribute resultFormatter
  1160. @type Function|null
  1161. **/
  1162. resultFormatter: {
  1163. validator: _FUNCTION_VALIDATOR,
  1164. value: null
  1165. },
  1166.  
  1167. /**
  1168. Function which will be used to highlight results. If provided, this function
  1169. will be called with two arguments after results have been received and
  1170. filtered: the query and an array of filtered result objects. The highlighter
  1171. is expected to return an array of highlighted result text in the form of
  1172. HTML strings.
  1173.  
  1174. See the documentation for the `results` event for a list of the properties
  1175. available on each result object.
  1176.  
  1177. If no `source` is set, the highlighter will not be called.
  1178.  
  1179. @attribute resultHighlighter
  1180. @type Function|null
  1181. **/
  1182. resultHighlighter: {
  1183. setter: '_setResultHighlighter',
  1184. value: null
  1185. },
  1186.  
  1187. /**
  1188. Locator that should be used to extract an array of results from a non-array
  1189. response.
  1190.  
  1191. By default, no locator is applied, and all responses are assumed to be
  1192. arrays by default. If all responses are already arrays, you don't need to
  1193. define a locator.
  1194.  
  1195. The locator may be either a function (which will receive the raw response as
  1196. an argument and must return an array) or a string representing an object
  1197. path, such as "foo.bar.baz" (which would return the value of
  1198. `result.foo.bar.baz` if the response is an object).
  1199.  
  1200. While `resultListLocator` may be set to either a function or a string, it
  1201. will always be returned as a function that accepts a response argument and
  1202. returns an array.
  1203.  
  1204. @attribute resultListLocator
  1205. @type Function|String|null
  1206. **/
  1207. resultListLocator: {
  1208. setter: '_setLocator',
  1209. value: null
  1210. },
  1211.  
  1212. /**
  1213. Current results, or an empty array if there are no results.
  1214.  
  1215. @attribute results
  1216. @type Array
  1217. @default []
  1218. @readonly
  1219. **/
  1220. results: {
  1221. readOnly: true,
  1222. value: []
  1223. },
  1224.  
  1225. /**
  1226. Locator that should be used to extract a plain text string from a non-string
  1227. result item. The resulting text value will typically be the value that ends
  1228. up being inserted into an input field or textarea when the user of an
  1229. autocomplete implementation selects a result.
  1230.  
  1231. By default, no locator is applied, and all results are assumed to be plain
  1232. text strings. If all results are already plain text strings, you don't need
  1233. to define a locator.
  1234.  
  1235. The locator may be either a function (which will receive the raw result as
  1236. an argument and must return a string) or a string representing an object
  1237. path, such as "foo.bar.baz" (which would return the value of
  1238. `result.foo.bar.baz` if the result is an object).
  1239.  
  1240. While `resultTextLocator` may be set to either a function or a string, it
  1241. will always be returned as a function that accepts a result argument and
  1242. returns a string.
  1243.  
  1244. @attribute resultTextLocator
  1245. @type Function|String|null
  1246. **/
  1247. resultTextLocator: {
  1248. setter: '_setLocator',
  1249. value: null
  1250. },
  1251.  
  1252. /**
  1253. Source for autocomplete results. The following source types are supported:
  1254.  
  1255. <dl>
  1256. <dt>Array</dt>
  1257. <dd>
  1258. <p>
  1259. The full array will be provided to any configured filters for each
  1260. query. This is an easy way to create a fully client-side autocomplete
  1261. implementation.
  1262. </p>
  1263.  
  1264. <p>
  1265. Example: `['first result', 'second result', 'etc']`
  1266. </p>
  1267. </dd>
  1268.  
  1269. <dt>DataSource</dt>
  1270. <dd>
  1271. A `DataSource` instance or other object that provides a DataSource-like
  1272. `sendRequest` method. See the `DataSource` documentation for details.
  1273. </dd>
  1274.  
  1275. <dt>Function</dt>
  1276. <dd>
  1277. <p>
  1278. A function source will be called with the current query and a
  1279. callback function as parameters, and should either return an array of
  1280. results (for synchronous operation) or return nothing and pass an
  1281. array of results to the provided callback (for asynchronous
  1282. operation).
  1283. </p>
  1284.  
  1285. <p>
  1286. Example (synchronous):
  1287. </p>
  1288.  
  1289. <pre>
  1290. function (query) {
  1291. return ['foo', 'bar'];
  1292. }
  1293. </pre>
  1294.  
  1295. <p>
  1296. Example (async):
  1297. </p>
  1298.  
  1299. <pre>
  1300. function (query, callback) {
  1301. callback(['foo', 'bar']);
  1302. }
  1303. </pre>
  1304. </dd>
  1305.  
  1306. <dt>Object</dt>
  1307. <dd>
  1308. <p>
  1309. An object will be treated as a query hashmap. If a property on the
  1310. object matches the current query, the value of that property will be
  1311. used as the response.
  1312. </p>
  1313.  
  1314. <p>
  1315. The response is assumed to be an array of results by default. If the
  1316. response is not an array, provide a `resultListLocator` to
  1317. process the response and return an array.
  1318. </p>
  1319.  
  1320. <p>
  1321. Example: `{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}`
  1322. </p>
  1323. </dd>
  1324. </dl>
  1325.  
  1326. If the optional `autocomplete-sources` module is loaded, then
  1327. the following additional source types will be supported as well:
  1328.  
  1329. <dl>
  1330. <dt>&lt;select&gt; Node</dt>
  1331. <dd>
  1332. You may provide a YUI Node instance wrapping a &lt;select&gt;
  1333. element, and the options in the list will be used as results. You
  1334. will also need to specify a `resultTextLocator` of 'text'
  1335. or 'value', depending on what you want to use as the text of the
  1336. result.
  1337.  
  1338. Each result will be an object with the following properties:
  1339.  
  1340. <dl>
  1341. <dt>html (String)</dt>
  1342. <dd>
  1343. <p>HTML content of the &lt;option&gt; element.</p>
  1344. </dd>
  1345.  
  1346. <dt>index (Number)</dt>
  1347. <dd>
  1348. <p>Index of the &lt;option&gt; element in the list.</p>
  1349. </dd>
  1350.  
  1351. <dt>node (Y.Node)</dt>
  1352. <dd>
  1353. <p>Node instance referring to the original &lt;option&gt; element.</p>
  1354. </dd>
  1355.  
  1356. <dt>selected (Boolean)</dt>
  1357. <dd>
  1358. <p>Whether or not this item is currently selected in the
  1359. &lt;select&gt; list.</p>
  1360. </dd>
  1361.  
  1362. <dt>text (String)</dt>
  1363. <dd>
  1364. <p>Text content of the &lt;option&gt; element.</p>
  1365. </dd>
  1366.  
  1367. <dt>value (String)</dt>
  1368. <dd>
  1369. <p>Value of the &lt;option&gt; element.</p>
  1370. </dd>
  1371. </dl>
  1372. </dd>
  1373.  
  1374. <dt>String (JSONP URL)</dt>
  1375. <dd>
  1376. <p>
  1377. If a URL with a `{callback}` placeholder is provided, it will be used to
  1378. make a JSONP request. The `{query}` placeholder will be replaced with
  1379. the current query, and the `{callback}` placeholder will be replaced
  1380. with an internally-generated JSONP callback name. Both placeholders must
  1381. appear in the URL, or the request will fail. An optional `{maxResults}`
  1382. placeholder may also be provided, and will be replaced with the value of
  1383. the maxResults attribute (or 1000 if the maxResults attribute is 0 or
  1384. less).
  1385. </p>
  1386.  
  1387. <p>
  1388. The response is assumed to be an array of results by default. If the
  1389. response is not an array, provide a `resultListLocator` to process the
  1390. response and return an array.
  1391. </p>
  1392.  
  1393. <p>
  1394. <strong>The `jsonp` module must be loaded in order for
  1395. JSONP URL sources to work.</strong> If the `jsonp` module
  1396. is not already loaded, it will be loaded on demand if possible.
  1397. </p>
  1398.  
  1399. <p>
  1400. Example: `'http://example.com/search?q={query}&callback={callback}'`
  1401. </p>
  1402. </dd>
  1403.  
  1404. <dt>String (XHR URL)</dt>
  1405. <dd>
  1406. <p>
  1407. If a URL without a `{callback}` placeholder is provided, it will be used
  1408. to make a same-origin XHR request. The `{query}` placeholder will be
  1409. replaced with the current query. An optional `{maxResults}` placeholder
  1410. may also be provided, and will be replaced with the value of the
  1411. maxResults attribute (or 1000 if the maxResults attribute is 0 or less).
  1412. </p>
  1413.  
  1414. <p>
  1415. The response is assumed to be a JSON array of results by default. If the
  1416. response is a JSON object and not an array, provide a
  1417. `resultListLocator` to process the response and return an array. If the
  1418. response is in some form other than JSON, you will need to use a custom
  1419. DataSource instance as the source.
  1420. </p>
  1421.  
  1422. <p>
  1423. <strong>The `io-base` and `json-parse` modules
  1424. must be loaded in order for XHR URL sources to work.</strong> If
  1425. these modules are not already loaded, they will be loaded on demand
  1426. if possible.
  1427. </p>
  1428.  
  1429. <p>
  1430. Example: `'http://example.com/search?q={query}'`
  1431. </p>
  1432. </dd>
  1433.  
  1434. <dt>String (YQL query)</dt>
  1435. <dd>
  1436. <p>
  1437. If a YQL query is provided, it will be used to make a YQL request. The
  1438. `{query}` placeholder will be replaced with the current autocomplete
  1439. query. This placeholder must appear in the YQL query, or the request
  1440. will fail. An optional `{maxResults}` placeholder may also be provided,
  1441. and will be replaced with the value of the maxResults attribute (or 1000
  1442. if the maxResults attribute is 0 or less).
  1443. </p>
  1444.  
  1445. <p>
  1446. <strong>The `yql` module must be loaded in order for YQL
  1447. sources to work.</strong> If the `yql` module is not
  1448. already loaded, it will be loaded on demand if possible.
  1449. </p>
  1450.  
  1451. <p>
  1452. Example: `'select * from search.suggest where query="{query}"'`
  1453. </p>
  1454. </dd>
  1455. </dl>
  1456.  
  1457. As an alternative to providing a source, you could simply listen for `query`
  1458. events and handle them any way you see fit. Providing a source is optional,
  1459. but will usually be simpler.
  1460.  
  1461. @attribute source
  1462. @type Array|DataSource|Function|Node|Object|String|null
  1463. **/
  1464. source: {
  1465. setter: '_setSource',
  1466. value: null
  1467. },
  1468.  
  1469. /**
  1470. May be used to force a specific source type, overriding the automatic source
  1471. type detection. It should almost never be necessary to do this, but as they
  1472. taught us in the Boy Scouts, one should always be prepared, so it's here if
  1473. you need it. Be warned that if you set this attribute and something breaks,
  1474. it's your own fault.
  1475.  
  1476. Supported `sourceType` values are: 'array', 'datasource', 'function', and
  1477. 'object'.
  1478.  
  1479. If the `autocomplete-sources` module is loaded, the following additional
  1480. source types are supported: 'io', 'jsonp', 'select', 'string', 'yql'
  1481.  
  1482. @attribute sourceType
  1483. @type String
  1484. **/
  1485. sourceType: {
  1486. value: null
  1487. },
  1488.  
  1489. /**
  1490. If the `inputNode` specified at instantiation time has a `node-tokeninput`
  1491. plugin attached to it, this attribute will be a reference to the
  1492. `Y.Plugin.TokenInput` instance.
  1493.  
  1494. @attribute tokenInput
  1495. @type Plugin.TokenInput
  1496. @readonly
  1497. **/
  1498. tokenInput: {
  1499. readOnly: true
  1500. },
  1501.  
  1502. /**
  1503. Current value of the input node.
  1504.  
  1505. @attribute value
  1506. @type String
  1507. @default ''
  1508. **/
  1509. value: {
  1510. // Why duplicate this._inputNode.get('value')? Because we need a
  1511. // reliable way to track the source of value changes. We want to perform
  1512. // completion when the user changes the value, but not when we change
  1513. // the value.
  1514. value: ''
  1515. }
  1516. };
  1517.  
  1518. // This tells Y.Base.create() to copy these static properties to any class
  1519. // AutoCompleteBase is mixed into.
  1520. AutoCompleteBase._buildCfg = {
  1521. aggregates: ['SOURCE_TYPES'],
  1522. statics : ['UI_SRC']
  1523. };
  1524.  
  1525. /**
  1526. Mapping of built-in source types to their setter functions. DataSource instances
  1527. and DataSource-like objects are handled natively, so are not mapped here.
  1528.  
  1529. @property SOURCE_TYPES
  1530. @type {Object}
  1531. @static
  1532. **/
  1533. AutoCompleteBase.SOURCE_TYPES = {
  1534. array : '_createArraySource',
  1535. 'function': '_createFunctionSource',
  1536. object : '_createObjectSource'
  1537. };
  1538.  
  1539. AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
  1540.  
  1541. Y.AutoCompleteBase = AutoCompleteBase;
  1542.