autocomplete-base.js revision 2c8eaddac21fee4fadf7f19ae7acb064a353a4d9
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp/**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Provides automatic input completion or suggestions for text input fields and
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * textareas.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp *
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @module autocomplete
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @since 3.3.0
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp */
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp/**
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <code>Y.Base</code> extension that provides core autocomplete logic (but no
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * UI implementation) for a text input field or textarea. Must be mixed into a
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <code>Y.Base</code>-derived class to be useful.
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @module autocomplete
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @submodule autocomplete-base
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp */
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp/**
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * <p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * Extension that provides core autocomplete logic (but no UI implementation)
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * for a text input field or textarea.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The <code>AutoCompleteBase</code> class provides events and attributes that
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * abstract away core autocomplete logic and configuration, but does not provide
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * a widget implementation or suggestion UI. For a prepackaged autocomplete
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * widget, see <code>AutoCompleteList</code>.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * This extension cannot be instantiated directly, since it doesn't provide an
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * actual implementation. It's intended to be mixed into a
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <code>Y.Base</code>-based class or widget.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <code>Y.Widget</code>-based example:
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <pre>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * YUI().use('autocomplete-base', 'widget', function (Y) {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * &nbsp;&nbsp;var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * &nbsp;&nbsp;&nbsp;&nbsp;// Custom prototype methods and properties.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * &nbsp;&nbsp;}, {
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;&nbsp;&nbsp;// Custom static methods and properties.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;});
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;// Custom implementation code.
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * });
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </pre>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <code>Y.Base</code>-based example:
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <pre>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * YUI().use('autocomplete-base', function (Y) {
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;&nbsp;&nbsp;initializer: function () {
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._bindUIACBase();
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._syncUIACBase();
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;&nbsp;&nbsp;},
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * &nbsp;
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;&nbsp;&nbsp;// Custom prototype methods and properties.
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * &nbsp;&nbsp;}, {
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;&nbsp;&nbsp;&nbsp;// Custom static methods and properties.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * &nbsp;&nbsp;});
a89ad754cce3cfc8aee71760e10217b54020360dTripp * &nbsp;
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp * &nbsp;&nbsp;// Custom implementation code.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * });
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </pre>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @class AutoCompleteBase
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp */
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTrippvar Lang = Y.Lang,
a89ad754cce3cfc8aee71760e10217b54020360dTripp YArray = Y.Array,
a89ad754cce3cfc8aee71760e10217b54020360dTripp YObject = Y.Object,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp isArray = Lang.isArray,
c093c1aed867e18aa4778708592e1ceb45d18cffTripp isFunction = Lang.isFunction,
a89ad754cce3cfc8aee71760e10217b54020360dTripp isObject = Lang.isObject,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp isString = Lang.isString,
a89ad754cce3cfc8aee71760e10217b54020360dTripp trim = Lang.trim,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp INVALID_VALUE = Y.Attribute.INVALID_VALUE,
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp _FUNCTION_VALIDATOR = '_functionValidator',
a89ad754cce3cfc8aee71760e10217b54020360dTripp _SOURCE_SUCCESS = '_sourceSuccess',
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp ALLOW_BROWSER_AC = 'allowBrowserAutocomplete',
a89ad754cce3cfc8aee71760e10217b54020360dTripp INPUT_NODE = 'inputNode',
a89ad754cce3cfc8aee71760e10217b54020360dTripp QUERY = 'query',
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp QUERY_DELIMITER = 'queryDelimiter',
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp REQUEST_TEMPLATE = 'requestTemplate',
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp RESULTS = 'results',
c093c1aed867e18aa4778708592e1ceb45d18cffTripp RESULT_LIST_LOCATOR = 'resultListLocator',
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp VALUE = 'value',
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp VALUE_CHANGE = 'valueChange',
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp EVT_CLEAR = 'clear',
a89ad754cce3cfc8aee71760e10217b54020360dTripp EVT_QUERY = QUERY,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp EVT_RESULTS = RESULTS;
a89ad754cce3cfc8aee71760e10217b54020360dTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTrippfunction AutoCompleteBase() {
a89ad754cce3cfc8aee71760e10217b54020360dTripp // AOP bindings.
a89ad754cce3cfc8aee71760e10217b54020360dTripp Y.before(this._bindUIACBase, this, 'bindUI');
a89ad754cce3cfc8aee71760e10217b54020360dTripp Y.before(this._destructorACBase, this, 'destructor');
a89ad754cce3cfc8aee71760e10217b54020360dTripp Y.before(this._syncUIACBase, this, 'syncUI');
a89ad754cce3cfc8aee71760e10217b54020360dTripp
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp // -- Public Events --------------------------------------------------------
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp /**
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * Fires after the query has been completely cleared or no longer meets the
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * minimum query length requirement.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @event clear
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @param {EventFacade} e Event facade with the following additional
a89ad754cce3cfc8aee71760e10217b54020360dTripp * properties:
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>prevVal (String)</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Value of the query before it was cleared.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @preventable _defClearFn
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp this.publish(EVT_CLEAR, {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp defaultFn: this._defClearFn,
c093c1aed867e18aa4778708592e1ceb45d18cffTripp queueable: true
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp });
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp /**
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Fires when the contents of the input field have changed and the input
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * value meets the criteria necessary to generate an autocomplete query.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @event query
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param {EventFacade} e Event facade with the following additional
bf801d6851ecf7ed14742ef3639a077daecb5cf8Tripp * properties:
a4d2446149b07f9e5c32947091dcbcf4d2eee765Tripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dl>
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * <dt>inputValue (String)</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Full contents of the text input field or textarea that generated
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * the query.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>query (String)</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Autocomplete query. This is the string that will be used to
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * request completion results. It may or may not be the same as
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <code>inputValue</code>.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @preventable _defQueryFn
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp this.publish(EVT_QUERY, {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp defaultFn: this._defQueryFn,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp queueable: true
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp });
c093c1aed867e18aa4778708592e1ceb45d18cffTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp /**
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Fires after query results are received from the <code>source</code>. If
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * source has been set, this event will not fire.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @event results
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @param {EventFacade} e Event facade with the following additional
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * properties:
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>data (Array|Object)</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Raw, unfiltered result data (if available).
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>query (String)</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Query that generated these results.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>results (Array)</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Array of filtered, formatted, and highlighted results. Each item in
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * the array is an object with the following properties:
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>display (Node|HTMLElement|String)</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Formatted result HTML suitable for display to the user.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>raw (mixed)</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Raw, unformatted result in whatever form it was provided by the
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <code>source</code>.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>text (String)</dt>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <dd>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Plain text version of the result, suitable for being inserted
a89ad754cce3cfc8aee71760e10217b54020360dTripp * into the value of a text input field or textarea when the result
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp * is selected by a user.
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * @preventable _defResultsFn
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp this.publish(EVT_RESULTS, {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp defaultFn: this._defResultsFn,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp queueable: true
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp });
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp}
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp// -- Public Static Properties -------------------------------------------------
66ca16dd76367c074fe4df1dcf7b555489a9bf85TrippAutoCompleteBase.ATTRS = {
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp /**
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * Whether or not to enable the browser's built-in autocomplete
c093c1aed867e18aa4778708592e1ceb45d18cffTripp * functionality for input fields.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp *
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @attribute allowBrowserAutocomplete
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp * @type Boolean
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp * @default false
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp */
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp allowBrowserAutocomplete: {
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp value: false
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp },
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp /**
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Node to monitor for changes, which will generate <code>query</code>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * events when appropriate. May be either an input field or a textarea.
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @attribute inputNode
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @type Node|HTMLElement|String
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @writeonce
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp */
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp inputNode: {
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp setter: Y.one,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp writeOnce: 'initOnly'
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp },
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp /**
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Maximum number of results to return. A value of <code>0</code> or less
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * will allow an unlimited number of results.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp *
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @attribute maxResults
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @type Number
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @default 0
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp maxResults: {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp value: 0
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp },
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp /**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Minimum number of characters that must be entered before a
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <code>query</code> event will be fired. A value of <code>0</code>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * allows empty queries; a negative value will effectively disable all
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <code>query</code> events.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
c790ee5b4dcc7d98b68c124495c76d7d515bdc8dTripp * @attribute minQueryLength
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type Number
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @default 1
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp minQueryLength: {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp value: 1
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp },
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp /**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * Current query, or <code>null</code> if there is no current query.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * The query might not be the same as the current value of the input
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * node, both for timing reasons (due to <code>queryDelay</code>) and
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * because when one or more <code>queryDelimiter</code> separators are
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * in use, only the last portion of the delimited input string will be
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * used as the query value.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @attribute query
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @type String|null
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @default null
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @readonly
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp */
a89ad754cce3cfc8aee71760e10217b54020360dTripp query: {
a89ad754cce3cfc8aee71760e10217b54020360dTripp readOnly: true,
a89ad754cce3cfc8aee71760e10217b54020360dTripp value: null
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp },
a89ad754cce3cfc8aee71760e10217b54020360dTripp
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp /**
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Number of milliseconds to delay after input before triggering a
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <code>query</code> event. If new input occurs before this delay is
a89ad754cce3cfc8aee71760e10217b54020360dTripp * over, the previous input event will be ignored and a new delay will
a89ad754cce3cfc8aee71760e10217b54020360dTripp * begin.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * This can be useful both to throttle queries to a remote data source
a89ad754cce3cfc8aee71760e10217b54020360dTripp * and to avoid distracting the user by showing them less relevant
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * results before they've paused their typing.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @attribute queryDelay
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @type Number
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @default 100
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp */
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp queryDelay: {
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp value: 100
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp },
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp /**
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Query delimiter string. When a delimiter is configured, the input value
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * will be split on the delimiter, and only the last portion will be used in
a89ad754cce3cfc8aee71760e10217b54020360dTripp * autocomplete queries and updated when the <code>query</code> attribute is
a89ad754cce3cfc8aee71760e10217b54020360dTripp * modified.
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @attribute queryDelimiter
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type String|null
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @default null
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp queryDelimiter: {
a89ad754cce3cfc8aee71760e10217b54020360dTripp value: null
a89ad754cce3cfc8aee71760e10217b54020360dTripp },
a89ad754cce3cfc8aee71760e10217b54020360dTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp /**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Source request template. This can be a function that accepts a query as a
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * parameter and returns a request string, or it can be a string containing
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * the placeholder "{query}", which will be replaced with the actual
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * URI-encoded query.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * While <code>requestTemplate</code> may be set to either a function or
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * a string, it will always be returned as a function that accepts a
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * query argument and returns a string.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @attribute requestTemplate
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Function|String|null
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * @default null
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp */
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp requestTemplate: {
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp setter: '_setRequestTemplate',
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp value: null
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp },
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp /**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Array of local result filter functions. If provided, each filter
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp * will be called with two arguments when results are received: the query
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * and an array of results (as returned by the <code>resultLocator</code>,
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * if one is set).
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * </p>
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp *
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp * <p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Each filter is expected to return a filtered or modified version of the
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * results, which will then be passed on to subsequent filters, then the
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <code>resultHighlighter</code> function (if set), then the
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <code>resultFormatter</code> function (if set), and finally to
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * subscribers to the <code>results</code> event.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * If no <code>source</code> is set, result filters will not be called.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @attribute resultFilters
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type Array
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @default []
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp resultFilters: {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp setter: '_setResultFilters',
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp value: []
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp },
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp /**
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Function which will be used to format results. If provided, this function
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * will be called with four arguments after results have been received and
a89ad754cce3cfc8aee71760e10217b54020360dTripp * filtered: the query, an array of raw results, an array of highlighted
a89ad754cce3cfc8aee71760e10217b54020360dTripp * results, and an array of plain text results. The formatter is expected to
a89ad754cce3cfc8aee71760e10217b54020360dTripp * return a modified copy of the results array with any desired custom
a89ad754cce3cfc8aee71760e10217b54020360dTripp * formatting applied.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * </p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * If no <code>source</code> is set, the formatter will not be called.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @attribute resultFormatter
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Function|null
a89ad754cce3cfc8aee71760e10217b54020360dTripp */
a89ad754cce3cfc8aee71760e10217b54020360dTripp resultFormatter: {
a89ad754cce3cfc8aee71760e10217b54020360dTripp validator: _FUNCTION_VALIDATOR
a89ad754cce3cfc8aee71760e10217b54020360dTripp },
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp /**
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Function which will be used to highlight results. If provided, this
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * function will be called with two arguments after results have been
a89ad754cce3cfc8aee71760e10217b54020360dTripp * received and filtered: the query and an array of filtered results. The
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * highlighter is expected to return a modified version of the results
a89ad754cce3cfc8aee71760e10217b54020360dTripp * array with the query highlighted in some form.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * If no <code>source</code> is set, the highlighter will not be called.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @attribute resultHighlighter
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @type Function|null
a89ad754cce3cfc8aee71760e10217b54020360dTripp */
a89ad754cce3cfc8aee71760e10217b54020360dTripp resultHighlighter: {
a89ad754cce3cfc8aee71760e10217b54020360dTripp setter: '_setResultHighlighter'
a89ad754cce3cfc8aee71760e10217b54020360dTripp },
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp /**
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Locator that should be used to extract an array of results from a
a89ad754cce3cfc8aee71760e10217b54020360dTripp * non-array response.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * By default, no locator is applied, and all responses are assumed to be
a89ad754cce3cfc8aee71760e10217b54020360dTripp * arrays by default. If all responses are already arrays, you don't need to
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * define a locator.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The locator may be either a function (which will receive the raw response
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * as an argument and must return an array) or a string representing an
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * object path, such as "foo.bar.baz" (which would return the value of
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <code>result.foo.bar.baz</code> if the response is an object).
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * While <code>resultListLocator</code> may be set to either a function or a
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * string, it will always be returned as a function that accepts a response
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * argument and returns an array.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @attribute resultListLocator
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type Function|String|null
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp resultListLocator: {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp setter: '_setLocator'
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp },
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp /**
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Current results, or an empty array if there are no results.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @attribute results
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type Array
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @default []
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @readonly
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp results: {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp readOnly: true,
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp value: []
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp },
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp /**
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * Locator that should be used to extract a plain text string from a
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * non-string result item. The resulting text value will be fed to any
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * defined filters, and will typically also be the value that ends up being
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * inserted into an input field or textarea when the user of an autocomplete
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * implementation selects a result.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * <p>
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * By default, no locator is applied, and all results are assumed to be
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp * plain text strings. If all results are already plain text strings, you
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * don't need to define a locator.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * </p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp *
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * <p>
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * The locator may be either a function (which will receive the raw result
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * as an argument and must return a string) or a string representing an
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * object path, such as "foo.bar.baz" (which would return the value of
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * <code>result.foo.bar.baz</code> if the result is an object).
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * </p>
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp *
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * <p>
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * While <code>resultTextLocator</code> may be set to either a function or a
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * string, it will always be returned as a function that accepts a result
6c4ec9d420df654d019b936fd06bef6f769db4cbTripp * argument and returns a string.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
b57ff76ab2ce5f3017d61855f13ed04ab46a965cTripp * @attribute resultTextLocator
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @type Function|String|null
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp */
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp resultTextLocator: {
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp setter: '_setLocator'
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp },
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp /**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * Source for autocomplete results. The following source types are
bbd1285cbb2183b7f89010412ad97ae1680b4b5eTripp * supported:
bbd1285cbb2183b7f89010412ad97ae1680b4b5eTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>Array</dt>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <i>Example:</i> <code>['first result', 'second result', 'etc']</code>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The full array will be provided to any configured filters for each
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * query. This is an easy way to create a fully client-side autocomplete
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * implementation.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <dt>DataSource</dt>
9eaaa502227248d304ac9170902697d02158c1d9Tripp * <dd>
9eaaa502227248d304ac9170902697d02158c1d9Tripp * <p>
9eaaa502227248d304ac9170902697d02158c1d9Tripp * A <code>DataSource</code> instance or other object that provides a
9eaaa502227248d304ac9170902697d02158c1d9Tripp * DataSource-like <code>sendRequest</code> method. See the
9eaaa502227248d304ac9170902697d02158c1d9Tripp * <code>DataSource</code> documentation for details.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * </dd>
4ef2f7e4cb7c7d255be077c47d542199f7bf8607Tripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>Object</dt>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
4ef2f7e4cb7c7d255be077c47d542199f7bf8607Tripp * <i>Example:</i> <code>{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}</code>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * <p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * An object will be treated as a query hashmap. If a property on the
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * object matches the current query, the value of that property will be
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * used as the response.
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * </p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp *
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The response is assumed to be an array of results by default. If the
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * response is not an array, provide a <code>resultListLocator</code> to
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * process the response and return an array.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * </p>
b79c07ef87dd1a48a03fc33a91c37d04f3addae2Tripp * </dd>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>String (JSONP URL)</dt>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <dd>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * <i>Example:</i> <code>'http://example.com/search?q={query}&callback={callback}'</code>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * </p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp *
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * <p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * If a URL is provided, it will be used to make a JSONP request. The
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * <code>{query}</code> placeholder will be replaced with the current
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * query, and the <code>{callback}</code> placeholder will be replaced with
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * an internally-generated JSONP callback name. Both placeholders must
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * appear in the URL, or the request will fail.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The response is assumed to be an array of results by default. If the
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * response is not an array, provide a <code>resultListLocator</code> to
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * process the response and return an array.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * <strong>The <code>jsonp</code> module must be loaded in order for URL
a89ad754cce3cfc8aee71760e10217b54020360dTripp * sources to work.</strong> If the <code>jsonp</code> module is not
a89ad754cce3cfc8aee71760e10217b54020360dTripp * already loaded, it will be loaded on demand if possible.
a89ad754cce3cfc8aee71760e10217b54020360dTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <dt>String (YQL query)</dt>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * <dd>
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <i>Example:</i> <code>'select * from search.suggest where query="{query}"'</code>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * If a YQL query is provided, it will be used to make a YQL request.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * The <code>{query}</code> placeholder will be replaced with the
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * current autocomplete query. This placeholder must appear in the YQL
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * query, or the request will fail.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <strong>The <code>yql</code> module must be loaded in order for YQL
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * sources to work.</strong> If the <code>yql</code> module is not
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * already loaded, it will be loaded on demand if possible.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dd>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </dl>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <p>
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * As an alternative to providing a source, you could also simply listen for
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * <code>query</code> events and handle them any way you see fit. Providing
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * a source is optional, but will usually be simpler.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * </p>
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp *
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @attribute source
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp * @type Array|DataSource|Object|String|null
e0caea9528bfbb244d27129aa9dea5aebc07fc18Tripp */
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp source: {
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp setter: '_setSource'
66ca16dd76367c074fe4df1dcf7b555489a9bf85Tripp },
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp /**
a89ad754cce3cfc8aee71760e10217b54020360dTripp * If the <code>inputNode</code> specified at instantiation time has a
a89ad754cce3cfc8aee71760e10217b54020360dTripp * <code>node-tokeninput</code> plugin attached to it, this attribute will
a89ad754cce3cfc8aee71760e10217b54020360dTripp * be a reference to the <code>Y.Plugin.TokenInput</code> instance.
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp *
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @attribute tokenInput
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type Plugin.TokenInput
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @readonly
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp tokenInput: {
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp readOnly: true
a89ad754cce3cfc8aee71760e10217b54020360dTripp },
a89ad754cce3cfc8aee71760e10217b54020360dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp /**
a89ad754cce3cfc8aee71760e10217b54020360dTripp * Current value of the input node.
a89ad754cce3cfc8aee71760e10217b54020360dTripp *
a89ad754cce3cfc8aee71760e10217b54020360dTripp * @attribute value
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @type String
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp * @default ''
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp */
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp value: {
a89ad754cce3cfc8aee71760e10217b54020360dTripp // Why duplicate this._inputNode.get('value')? Because we need a
a89ad754cce3cfc8aee71760e10217b54020360dTripp // reliable way to track the source of value changes. We want to perform
a89ad754cce3cfc8aee71760e10217b54020360dTripp // completion when the user changes the value, but not when we change
a89ad754cce3cfc8aee71760e10217b54020360dTripp // the value.
7947db4b7d8682ea81598e3a4283e659a8103be6Tripp value: ''
c7ba96d16d58075a9ab8d5c1e46c6c83ce11cb4eTripp },
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp
a89ad754cce3cfc8aee71760e10217b54020360dTripp /**
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp * URL protocol to use when the <code>source</code> is set to a YQL query.
09688ec5ffb8b9cf9883a770e2f9ebd60b28888dTripp *
* @attribute yqlProtocol
* @type String
* @default 'http'
*/
yqlProtocol: {
value: 'http'
}
};
AutoCompleteBase.CSS_PREFIX = 'ac';
AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
AutoCompleteBase.prototype = {
// -- Public Prototype Methods ---------------------------------------------
/**
* <p>
* Sends a request to the configured source. If no source is configured,
* this method won't do anything.
* </p>
*
* <p>
* Usually there's no reason to call this method manually; it will be
* called automatically when user input causes a <code>query</code> event to
* be fired. The only time you'll need to call this method manually is if
* you want to force a request to be sent when no user input has occurred.
* </p>
*
* @method sendRequest
* @param {String} query (optional) Query to send. If specified, the
* <code>query</code> attribute will be set to this query. If not
* specified, the current value of the <code>query</code> attribute will
* be used.
* @param {Function} requestTemplate (optional) Request template function.
* If not specified, the current value of the <code>requestTemplate</code>
* attribute will be used.
* @chainable
*/
sendRequest: function (query, requestTemplate) {
var request,
source = this.get('source');
if (source) {
if (query || query === '') {
this._set(QUERY, query);
} else {
query = this.get(QUERY);
}
if (!requestTemplate) {
requestTemplate = this.get(REQUEST_TEMPLATE);
}
request = requestTemplate ? requestTemplate(query) : query;
Y.log('sendRequest: ' + request, 'info', 'autocomplete-base');
source.sendRequest({
request: request,
callback: {
success: Y.bind(this._onResponse, this, query)
}
});
}
return this;
},
// -- Protected Lifecycle Methods ------------------------------------------
/**
* Attaches event listeners and behaviors.
*
* @method _bindUIACBase
* @protected
*/
_bindUIACBase: function () {
var inputNode = this.get(INPUT_NODE),
tokenInput = inputNode && inputNode.tokenInput;
// If the inputNode has a node-tokeninput plugin attached, bind to the
// plugin's inputNode instead.
if (tokenInput) {
inputNode = tokenInput.get(INPUT_NODE);
this._set('tokenInput', tokenInput);
}
if (!inputNode) {
Y.error('No inputNode specified.');
return;
}
this._inputNode = inputNode;
this._acBaseEvents = [
// This is the valueChange event on the inputNode, provided by the
// event-valuechange module, not our own valueChange.
inputNode.on(VALUE_CHANGE, this._onInputValueChange, this),
this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
this.after(VALUE_CHANGE, this._afterValueChange)
];
},
/**
* Detaches AutoCompleteBase event listeners.
*
* @method _destructorACBase
* @protected
*/
_destructorACBase: function () {
var events = this._acBaseEvents;
while (events && events.length) {
events.pop().detach();
}
},
/**
* Synchronizes the UI state of the <code>inputNode</code>.
*
* @method _syncUIACBase
* @protected
*/
_syncUIACBase: function () {
this._syncBrowserAutocomplete();
this.set(VALUE, this.get(INPUT_NODE).get(VALUE),
{src: AutoCompleteBase.UI_SRC});
},
// -- Protected Prototype Methods ------------------------------------------
/**
* Creates a DataSource-like object that simply returns the specified array
* as a response. See the <code>source</code> attribute for more details.
*
* @method _createArraySource
* @param {Array} source
* @return {Object} DataSource-like object.
* @protected
*/
_createArraySource: function (source) {
var that = this;
return {sendRequest: function (request) {
that[_SOURCE_SUCCESS](source.concat(), request);
}};
},
/**
* Creates a DataSource-like object that uses the specified JSONPRequest
* instance as a source. See the <code>source</code> attribute for more
* details.
*
* @method _createJSONPSource
* @param {JSONPRequest} source
* @return {Object} DataSource-like object.
* @protected
*/
_createJSONPSource: function (source) {
var cache = {},
jsonpSource = {},
that = this,
lastRequest, loading;
jsonpSource.sendRequest = function (request) {
var _sendRequest = function (request) {
var query = request.request;
if (cache[query]) {
that[_SOURCE_SUCCESS](cache[query], request);
} else {
// Hack alert: JSONPRequest currently doesn't support
// per-request callbacks, so we're reaching into the protected
// _config object to make it happen.
//
// This limitation is mentioned in the following JSONP
// enhancement ticket:
//
// http://yuilibrary.com/projects/yui3/ticket/2529371
source._config.on.success = function (data) {
cache[query] = data;
that[_SOURCE_SUCCESS](data, request);
};
source.send(query);
}
};
// Keep track of the most recent request in case there are multiple
// requests while we're waiting for the JSONP module to load. Only
// the most recent request will be sent.
lastRequest = request;
if (!loading) {
loading = true;
// Lazy-load the JSONP module if necessary, then overwrite the
// sendRequest method to bypass this check in the future.
Y.use('jsonp', function () {
// Turn the source into a JSONPRequest instance if it isn't
// one already.
if (!(source instanceof Y.JSONPRequest)) {
source = new Y.JSONPRequest(source, {
format: Y.bind(that._jsonpFormatter, that)
});
}
jsonpSource.sendRequest = _sendRequest;
_sendRequest(lastRequest);
});
}
};
return jsonpSource;
},
/**
* Creates a DataSource-like object that looks up queries as properties on
* the specified object, and returns the found value (if any) as a response.
* See the <code>source</code> attribute for more details.
*
* @method _createObjectSource
* @param {Object} source
* @return {Object} DataSource-like object.
* @protected
*/
_createObjectSource: function (source) {
return {sendRequest: function (request) {
var query = request.request,
that = this;
that[_SOURCE_SUCCESS](
YObject.owns(source, query) ? source[query] : [],
request
);
}};
},
/**
* Creates a DataSource-like object that calls the specified JSONP
* URL or executes the specified YQL query for results. If the string starts
* with "select ", "use ", or "set " (case-insensitive), it's assumed to be
* a YQL query; otherwise, it's assumed to be a URL (which may be absolute
* or relative). See the <code>source</code> attribute for more details.
*
* @method _createStringSource
* @param {String} source JSONP URL or YQL query.
* @return {Object} DataSource-like object.
* @protected
*/
_createStringSource: function (source) {
if (/^(?:select|use|set)\s+/i.test(source)) {
// Looks like a YQL query.
return this._createYQLSource(source);
} else {
// Doesn't look like a YQL query, so assume it's a URL.
return this._createJSONPSource(source);
}
},
/**
* Creates a DataSource-like object that uses the specified YQL query string
* to create a YQL-based source. See the <code>source</code> attribute for
* details. If no <code>resultListLocator</code> is defined, this method
* will set a best-guess locator that might work for many typical YQL
* queries.
*
* @method _createYQLSource
* @param {String} source YQL query.
* @return {Object} DataSource-like object.
* @protected
*/
_createYQLSource: function (source) {
var cache = {},
yqlSource = {},
that = this,
lastRequest, loading;
if (!this.get(RESULT_LIST_LOCATOR)) {
this.set(RESULT_LIST_LOCATOR, this._defaultYQLLocator);
}
yqlSource.sendRequest = function (request) {
var yqlRequest,
_sendRequest = function (request) {
var query = request.request,
callback, opts, yqlQuery;
if (cache[query]) {
that[_SOURCE_SUCCESS](cache[query], request);
} else {
callback = function (data) {
cache[query] = data;
that[_SOURCE_SUCCESS](data, request);
};
opts = {proto: that.get('yqlProtocol')};
yqlQuery = Lang.sub(source, {query: query});
// Only create a new YQLRequest instance if this is the
// first request. For subsequent requests, we'll reuse the
// original instance.
if (yqlRequest) {
yqlRequest._callback = callback;
yqlRequest._opts = opts;
yqlRequest._params.q = yqlQuery;
} else {
yqlRequest = new Y.YQLRequest(yqlQuery, callback, null, opts);
}
yqlRequest.send();
}
};
// Keep track of the most recent request in case there are multiple
// requests while we're waiting for the YQL module to load. Only the
// most recent request will be sent.
lastRequest = request;
if (!loading) {
// Lazy-load the YQL module if necessary, then overwrite the
// sendRequest method to bypass this check in the future.
loading = true;
Y.use('yql', function () {
yqlSource.sendRequest = _sendRequest;
_sendRequest(lastRequest);
});
}
};
return yqlSource;
},
/**
* Default resultListLocator used when a string-based YQL source is set and
* the implementer hasn't already specified one.
*
* @method _defaultYQLLocator
* @param {Object} response YQL response object.
* @return {Array}
* @protected
*/
_defaultYQLLocator: function (response) {
var results = response && response.query && response.query.results,
values;
if (results && isObject(results)) {
// If there's only a single value on YQL's results object, that
// value almost certainly contains the array of results we want. If
// there are 0 or 2+ values, then the values themselves are most
// likely the results we want.
values = YObject.values(results) || [];
results = values.length === 1 ? values[0] : values;
if (!isArray(results)) {
results = [results];
}
} else {
results = [];
}
return results;
},
/**
* Returns <code>true</code> if <i>value</i> is either a function or
* <code>null</code>.
*
* @method _functionValidator
* @param {Function|null} value Value to validate.
* @protected
*/
_functionValidator: function (value) {
return value === null || isFunction(value);
},
/**
* Faster and safer alternative to Y.Object.getValue(). Doesn't bother
* casting the path to an array (since we already know it's an array) and
* doesn't throw an error if a value in the middle of the object hierarchy
* is neither <code>undefined</code> nor an object.
*
* @method _getObjectValue
* @param {Object} obj
* @param {Array} path
* @return {mixed} Located value, or <code>undefined</code> if the value was
* not found at the specified path.
* @protected
*/
_getObjectValue: function (obj, path) {
if (!obj) {
return;
}
for (var i = 0, len = path.length; obj && i < len; i++) {
obj = obj[path[i]];
}
return obj;
},
/**
* URL formatter passed to <code>JSONPRequest</code> instances.
*
* @method _jsonpFormatter
* @param {String} url
* @param {String} proxy
* @param {String} query
* @return {String} Formatted URL
* @protected
*/
_jsonpFormatter: function (url, proxy, query) {
var requestTemplate = this.get(REQUEST_TEMPLATE);
if (requestTemplate) {
url = url + requestTemplate(query);
}
return Lang.sub(url, {
callback: proxy,
// If a requestTemplate is set, assume that it will
// handle URI encoding if necessary. Otherwise,
// encode the query.
query: requestTemplate ? query : encodeURIComponent(query)
});
},
/**
* Parses result responses, performs filtering and highlighting, and fires
* the <code>results</code> event.
*
* @method _parseResponse
* @param {String} query Query that generated these results.
* @param {Object} response Response containing results.
* @param {Object} data Raw response data.
* @protected
*/
_parseResponse: function (query, response, data) {
var facade = {
data : data,
query : query,
results: []
},
// Filtered result arrays representing different formats. These will
// be unrolled into the final array of result objects as properties.
formatted, // HTML, Nodes, whatever
raw, // whatever format came back in the response
unformatted, // plain text (ideally)
// Unfiltered raw results, fresh from the response.
unfiltered = response && response.results,
// Final array of result objects.
results = [],
// Other stuff.
filters,
formatter,
highlighter,
i,
len,
listLocator = this.get(RESULT_LIST_LOCATOR),
maxResults,
textLocator,
textLocatorMap;
if (unfiltered && listLocator) {
unfiltered = listLocator(unfiltered);
}
if (unfiltered) {
filters = this.get('resultFilters');
formatter = this.get('resultFormatter');
highlighter = this.get('resultHighlighter');
maxResults = this.get('maxResults');
textLocator = this.get('resultTextLocator');
if (textLocator) {
// In order to allow filtering based on locator queries, we have
// to create a mapping of "located" results to original results
// so we can sync up the original results later without
// requiring the filters to do extra work.
raw = YArray.map(unfiltered, textLocator);
textLocatorMap = YArray.hash(raw, unfiltered);
} else {
raw = unfiltered;
}
// Run the raw results through all configured result filters.
for (i = 0, len = filters.length; i < len; ++i) {
raw = filters[i](query, raw);
if (!raw || !raw.length) {
break;
}
}
if (textLocator) {
// Sync up the original results with the filtered, "located"
// results.
unformatted = raw;
raw = [];
for (i = 0, len = unformatted.length; i < len; ++i) {
raw.push(textLocatorMap[unformatted[i]]);
}
} else {
unformatted = [].concat(raw);
}
// Run the unformatted results through the configured highlighter
// (if any) to produce the first stage of formatted results.
formatted = highlighter ? highlighter(query, unformatted) :
[].concat(unformatted);
// Run the highlighted results through the configured formatter (if
// any) to produce the final formatted results.
if (formatter) {
formatted = formatter(query, raw, formatted, unformatted);
}
// Finally, unroll all the result arrays into a single array of
// result objects.
len = maxResults > 0 ? Math.min(maxResults, formatted.length) :
formatted.length;
for (i = 0; i < len; ++i) {
results[i] = {
display: formatted[i],
raw : raw[i],
text : unformatted[i]
};
}
facade.results = results;
}
this.fire(EVT_RESULTS, facade);
},
/**
* <p>
* Returns the query portion of the specified input value, or
* <code>null</code> if there is no suitable query within the input value.
* </p>
*
* <p>
* If a query delimiter is defined, the query will be the last delimited
* part of of the string.
* </p>
*
* @method _parseValue
* @param {String} value Input value from which to extract the query.
* @return {String|null} query
* @protected
*/
_parseValue: function (value) {
var delim = this.get(QUERY_DELIMITER);
if (delim) {
value = value.split(delim);
value = value[value.length - 1];
}
return Lang.trimLeft(value);
},
/**
* Setter for locator attributes.
*
* @method _setLocator
* @param {Function|String|null} locator
* @return {Function|null}
* @protected
*/
_setLocator: function (locator) {
if (this[_FUNCTION_VALIDATOR](locator)) {
return locator;
}
var that = this;
locator = locator.toString().split('.');
return function (result) {
return result && that._getObjectValue(result, locator);
};
},
/**
* Setter for the <code>requestTemplate</code> attribute.
*
* @method _setRequestTemplate
* @param {Function|String|null} template
* @return {Function|null}
* @protected
*/
_setRequestTemplate: function (template) {
if (this[_FUNCTION_VALIDATOR](template)) {
return template;
}
template = template.toString();
return function (query) {
return Lang.sub(template, {query: encodeURIComponent(query)});
};
},
/**
* Setter for the <code>resultFilters</code> attribute.
*
* @method _setResultFilters
* @param {Array|Function|String|null} filters <code>null</code>, a filter
* function, an array of filter functions, or a string or array of strings
* representing the names of methods on
* <code>Y.AutoCompleteFilters</code>.
* @return {Array} Array of filter functions (empty if <i>filters</i> is
* <code>null</code>).
* @protected
*/
_setResultFilters: function (filters) {
var acFilters, getFilterFunction;
if (filters === null) {
return [];
}
acFilters = Y.AutoCompleteFilters;
getFilterFunction = function (filter) {
if (isFunction(filter)) {
return filter;
}
if (isString(filter) && acFilters &&
isFunction(acFilters[filter])) {
return acFilters[filter];
}
return false;
};
if (isArray(filters)) {
filters = YArray.map(filters, getFilterFunction);
return YArray.every(filters, function (f) { return !!f; }) ?
filters : INVALID_VALUE;
} else {
filters = getFilterFunction(filters);
return filters ? [filters] : INVALID_VALUE;
}
},
/**
* Setter for the <code>resultHighlighter</code> attribute.
*
* @method _setResultHighlighter
* @param {Function|String|null} highlighter <code>null</code>, a
* highlighter function, or a string representing the name of a method on
* <code>Y.AutoCompleteHighlighters</code>.
* @return {Function|null}
* @protected
*/
_setResultHighlighter: function (highlighter) {
var acHighlighters;
if (this._functionValidator(highlighter)) {
return highlighter;
}
acHighlighters = Y.AutoCompleteHighlighters;
if (isString(highlighter) && acHighlighters &&
isFunction(acHighlighters[highlighter])) {
return acHighlighters[highlighter];
}
return INVALID_VALUE;
},
/**
* Setter for the <code>source</code> attribute. Returns a DataSource or
* a DataSource-like object depending on the type of <i>source</i>.
*
* @method _setSource
* @param {Array|DataSource|Object|String} source AutoComplete source. See
* the <code>source</code> attribute for details.
* @return {DataSource|Object}
* @protected
*/
_setSource: function (source) {
if ((source && isFunction(source.sendRequest)) || source === null) {
// Quacks like a DataSource instance (or null). Make it so!
return source;
} else if (isString(source)) {
// Assume the string is a JSONP URL or a YQL query.
return this._createStringSource(source);
} else if (isArray(source)) {
// Wrap the array in a teensy tiny fake DataSource that just returns
// the array itself for each request. Filters will do the rest.
return this._createArraySource(source);
} else if (isObject(source)) {
// Wrap the object in a teensy tiny fake DataSource that looks for
// the request as a property on the object and returns it if it
// exists, or an empty array otherwise.
return this._createObjectSource(source);
} else if (Y.JSONPRequest && source instanceof Y.JSONPRequest) {
return this._createJSONPSource(source);
}
return INVALID_VALUE;
},
/**
* Shared success callback for non-DataSource sources.
*
* @method _sourceSuccess
* @param {mixed} data Response data.
* @param {Object} request Request object.
* @protected
*/
_sourceSuccess: function (data, request) {
request.callback.success({
data: data,
response: {results: data}
});
},
/**
* Synchronizes the UI state of the <code>allowBrowserAutocomplete</code>
* attribute.
*
* @method _syncBrowserAutocomplete
* @protected
*/
_syncBrowserAutocomplete: function () {
var inputNode = this.get(INPUT_NODE);
if (inputNode.get('nodeName').toLowerCase() === 'input') {
inputNode.setAttribute('autocomplete',
this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
}
},
/**
* <p>
* Updates the query portion of the <code>value</code> attribute.
* </p>
*
* <p>
* If a query delimiter is defined, the last delimited portion of the input
* value will be replaced with the specified <i>value</i>.
* </p>
*
* @method _updateValue
* @param {String} newVal New value.
* @protected
*/
_updateValue: function (newVal) {
var delim = this.get(QUERY_DELIMITER),
insertDelim,
len,
prevVal;
newVal = Lang.trimLeft(newVal);
if (delim) {
insertDelim = trim(delim); // so we don't double up on spaces
prevVal = YArray.map(trim(this.get(VALUE)).split(delim), trim);
len = prevVal.length;
if (len > 1) {
prevVal[len - 1] = newVal;
newVal = prevVal.join(insertDelim + ' ');
}
newVal = newVal + insertDelim + ' ';
}
this.set(VALUE, newVal);
},
// -- Protected Event Handlers ---------------------------------------------
/**
* Handles change events for the <code>value</code> attribute.
*
* @method _afterValueChange
* @param {EventFacade} e
* @protected
*/
_afterValueChange: function (e) {
var delay,
fire,
newVal = e.newVal,
query,
that;
// Don't query on value changes that didn't come from the user.
if (e.src !== AutoCompleteBase.UI_SRC) {
this._inputNode.set(VALUE, newVal);
return;
}
Y.log('valueChange: new: "' + newVal + '"; old: "' + e.prevVal + '"', 'info', 'autocomplete-base');
query = this._parseValue(newVal) || '';
if (query.length >= this.get('minQueryLength')) {
delay = this.get('queryDelay');
that = this;
fire = function () {
that.fire(EVT_QUERY, {
inputValue: newVal,
query : query
});
};
if (delay) {
clearTimeout(this._delay);
this._delay = setTimeout(fire, delay);
} else {
fire();
}
} else {
clearTimeout(this._delay);
this.fire(EVT_CLEAR);
}
},
/**
* Handles <code>valueChange</code> events on the input node and fires a
* <code>query</code> event when the input value meets the configured
* criteria.
*
* @method _onInputValueChange
* @param {EventFacade} e
* @protected
*/
_onInputValueChange: function (e) {
var newVal = e.newVal;
// Don't query if the internal value is the same as the new value
// reported by valueChange.
if (newVal === this.get(VALUE)) {
return;
}
this.set(VALUE, newVal, {src: AutoCompleteBase.UI_SRC});
},
/**
* Handles source responses and fires the <code>results</code> event.
*
* @method _onResponse
* @param {EventFacade} e
* @protected
*/
_onResponse: function (query, e) {
// Ignore stale responses that aren't for the current query.
if (query === this.get(QUERY)) {
this._parseResponse(query, e.response, e.data);
}
},
// -- Protected Default Event Handlers -------------------------------------
/**
* Default <code>clear</code> event handler. Sets the <code>results</code>
* property to an empty array and <code>query</code> to null.
*
* @method _defClearFn
* @protected
*/
_defClearFn: function () {
this._set(QUERY, null);
this._set(RESULTS, []);
},
/**
* Default <code>query</code> event handler. Sets the <code>query</code>
* property and sends a request to the source if one is configured.
*
* @method _defQueryFn
* @param {EventFacade} e
* @protected
*/
_defQueryFn: function (e) {
var query = e.query;
Y.log('query: "' + query + '"; inputValue: "' + e.inputValue + '"', 'info', 'autocomplete-base');
this.sendRequest(query); // sendRequest will set the 'query' attribute
},
/**
* Default <code>results</code> event handler. Sets the <code>results</code>
* property to the latest results.
*
* @method _defResultsFn
* @param {EventFacade} e
* @protected
*/
_defResultsFn: function (e) {
Y.log('results: ' + Y.dump(e.results), 'info', 'autocomplete-base');
this._set(RESULTS, e[RESULTS]);
}
};
Y.AutoCompleteBase = AutoCompleteBase;