1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642 |
- /*
- YUI 3.4.1 (build 4118)
- Copyright 2011 Yahoo! Inc. All rights reserved.
- Licensed under the BSD License.
- http://yuilibrary.com/license/
- */
- YUI.add('autocomplete-base', function(Y) {
- /**
- * Provides automatic input completion or suggestions for text input fields and
- * textareas.
- *
- * @module autocomplete
- * @main autocomplete
- * @since 3.3.0
- */
- /**
- * <code>Y.Base</code> extension that provides core autocomplete logic (but no
- * UI implementation) for a text input field or textarea. Must be mixed into a
- * <code>Y.Base</code>-derived class to be useful.
- *
- * @submodule autocomplete-base
- */
- /**
- * <p>
- * Extension that provides core autocomplete logic (but no UI implementation)
- * for a text input field or textarea.
- * </p>
- *
- * <p>
- * The <code>AutoCompleteBase</code> class provides events and attributes that
- * abstract away core autocomplete logic and configuration, but does not provide
- * a widget implementation or suggestion UI. For a prepackaged autocomplete
- * widget, see <code>AutoCompleteList</code>.
- * </p>
- *
- * <p>
- * This extension cannot be instantiated directly, since it doesn't provide an
- * actual implementation. It's intended to be mixed into a
- * <code>Y.Base</code>-based class or widget.
- * </p>
- *
- * <p>
- * <code>Y.Widget</code>-based example:
- * </p>
- *
- * <pre>
- * YUI().use('autocomplete-base', 'widget', function (Y) {
- * var MyAC = Y.Base.create('myAC', Y.Widget, [Y.AutoCompleteBase], {
- * // Custom prototype methods and properties.
- * }, {
- * // Custom static methods and properties.
- * });
- *
- * // Custom implementation code.
- * });
- * </pre>
- *
- * <p>
- * <code>Y.Base</code>-based example:
- * </p>
- *
- * <pre>
- * YUI().use('autocomplete-base', function (Y) {
- * var MyAC = Y.Base.create('myAC', Y.Base, [Y.AutoCompleteBase], {
- * initializer: function () {
- * this._bindUIACBase();
- * this._syncUIACBase();
- * },
- *
- * // Custom prototype methods and properties.
- * }, {
- * // Custom static methods and properties.
- * });
- *
- * // Custom implementation code.
- * });
- * </pre>
- *
- * @class AutoCompleteBase
- */
- var Escape = Y.Escape,
- Lang = Y.Lang,
- YArray = Y.Array,
- YObject = Y.Object,
- isFunction = Lang.isFunction,
- isString = Lang.isString,
- trim = Lang.trim,
- INVALID_VALUE = Y.Attribute.INVALID_VALUE,
- _FUNCTION_VALIDATOR = '_functionValidator',
- _SOURCE_SUCCESS = '_sourceSuccess',
- ALLOW_BROWSER_AC = 'allowBrowserAutocomplete',
- INPUT_NODE = 'inputNode',
- QUERY = 'query',
- QUERY_DELIMITER = 'queryDelimiter',
- REQUEST_TEMPLATE = 'requestTemplate',
- RESULTS = 'results',
- RESULT_LIST_LOCATOR = 'resultListLocator',
- VALUE = 'value',
- VALUE_CHANGE = 'valueChange',
- EVT_CLEAR = 'clear',
- EVT_QUERY = QUERY,
- EVT_RESULTS = RESULTS;
- function AutoCompleteBase() {
- // AOP bindings.
- Y.before(this._bindUIACBase, this, 'bindUI');
- Y.before(this._destructorACBase, this, 'destructor');
- Y.before(this._syncUIACBase, this, 'syncUI');
- // -- Public Events --------------------------------------------------------
- /**
- * Fires after the query has been completely cleared or no longer meets the
- * minimum query length requirement.
- *
- * @event clear
- * @param {EventFacade} e Event facade with the following additional
- * properties:
- *
- * <dl>
- * <dt>prevVal (String)</dt>
- * <dd>
- * Value of the query before it was cleared.
- * </dd>
- * </dl>
- *
- * @preventable _defClearFn
- */
- this.publish(EVT_CLEAR, {
- defaultFn: this._defClearFn
- });
- /**
- * Fires when the contents of the input field have changed and the input
- * value meets the criteria necessary to generate an autocomplete query.
- *
- * @event query
- * @param {EventFacade} e Event facade with the following additional
- * properties:
- *
- * <dl>
- * <dt>inputValue (String)</dt>
- * <dd>
- * Full contents of the text input field or textarea that generated
- * the query.
- * </dd>
- *
- * <dt>query (String)</dt>
- * <dd>
- * Autocomplete query. This is the string that will be used to
- * request completion results. It may or may not be the same as
- * <code>inputValue</code>.
- * </dd>
- * </dl>
- *
- * @preventable _defQueryFn
- */
- this.publish(EVT_QUERY, {
- defaultFn: this._defQueryFn
- });
- /**
- * Fires after query results are received from the <code>source</code>. If
- * no source has been set, this event will not fire.
- *
- * @event results
- * @param {EventFacade} e Event facade with the following additional
- * properties:
- *
- * <dl>
- * <dt>data (Array|Object)</dt>
- * <dd>
- * Raw, unfiltered result data (if available).
- * </dd>
- *
- * <dt>query (String)</dt>
- * <dd>
- * Query that generated these results.
- * </dd>
- *
- * <dt>results (Array)</dt>
- * <dd>
- * Array of filtered, formatted, and highlighted results. Each item in
- * the array is an object with the following properties:
- *
- * <dl>
- * <dt>display (Node|HTMLElement|String)</dt>
- * <dd>
- * Formatted result HTML suitable for display to the user. If no
- * custom formatter is set, this will be an HTML-escaped version of
- * the string in the <code>text</code> property.
- * </dd>
- *
- * <dt>highlighted (String)</dt>
- * <dd>
- * Highlighted (but not formatted) result text. This property will
- * only be set if a highlighter is in use.
- * </dd>
- *
- * <dt>raw (mixed)</dt>
- * <dd>
- * Raw, unformatted result in whatever form it was provided by the
- * <code>source</code>.
- * </dd>
- *
- * <dt>text (String)</dt>
- * <dd>
- * Plain text version of the result, suitable for being inserted
- * into the value of a text input field or textarea when the result
- * is selected by a user. This value is not HTML-escaped and should
- * not be inserted into the page using innerHTML.
- * </dd>
- * </dl>
- * </dd>
- * </dl>
- *
- * @preventable _defResultsFn
- */
- this.publish(EVT_RESULTS, {
- defaultFn: this._defResultsFn
- });
- }
- // -- Public Static Properties -------------------------------------------------
- AutoCompleteBase.ATTRS = {
- /**
- * Whether or not to enable the browser's built-in autocomplete
- * functionality for input fields.
- *
- * @attribute allowBrowserAutocomplete
- * @type Boolean
- * @default false
- */
- allowBrowserAutocomplete: {
- value: false
- },
- /**
- * When a <code>queryDelimiter</code> is set, trailing delimiters will
- * automatically be stripped from the input value by default when the
- * input node loses focus. Set this to <code>true</code> to allow trailing
- * delimiters.
- *
- * @attribute allowTrailingDelimiter
- * @type Boolean
- * @default false
- */
- allowTrailingDelimiter: {
- value: false
- },
- /**
- * Node to monitor for changes, which will generate <code>query</code>
- * events when appropriate. May be either an input field or a textarea.
- *
- * @attribute inputNode
- * @type Node|HTMLElement|String
- * @writeonce
- */
- inputNode: {
- setter: Y.one,
- writeOnce: 'initOnly'
- },
- /**
- * Maximum number of results to return. A value of <code>0</code> or less
- * will allow an unlimited number of results.
- *
- * @attribute maxResults
- * @type Number
- * @default 0
- */
- maxResults: {
- value: 0
- },
- /**
- * Minimum number of characters that must be entered before a
- * <code>query</code> event will be fired. A value of <code>0</code>
- * allows empty queries; a negative value will effectively disable all
- * <code>query</code> events.
- *
- * @attribute minQueryLength
- * @type Number
- * @default 1
- */
- minQueryLength: {
- value: 1
- },
- /**
- * <p>
- * Current query, or <code>null</code> if there is no current query.
- * </p>
- *
- * <p>
- * The query might not be the same as the current value of the input
- * node, both for timing reasons (due to <code>queryDelay</code>) and
- * because when one or more <code>queryDelimiter</code> separators are
- * in use, only the last portion of the delimited input string will be
- * used as the query value.
- * </p>
- *
- * @attribute query
- * @type String|null
- * @default null
- * @readonly
- */
- query: {
- readOnly: true,
- value: null
- },
- /**
- * <p>
- * Number of milliseconds to delay after input before triggering a
- * <code>query</code> event. If new input occurs before this delay is
- * over, the previous input event will be ignored and a new delay will
- * begin.
- * </p>
- *
- * <p>
- * This can be useful both to throttle queries to a remote data source
- * and to avoid distracting the user by showing them less relevant
- * results before they've paused their typing.
- * </p>
- *
- * @attribute queryDelay
- * @type Number
- * @default 100
- */
- queryDelay: {
- value: 100
- },
- /**
- * Query delimiter string. When a delimiter is configured, the input value
- * will be split on the delimiter, and only the last portion will be used in
- * autocomplete queries and updated when the <code>query</code> attribute is
- * modified.
- *
- * @attribute queryDelimiter
- * @type String|null
- * @default null
- */
- queryDelimiter: {
- value: null
- },
- /**
- * <p>
- * Source request template. This can be a function that accepts a query as a
- * parameter and returns a request string, or it can be a string containing
- * the placeholder "{query}", which will be replaced with the actual
- * URI-encoded query. In either case, the resulting string will be appended
- * to the request URL when the <code>source</code> attribute is set to a
- * remote DataSource, JSONP URL, or XHR URL (it will not be appended to YQL
- * URLs).
- * </p>
- *
- * <p>
- * While <code>requestTemplate</code> may be set to either a function or
- * a string, it will always be returned as a function that accepts a
- * query argument and returns a string.
- * </p>
- *
- * @attribute requestTemplate
- * @type Function|String|null
- * @default null
- */
- requestTemplate: {
- setter: '_setRequestTemplate',
- value: null
- },
- /**
- * <p>
- * Array of local result filter functions. If provided, each filter
- * will be called with two arguments when results are received: the query
- * and an array of result objects. See the documentation for the
- * <code>results</code> event for a list of the properties available on each
- * result object.
- * </p>
- *
- * <p>
- * Each filter is expected to return a filtered or modified version of the
- * results array, which will then be passed on to subsequent filters, then
- * the <code>resultHighlighter</code> function (if set), then the
- * <code>resultFormatter</code> function (if set), and finally to
- * subscribers to the <code>results</code> event.
- * </p>
- *
- * <p>
- * If no <code>source</code> is set, result filters will not be called.
- * </p>
- *
- * <p>
- * Prepackaged result filters provided by the autocomplete-filters and
- * autocomplete-filters-accentfold modules can be used by specifying the
- * filter name as a string, such as <code>'phraseMatch'</code> (assuming
- * the necessary filters module is loaded).
- * </p>
- *
- * @attribute resultFilters
- * @type Array
- * @default []
- */
- resultFilters: {
- setter: '_setResultFilters',
- value: []
- },
- /**
- * <p>
- * Function which will be used to format results. If provided, this function
- * will be called with two arguments after results have been received and
- * filtered: the query and an array of result objects. The formatter is
- * expected to return an array of HTML strings or Node instances containing
- * the desired HTML for each result.
- * </p>
- *
- * <p>
- * See the documentation for the <code>results</code> event for a list of
- * the properties available on each result object.
- * </p>
- *
- * <p>
- * If no <code>source</code> is set, the formatter will not be called.
- * </p>
- *
- * @attribute resultFormatter
- * @type Function|null
- */
- resultFormatter: {
- validator: _FUNCTION_VALIDATOR
- },
- /**
- * <p>
- * Function which will be used to highlight results. If provided, this
- * function will be called with two arguments after results have been
- * received and filtered: the query and an array of filtered result objects.
- * The highlighter is expected to return an array of highlighted result
- * text in the form of HTML strings.
- * </p>
- *
- * <p>
- * See the documentation for the <code>results</code> event for a list of
- * the properties available on each result object.
- * </p>
- *
- * <p>
- * If no <code>source</code> is set, the highlighter will not be called.
- * </p>
- *
- * @attribute resultHighlighter
- * @type Function|null
- */
- resultHighlighter: {
- setter: '_setResultHighlighter'
- },
- /**
- * <p>
- * Locator that should be used to extract an array of results from a
- * non-array response.
- * </p>
- *
- * <p>
- * By default, no locator is applied, and all responses are assumed to be
- * arrays by default. If all responses are already arrays, you don't need to
- * define a locator.
- * </p>
- *
- * <p>
- * The locator may be either a function (which will receive the raw response
- * as an argument and must return an array) or a string representing an
- * object path, such as "foo.bar.baz" (which would return the value of
- * <code>result.foo.bar.baz</code> if the response is an object).
- * </p>
- *
- * <p>
- * While <code>resultListLocator</code> may be set to either a function or a
- * string, it will always be returned as a function that accepts a response
- * argument and returns an array.
- * </p>
- *
- * @attribute resultListLocator
- * @type Function|String|null
- */
- resultListLocator: {
- setter: '_setLocator'
- },
- /**
- * Current results, or an empty array if there are no results.
- *
- * @attribute results
- * @type Array
- * @default []
- * @readonly
- */
- results: {
- readOnly: true,
- value: []
- },
- /**
- * <p>
- * Locator that should be used to extract a plain text string from a
- * non-string result item. The resulting text value will typically be the
- * value that ends up being inserted into an input field or textarea when
- * the user of an autocomplete implementation selects a result.
- * </p>
- *
- * <p>
- * By default, no locator is applied, and all results are assumed to be
- * plain text strings. If all results are already plain text strings, you
- * don't need to define a locator.
- * </p>
- *
- * <p>
- * The locator may be either a function (which will receive the raw result
- * as an argument and must return a string) or a string representing an
- * object path, such as "foo.bar.baz" (which would return the value of
- * <code>result.foo.bar.baz</code> if the result is an object).
- * </p>
- *
- * <p>
- * While <code>resultTextLocator</code> may be set to either a function or a
- * string, it will always be returned as a function that accepts a result
- * argument and returns a string.
- * </p>
- *
- * @attribute resultTextLocator
- * @type Function|String|null
- */
- resultTextLocator: {
- setter: '_setLocator'
- },
- /**
- * <p>
- * Source for autocomplete results. The following source types are
- * supported:
- * </p>
- *
- * <dl>
- * <dt>Array</dt>
- * <dd>
- * <p>
- * <i>Example:</i> <code>['first result', 'second result', 'etc']</code>
- * </p>
- *
- * <p>
- * The full array will be provided to any configured filters for each
- * query. This is an easy way to create a fully client-side autocomplete
- * implementation.
- * </p>
- * </dd>
- *
- * <dt>DataSource</dt>
- * <dd>
- * <p>
- * A <code>DataSource</code> instance or other object that provides a
- * DataSource-like <code>sendRequest</code> method. See the
- * <code>DataSource</code> documentation for details.
- * </p>
- * </dd>
- *
- * <dt>Function</dt>
- * <dd>
- * <p>
- * <i>Example (synchronous):</i> <code>function (query) { return ['foo', 'bar']; }</code><br>
- <i>Example (async):</i> <code>function (query, callback) { callback(['foo', 'bar']); }</code>
- * </p>
- *
- * <p>
- * A function source will be called with the current query and a
- * callback function as parameters, and should either return an array of
- * results (for synchronous operation) or return nothing and pass an
- * array of results to the provided callback (for asynchronous
- * operation).
- * </p>
- * </dd>
- *
- * <dt>Object</dt>
- * <dd>
- * <p>
- * <i>Example:</i> <code>{foo: ['foo result 1', 'foo result 2'], bar: ['bar result']}</code>
- * </p>
- *
- * <p>
- * An object will be treated as a query hashmap. If a property on the
- * object matches the current query, the value of that property will be
- * used as the response.
- * </p>
- *
- * <p>
- * The response is assumed to be an array of results by default. If the
- * response is not an array, provide a <code>resultListLocator</code> to
- * process the response and return an array.
- * </p>
- * </dd>
- * </dl>
- *
- * <p>
- * If the optional <code>autocomplete-sources</code> module is loaded, then
- * the following additional source types will be supported as well:
- * </p>
- *
- * <dl>
- * <dt><select> Node</dt>
- * <dd>
- * <p>
- * You may provide a YUI Node instance wrapping a <select>
- * element, and the options in the list will be used as results. You
- * will also need to specify a <code>resultTextLocator</code> of 'text'
- * or 'value', depending on what you want to use as the text of the
- * result.
- * </p>
- *
- * <p>
- * Each result will be an object with the following properties:
- * </p>
- *
- * <dl>
- * <dt>html (String)</dt>
- * <dd>
- * <p>HTML content of the <option> element.</p>
- * </dd>
- *
- * <dt>index (Number)</dt>
- * <dd>
- * <p>Index of the <option> element in the list.</p>
- * </dd>
- *
- * <dt>node (Y.Node)</dt>
- * <dd>
- * <p>Node instance referring to the original <option> element.</p>
- * </dd>
- *
- * <dt>selected (Boolean)</dt>
- * <dd>
- * <p>Whether or not this item is currently selected in the
- * <select> list.</p>
- * </dd>
- *
- * <dt>text (String)</dt>
- * <dd>
- * <p>Text content of the <option> element.</p>
- * </dd>
- *
- * <dt>value (String)</dt>
- * <dd>
- * <p>Value of the <option> element.</p>
- * </dd>
- * </dl>
- * </dd>
- *
- * <dt>String (JSONP URL)</dt>
- * <dd>
- * <p>
- * <i>Example:</i> <code>'http://example.com/search?q={query}&callback={callback}'</code>
- * </p>
- *
- * <p>
- * If a URL with a <code>{callback}</code> placeholder is provided, it
- * will be used to make a JSONP request. The <code>{query}</code>
- * placeholder will be replaced with the current query, and the
- * <code>{callback}</code> placeholder will be replaced with an
- * internally-generated JSONP callback name. Both placeholders must
- * appear in the URL, or the request will fail. An optional
- * <code>{maxResults}</code> placeholder may also be provided, and will
- * be replaced with the value of the maxResults attribute (or 1000 if
- * the maxResults attribute is 0 or less).
- * </p>
- *
- * <p>
- * The response is assumed to be an array of results by default. If the
- * response is not an array, provide a <code>resultListLocator</code> to
- * process the response and return an array.
- * </p>
- *
- * <p>
- * <strong>The <code>jsonp</code> module must be loaded in order for
- * JSONP URL sources to work.</strong> If the <code>jsonp</code> module
- * is not already loaded, it will be loaded on demand if possible.
- * </p>
- * </dd>
- *
- * <dt>String (XHR URL)</dt>
- * <dd>
- * <p>
- * <i>Example:</i> <code>'http://example.com/search?q={query}'</code>
- * </p>
- *
- * <p>
- * If a URL without a <code>{callback}</code> placeholder is provided,
- * it will be used to make a same-origin XHR request. The
- * <code>{query}</code> placeholder will be replaced with the current
- * query. An optional <code>{maxResults}</code> placeholder may also be
- * provided, and will be replaced with the value of the maxResults
- * attribute (or 1000 if the maxResults attribute is 0 or less).
- * </p>
- *
- * <p>
- * The response is assumed to be a JSON array of results by default. If
- * the response is a JSON object and not an array, provide a
- * <code>resultListLocator</code> to process the response and return an
- * array. If the response is in some form other than JSON, you will
- * need to use a custom DataSource instance as the source.
- * </p>
- *
- * <p>
- * <strong>The <code>io-base</code> and <code>json-parse</code> modules
- * must be loaded in order for XHR URL sources to work.</strong> If
- * these modules are not already loaded, they will be loaded on demand
- * if possible.
- * </p>
- * </dd>
- *
- * <dt>String (YQL query)</dt>
- * <dd>
- * <p>
- * <i>Example:</i> <code>'select * from search.suggest where query="{query}"'</code>
- * </p>
- *
- * <p>
- * If a YQL query is provided, it will be used to make a YQL request.
- * The <code>{query}</code> placeholder will be replaced with the
- * current autocomplete query. This placeholder must appear in the YQL
- * query, or the request will fail. An optional
- * <code>{maxResults}</code> placeholder may also be provided, and will
- * be replaced with the value of the maxResults attribute (or 1000 if
- * the maxResults attribute is 0 or less).
- * </p>
- *
- * <p>
- * <strong>The <code>yql</code> module must be loaded in order for YQL
- * sources to work.</strong> If the <code>yql</code> module is not
- * already loaded, it will be loaded on demand if possible.
- * </p>
- * </dd>
- * </dl>
- *
- * <p>
- * As an alternative to providing a source, you could simply listen for
- * <code>query</code> events and handle them any way you see fit. Providing
- * a source is optional, but will usually be simpler.
- * </p>
- *
- * @attribute source
- * @type Array|DataSource|Function|Node|Object|String|null
- */
- source: {
- setter: '_setSource'
- },
- /**
- * <p>
- * May be used to force a specific source type, overriding the automatic
- * source type detection. It should almost never be necessary to do this,
- * but as they taught us in the Boy Scouts, one should always be prepared,
- * so it's here if you need it. Be warned that if you set this attribute and
- * something breaks, it's your own fault.
- * </p>
- *
- * <p>
- * Supported <code>sourceType</code> values are: 'array', 'datasource',
- * 'function', and 'object'.
- * </p>
- *
- * <p>
- * If the <code>autocomplete-sources</code> module is loaded, the following
- * additional source types are supported: 'io', 'jsonp', 'select',
- * 'string', 'yql'
- * </p>
- *
- * @attribute sourceType
- * @type String
- */
- sourceType: {
- value: null
- },
- /**
- * If the <code>inputNode</code> specified at instantiation time has a
- * <code>node-tokeninput</code> plugin attached to it, this attribute will
- * be a reference to the <code>Y.Plugin.TokenInput</code> instance.
- *
- * @attribute tokenInput
- * @type Plugin.TokenInput
- * @readonly
- */
- tokenInput: {
- readOnly: true
- },
- /**
- * Current value of the input node.
- *
- * @attribute value
- * @type String
- * @default ''
- */
- value: {
- // Why duplicate this._inputNode.get('value')? Because we need a
- // reliable way to track the source of value changes. We want to perform
- // completion when the user changes the value, but not when we change
- // the value.
- value: ''
- }
- };
- AutoCompleteBase.CSS_PREFIX = 'ac';
- AutoCompleteBase.UI_SRC = (Y.Widget && Y.Widget.UI_SRC) || 'ui';
- /**
- * Mapping of built-in source types to their setter functions. DataSource
- * instances and DataSource-like objects are handled natively, so are not
- * mapped here.
- *
- * @property SOURCE_TYPES
- * @type {Object}
- * @static
- */
- AutoCompleteBase.SOURCE_TYPES = {
- array : '_createArraySource',
- 'function': '_createFunctionSource',
- object : '_createObjectSource'
- };
- 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 (query || query === '') {
- this._set(QUERY, query);
- } else {
- query = this.get(QUERY) || '';
- }
- if (source) {
- if (!requestTemplate) {
- requestTemplate = this.get(REQUEST_TEMPLATE);
- }
- request = requestTemplate ?
- requestTemplate.call(this, query) : query;
- Y.log('sendRequest: ' + request, 'info', 'autocomplete-base');
- source.sendRequest({
- query : query,
- 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 = new Y.EventHandle([
- // 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),
- inputNode.on('blur', this._onInputBlur, this),
- this.after(ALLOW_BROWSER_AC + 'Change', this._syncBrowserAutocomplete),
- this.after('sourceTypeChange', this._afterSourceTypeChange),
- this.after(VALUE_CHANGE, this._afterValueChange)
- ]);
- },
- /**
- * Detaches AutoCompleteBase event listeners.
- *
- * @method _destructorACBase
- * @protected
- */
- _destructorACBase: function () {
- this._acBaseEvents.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));
- },
- // -- 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 {
- type: 'array',
- sendRequest: function (request) {
- that[_SOURCE_SUCCESS](source.concat(), request);
- }
- };
- },
- /**
- * Creates a DataSource-like object that passes the query to a
- * custom-defined function, which is expected to call the provided callback
- * with an array of results. See the <code>source</code> attribute for more
- * details.
- *
- * @method _createFunctionSource
- * @param {Function} source Function that accepts a query and a callback as
- * parameters, and calls the callback with an array of results.
- * @return {Object} DataSource-like object.
- * @protected
- */
- _createFunctionSource: function (source) {
- var that = this;
- return {
- type: 'function',
- sendRequest: function (request) {
- var value;
- function afterResults(results) {
- that[_SOURCE_SUCCESS](results || [], request);
- }
- // Allow both synchronous and asynchronous functions. If we get
- // a truthy return value, assume the function is synchronous.
- if ((value = source(request.query, afterResults))) {
- afterResults(value);
- }
- }
- };
- },
- /**
- * 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) {
- var that = this;
- return {
- type: 'object',
- sendRequest: function (request) {
- var query = request.query;
- that[_SOURCE_SUCCESS](
- YObject.owns(source, query) ? source[query] : [],
- request
- );
- }
- };
- },
- /**
- * 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;
- },
- /**
- * 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: []
- },
- listLocator = this.get(RESULT_LIST_LOCATOR),
- results = [],
- unfiltered = response && response.results,
- filters,
- formatted,
- formatter,
- highlighted,
- highlighter,
- i,
- len,
- maxResults,
- result,
- text,
- textLocator;
- if (unfiltered && listLocator) {
- unfiltered = listLocator.call(this, unfiltered);
- }
- if (unfiltered && unfiltered.length) {
- filters = this.get('resultFilters');
- textLocator = this.get('resultTextLocator');
- // Create a lightweight result object for each result to make them
- // easier to work with. The various properties on the object
- // represent different formats of the result, and will be populated
- // as we go.
- for (i = 0, len = unfiltered.length; i < len; ++i) {
- result = unfiltered[i];
- text = textLocator ?
- textLocator.call(this, result) :
- result.toString();
- results.push({
- display: Escape.html(text),
- raw : result,
- text : text
- });
- }
- // Run the results through all configured result filters. Each
- // filter returns an array of (potentially fewer) result objects,
- // which is then passed to the next filter, and so on.
- for (i = 0, len = filters.length; i < len; ++i) {
- results = filters[i].call(this, query, results.concat());
- if (!results) {
- Y.log("Filter didn't return anything.", 'warn', 'autocomplete-base');
- return;
- }
- if (!results.length) {
- break;
- }
- }
- if (results.length) {
- formatter = this.get('resultFormatter');
- highlighter = this.get('resultHighlighter');
- maxResults = this.get('maxResults');
- // If maxResults is set and greater than 0, limit the number of
- // results.
- if (maxResults && maxResults > 0 &&
- results.length > maxResults) {
- results.length = maxResults;
- }
- // Run the results through the configured highlighter (if any).
- // The highlighter returns an array of highlighted strings (not
- // an array of result objects), and these strings are then added
- // to each result object.
- if (highlighter) {
- highlighted = highlighter.call(this, query,
- results.concat());
- if (!highlighted) {
- Y.log("Highlighter didn't return anything.", 'warn', 'autocomplete-base');
- return;
- }
- for (i = 0, len = highlighted.length; i < len; ++i) {
- result = results[i];
- result.highlighted = highlighted[i];
- result.display = result.highlighted;
- }
- }
- // Run the results through the configured formatter (if any) to
- // produce the final formatted results. The formatter returns an
- // array of strings or Node instances (not an array of result
- // objects), and these strings/Nodes are then added to each
- // result object.
- if (formatter) {
- formatted = formatter.call(this, query, results.concat());
- if (!formatted) {
- Y.log("Formatter didn't return anything.", 'warn', 'autocomplete-base');
- return;
- }
- for (i = 0, len = formatted.length; i < len; ++i) {
- results[i].display = formatted[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 (Lang.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> and/or
- * the value of the <code>sourceType</code> attribute.
- *
- * @method _setSource
- * @param {mixed} source AutoComplete source. See the <code>source</code>
- * attribute for details.
- * @return {DataSource|Object}
- * @protected
- */
- _setSource: function (source) {
- var sourceType = this.get('sourceType') || Lang.type(source),
- sourceSetter;
- if ((source && isFunction(source.sendRequest))
- || source === null
- || sourceType === 'datasource') {
- // Quacks like a DataSource instance (or null). Make it so!
- this._rawSource = source;
- return source;
- }
- // See if there's a registered setter for this source type.
- if ((sourceSetter = AutoCompleteBase.SOURCE_TYPES[sourceType])) {
- this._rawSource = source;
- return Lang.isString(sourceSetter) ?
- this[sourceSetter](source) : sourceSetter(source);
- }
- Y.error("Unsupported source type '" + sourceType + "'. Maybe autocomplete-sources isn't loaded?");
- 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 ---------------------------------------------
- /**
- * Updates the current <code>source</code> based on the new
- * <code>sourceType</code> to ensure that the two attributes don't get out
- * of sync when they're changed separately.
- *
- * @method _afterSourceTypeChange
- * @param {EventFacade} e
- * @protected
- */
- _afterSourceTypeChange: function (e) {
- if (this._rawSource) {
- this.set('source', this._rawSource);
- }
- },
- /**
- * Handles change events for the <code>value</code> attribute.
- *
- * @method _afterValueChange
- * @param {EventFacade} e
- * @protected
- */
- _afterValueChange: function (e) {
- var delay,
- fire,
- minQueryLength,
- 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');
- minQueryLength = this.get('minQueryLength');
- query = this._parseValue(newVal) || '';
- if (minQueryLength >= 0 && query.length >= 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, {
- prevVal: e.prevVal ? this._parseValue(e.prevVal) : null
- });
- }
- },
- /**
- * Handles <code>blur</code> events on the input node.
- *
- * @method _onInputBlur
- * @param {EventFacade} e
- * @protected
- */
- _onInputBlur: function (e) {
- var delim = this.get(QUERY_DELIMITER),
- delimPos,
- newVal,
- value;
- // If a query delimiter is set and the input's value contains one or
- // more trailing delimiters, strip them.
- if (delim && !this.get('allowTrailingDelimiter')) {
- delim = Lang.trimRight(delim);
- value = newVal = this._inputNode.get(VALUE);
- if (delim) {
- while ((newVal = Lang.trimRight(newVal)) &&
- (delimPos = newVal.length - delim.length) &&
- newVal.lastIndexOf(delim) === delimPos) {
- newVal = newVal.substring(0, delimPos);
- }
- } else {
- // Delimiter is one or more space characters, so just trim the
- // value.
- newVal = Lang.trimRight(newVal);
- }
- if (newVal !== value) {
- this.set(VALUE, newVal);
- }
- }
- },
- /**
- * 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;
- }, '3.4.1' ,{optional:['autocomplete-sources'], requires:['array-extras', 'base-build', 'escape', 'event-valuechange', 'node-base']});
|