autocomplete-base.js revision b5554435260964a5bbdcd74ab7f7d541759bd7a3
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly/**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly * Extension that provides core autocomplete logic for a text input field or
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * textarea.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @module autocomplete
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @submodule autocomplete-base
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith/**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Extension that provides core autocomplete logic for a text input field or
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * textarea.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * This extension cannot be instantiated directly, since it doesn't provide an
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * actual implementation. It provides the core logic used by the
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <code>AutoComplete</code> class, and you can mix it into a custom class as
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * follows if you'd like to create a customized autocomplete implementation:
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <pre>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * YUI().use('autocomplete-base', 'base', function (Y) {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;var MyAutoComplete = Y.Base.create('myAutocomplete', Y.Base, [Y.AutoComplete], {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;&nbsp;&nbsp;initializer: function () {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._bindInput();
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._syncInput();
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;&nbsp;&nbsp;},
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;&nbsp;&nbsp;destructor: function () {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;this._unbindInput();
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly * &nbsp;&nbsp;&nbsp;&nbsp;}
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;});
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * &nbsp;&nbsp;// ... custom implementation code ...
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * });
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </pre>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @class AutoCompleteBase
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smithvar Lang = Y.Lang,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith isFunction = Lang.isFunction,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith isNumber = Lang.isNumber,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith ALLOW_BROWSER_AC = 'allowBrowserAutocomplete',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith DATA_SOURCE = 'dataSource',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith MIN_QUERY_LENGTH = 'minQueryLength',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith QUERY = 'query',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith QUERY_DELAY = 'queryDelay',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith REQUEST_TEMPLATE = 'requestTemplate',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith RESULT_FILTERS = 'resultFilters',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith RESULT_HIGHLIGHTER = 'resultHighlighter',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith VALUE_CHANGE = 'valueChange',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith EVT_CLEAR = 'clear',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith EVT_QUERY = QUERY,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith EVT_RESULTS = 'results',
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith EVT_VALUE_CHANGE = VALUE_CHANGE;
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smithfunction AutoCompleteBase() {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Fires after the contents of the input field have been completely cleared.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @event clear
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @param {EventFacade} e Event facade with the following additional
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * properties:
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dl>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dt>prevVal (String)</dt>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Value of the input node before it was cleared.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dl>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith this.publish(EVT_CLEAR, {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith preventable: false
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith });
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Fires when the contents of the input field have changed and the input
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * value meets the criteria necessary to generate an autocomplete query.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @event query
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @param {EventFacade} e Event facade with the following additional
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * properties:
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dl>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dt>inputValue (String)</dt>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Full contents of the text input field or textarea that generated
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * the query.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dt>query (String)</dt>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Autocomplete query. This is the string that will be used to
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * request completion results. It may or may not be the same as
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <code>inputValue</code>.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dl>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @preventable _defQueryFn
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith this.publish(EVT_QUERY, {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith defaultFn: this._defQueryFn,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith queueable: true
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith });
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Fires after query results are received from the DataSource. If no
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * DataSource has been set, this event will not fire.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @event results
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @param {EventFacade} e Event facade with the following additional
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * properties:
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dl>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dt>data (Array|Object)</dt>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Raw, unfiltered result data (if available).
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dt>query (String)</dt>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Query that generated these results.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dt>results (Array|Object)</dt>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Normalized and filtered result data returned from the DataSource.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dl>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith this.publish(EVT_RESULTS, {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith queueable: true
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly });
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Fires after the input node's value changes, and before the
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <code>query</code> event.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @event valueChange
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @param {EventFacade} e Event facade with the following additional
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * properties:
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dl>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dt>newVal (String)</dt>
8392a541bf432ed8d6e1985b6306b83dc898768dJenny Han Donnelly * <dd>
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly * Value of the input node after the change.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dt>prevVal (String)</dt>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Value of the input node prior to the change.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dd>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </dl>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith this.publish(EVT_VALUE_CHANGE, {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith preventable: false
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith });
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly}
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith// -- Public Static Properties -------------------------------------------------
3f8c8f56d68f749e363b10328b2a5c97a5c96863Jenny DonnellyAutoCompleteBase.ATTRS = {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
b3558e978532e4905c7b41b2d0821948ac047dbbLuke Smith * Whether or not to enable the browser's built-in autocomplete
b3558e978532e4905c7b41b2d0821948ac047dbbLuke Smith * functionality for input fields.
b3558e978532e4905c7b41b2d0821948ac047dbbLuke Smith *
b3558e978532e4905c7b41b2d0821948ac047dbbLuke Smith * @attribute allowBrowserAutocomplete
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @type Boolean
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @default false
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @writeonce
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith allowBrowserAutocomplete: {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith validator: Lang.isBoolean,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith value: false,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith writeOnce: 'initOnly'
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * DataSource object which will be used to make queries. This can be
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * an actual DataSource instance or any other object with a
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * sendRequest() method that has the same signature as DataSource's
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * sendRequest() method.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * As an alternative to providing a DataSource, you could listen for
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <code>query</code> events and handle them any way you see fit.
d3ac39f387c773c28001a1323aa85082051965aemattsnider * Providing a DataSource or DataSource-like object is optional, but
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * will often be simpler.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @attribute dataSource
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @type DataSource|Object|null
d3ac39f387c773c28001a1323aa85082051965aemattsnider */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith dataSource: {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith validator: function (value) {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith return (value && isFunction(value.sendRequest)) || value === null;
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith }
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Node to monitor for changes, which will generate <code>query</code>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * events when appropriate. May be either an input field or a textarea.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @attribute inputNode
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @type Node|HTMLElement|String
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @writeonce
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith inputNode: {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith setter: Y.one,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith writeOnce: 'initOnly'
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Minimum number of characters that must be entered before a
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <code>query</code> event will be fired. A value of <code>0</code>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * allows empty queries; a negative value will effectively disable all
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <code>query</code> events.
3f8c8f56d68f749e363b10328b2a5c97a5c96863Jenny Donnelly *
3f8c8f56d68f749e363b10328b2a5c97a5c96863Jenny Donnelly * @attribute minQueryLength
d3ac39f387c773c28001a1323aa85082051965aemattsnider * @type Number
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @default 1
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith minQueryLength: {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith validator: isNumber,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith value: 1
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Current query, or <code>null</code> if there is no current query.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * The query might not be the same as the current value of the input
3f8c8f56d68f749e363b10328b2a5c97a5c96863Jenny Donnelly * node, both for timing reasons (due to <code>queryDelay</code>) and
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly * because when one or more <code>queryDelimiter</code> separators are
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * in use, only the last portion of the delimited input string will be
8392a541bf432ed8d6e1985b6306b83dc898768dJenny Han Donnelly * used as the query value.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @attribute query
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @type String|null
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @default null
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @readonly
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith query: {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith readOnly: true,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith value: null
d3ac39f387c773c28001a1323aa85082051965aemattsnider },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Number of milliseconds to delay after input before triggering a
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <code>query</code> event. If new input occurs before this delay is
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * over, the previous input event will be ignored and a new delay will
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * begin.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * This can be useful both to throttle queries to a remote data source
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * and to avoid distracting the user by showing them less relevant
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * results before they've paused their typing.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @attribute queryDelay
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @type Number
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @default 150
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith queryDelay: {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith validator: function (value) {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith return isNumber(value) && value >= 0;
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith value: 150
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * DataSource request template. This can be a function that accepts a
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * query as a parameter and returns a request string, or it can be a
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * string containing the placeholder "{query}", which will be replaced
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * with the actual URI-encoded query.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * When using a string template, if it's necessary for the literal
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * string "{query}" to appear in the request, escape it with a slash:
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * "\{query}".
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * While <code>requestTemplate</code> can be set to either a function or
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * a string, it will always be returned as a function that accepts a
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * query argument and returns a string.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
b9436848b51ee2b4f04b9217e74172c5a05ce276Jenny Han Donnelly * @attribute requestTemplate
b9436848b51ee2b4f04b9217e74172c5a05ce276Jenny Han Donnelly * @type Function|String
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @default encodeURIComponent
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith requestTemplate: {
d3ac39f387c773c28001a1323aa85082051965aemattsnider setter: function (template) {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith if (isFunction(template)) {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith return template;
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith }
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith template = template.toString();
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith return function (query) {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith // Replace {query} with the URI-encoded query, but turn
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith // \{query} into the literal string "{query}" to allow that
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith // string to appear in the request if necessary.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith return template.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith replace(/(^|[^\\])((\\{2})*)\{query\}/, '$1$2' + encodeURIComponent(query)).
d3ac39f387c773c28001a1323aa85082051965aemattsnider replace(/(^|[^\\])((\\{2})*)\\(\{query\})/, '$1$2$4');
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith };
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith value: encodeURIComponent
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
b9436848b51ee2b4f04b9217e74172c5a05ce276Jenny Han Donnelly * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * Array of local result filter functions. If provided, each filter
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * will be called with two arguments when results are received: the
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * query and the results received from the DataSource. Each filter is
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * expected to return a filtered or modified version of those results,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * which will then be passed on to subsequent filters, to the
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <code>resultHighlighter</code> function (if set), and finally to
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * subscribers to the <code>results</code> event.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * If no DataSource is set, result filters will not be called.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @attribute resultFilters
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @type Array
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @default []
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith resultFilters: {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith validator: Lang.isArray,
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith value: []
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith },
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith /**
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
8392a541bf432ed8d6e1985b6306b83dc898768dJenny Han Donnelly * Function which will be used to highlight results. If provided, this
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * function will be called with two arguments after results have been
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * received and filtered: the query and the filtered results. The
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * highlighter is expected to return a modified version of the results
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * with the query highlighted in some form.
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * <p>
8392a541bf432ed8d6e1985b6306b83dc898768dJenny Han Donnelly * If no DataSource is set, the highlighter will not be called.
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly * </p>
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith *
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @attribute resultHighlighter
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith * @type Function|null
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith */
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith resultHighlighter: {
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith validator: function (value) {
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly return isFunction(value) || value === null;
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith }
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith }
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith};
0d6d1a2d994933a68a100ec3dcdc7c7a0eeeae6cJenny Han Donnelly
f805ad34c19740fa0c9729ce35fe59d191912f32Luke Smith// Because nobody wants to type ".yui3-autocomplete-blah" a hundred times.
AutoCompleteBase.CSS_PREFIX = 'ac';
AutoCompleteBase.prototype = {
// -- Protected Prototype Methods ------------------------------------------
/**
* Attaches <code>inputNode</code> event listeners.
*
* @method _bindInput
* @protected
*/
_bindInput: function () {
var inputNode = this.get(INPUT_NODE);
if (!inputNode) {
Y.error('No inputNode specified.');
}
// Unbind first, just in case.
this._unbindInput();
this._inputEvents = [
// We're listening to the valueChange event from the
// event-valuechange module here, not our own valueChange event
// (which just wraps this one for convenience).
inputNode.on(VALUE_CHANGE, this._onValueChange, this)
];
},
/**
* <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>
* In <code>autocomplete-base</code> this just returns the input value
* itself, but it can be overridden to implement more complex logic, such as
* adding support for query delimiters (see the
* <code>autocomplete-delim</code> module).
* </p>
*
* @method _parseValue
* @param {String} value input value from which to extract the query
* @return {String|null} query
* @protected
*/
_parseValue: function (value) {
return value;
},
/**
* Synchronizes the state of the <code>inputNode</code>.
*
* @method _syncInput
* @protected
*/
_syncInput: function () {
var inputNode = this.get(INPUT_NODE);
if (inputNode.get('nodeName').toLowerCase() === 'input') {
inputNode.setAttribute('autocomplete', this.get(ALLOW_BROWSER_AC) ? 'on' : 'off');
}
},
/**
* Detaches <code>inputNode</code> event listeners.
*
* @method _unbindInput
* @protected
*/
_unbindInput: function () {
while (this._inputEvents && this._inputEvents.length) {
this._inputEvents.pop().detach();
}
},
// -- Protected Event Handlers ---------------------------------------------
/**
* Handles DataSource responses and fires the <code>results</code> event if
* there appear to be results.
*
* @method _onResponse
* @param {EventFacade} e
* @protected
*/
_onResponse: function (e) {
var filters,
highlighter,
i,
len,
query,
results = e && e.response && e.response.results;
if (results) {
query = e.callback.query;
// Ignore stale responses that aren't for the current query.
if (query === this.get(QUERY)) {
filters = this.get(RESULT_FILTERS) || [];
highlighter = this.get(RESULT_HIGHLIGHTER);
if (highlighter) {
// The highlighter is treated just like a filter except that
// it's always called last. Concat is used to ensure that
// the original filters array isn't touched.
filters = filters.concat([highlighter]);
}
for (i = 0, len = filters.length; i < len; ++i) {
results = filters[i](query, results);
}
this.fire(EVT_RESULTS, {
data : e.data,
query : query,
results: results
});
}
}
},
/**
* 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 _onValueChange
* @param {EventFacade} e
* @protected
*/
_onValueChange: function (e) {
var delay,
fire,
value = e.newVal,
query = this._parseValue(value),
that;
Y.log('valueChange: new: "' + value + '"; old: "' + e.prevVal + '"', 'info', 'autocompleteBase');
this.fire(EVT_VALUE_CHANGE, {
newVal : value,
prevVal: e.prevVal
});
if (query.length >= this.get(MIN_QUERY_LENGTH)) {
delay = this.get(QUERY_DELAY);
that = this;
fire = function () {
that.fire(EVT_QUERY, {
inputValue: value,
query : query
});
};
if (delay) {
clearTimeout(this._delay);
this._delay = setTimeout(fire, delay);
} else {
fire();
}
}
if (!value.length) {
this.fire(EVT_CLEAR, {
prevVal: e.prevVal
});
}
},
// -- Protected Default Event Handlers -------------------------------------
/**
* Default <code>query</code> event handler. Sets the <code>query</code>
* property and sends a request to the DataSource if one is configured.
*
* @method _defQueryFn
* @param {EventFacade} e
* @protected
*/
_defQueryFn: function (e) {
var dataSource = this.get(DATA_SOURCE),
query = e.query;
this._set(QUERY, query);
Y.log('query: "' + query + '"; inputValue: "' + e.inputValue + '"', 'info', 'autocompleteBase');
if (dataSource) {
Y.log('sendRequest: ' + this.get(REQUEST_TEMPLATE)(query), 'info', 'autocompleteBase');
dataSource.sendRequest({
request: this.get(REQUEST_TEMPLATE)(query),
callback: {
query : query,
success: Y.bind(this._onResponse, this)
// TODO: handle failures here, or should the implementer rely on DataSource events for that?
}
});
}
}
};
Y.AutoCompleteBase = AutoCompleteBase;