2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
7 /////////////////////////////////////////////////////////////////////////////
9 // YAHOO.widget.DataSource Backwards Compatibility
11 /////////////////////////////////////////////////////////////////////////////
13 YAHOO.widget.DS_JSArray = YAHOO.util.LocalDataSource;
15 YAHOO.widget.DS_JSFunction = YAHOO.util.FunctionDataSource;
17 YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
18 var DS = new YAHOO.util.XHRDataSource(sScriptURI, oConfigs);
19 DS._aDeprecatedSchema = aSchema;
23 YAHOO.widget.DS_ScriptNode = function(sScriptURI, aSchema, oConfigs) {
24 var DS = new YAHOO.util.ScriptNodeDataSource(sScriptURI, oConfigs);
25 DS._aDeprecatedSchema = aSchema;
29 YAHOO.widget.DS_XHR.TYPE_JSON = YAHOO.util.DataSourceBase.TYPE_JSON;
30 YAHOO.widget.DS_XHR.TYPE_XML = YAHOO.util.DataSourceBase.TYPE_XML;
31 YAHOO.widget.DS_XHR.TYPE_FLAT = YAHOO.util.DataSourceBase.TYPE_TEXT;
33 // TODO: widget.DS_ScriptNode.scriptCallbackParam
38 * The AutoComplete control provides the front-end logic for text-entry suggestion and
39 * completion functionality.
41 * @module autocomplete
42 * @requires yahoo, dom, event, datasource
44 * @namespace YAHOO.widget
45 * @title AutoComplete Widget
48 /****************************************************************************/
49 /****************************************************************************/
50 /****************************************************************************/
53 * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
54 * auto completion widget. Some key features:
56 * <li>Navigate with up/down arrow keys and/or mouse to pick a selection</li>
57 * <li>The drop down container can "roll down" or "fly out" via configurable
59 * <li>UI look-and-feel customizable through CSS, including container
60 * attributes, borders, position, fonts, etc</li>
65 * @param elInput {HTMLElement} DOM element reference of an input field.
66 * @param elInput {String} String ID of an input field.
67 * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
68 * @param elContainer {String} String ID of an existing DIV.
69 * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
70 * @param oConfigs {Object} (optional) Object literal of configuration params.
72 YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
73 if(elInput && elContainer && oDataSource) {
74 // Validate DataSource
75 if(oDataSource instanceof YAHOO.util.DataSourceBase) {
76 this.dataSource = oDataSource;
79 YAHOO.log("Could not instantiate AutoComplete due to an invalid DataSource", "error", this.toString());
83 // YAHOO.widget.DataSource schema backwards compatibility
84 // Converted deprecated schema into supported schema
85 // First assume key data is held in position 0 of results array
87 var schema = oDataSource.responseSchema;
88 // An old school schema has been defined in the deprecated DataSource constructor
89 if(oDataSource._aDeprecatedSchema) {
90 var aDeprecatedSchema = oDataSource._aDeprecatedSchema;
91 if(YAHOO.lang.isArray(aDeprecatedSchema)) {
93 if((oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_JSON) ||
94 (oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_UNKNOWN)) { // Used to default to unknown
95 // Store the resultsList
96 schema.resultsList = aDeprecatedSchema[0];
98 this.key = aDeprecatedSchema[1];
99 // Only resultsList and key are defined, so grab all the data
100 schema.fields = (aDeprecatedSchema.length < 3) ? null : aDeprecatedSchema.slice(1);
102 else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_XML) {
103 schema.resultNode = aDeprecatedSchema[0];
104 this.key = aDeprecatedSchema[1];
105 schema.fields = aDeprecatedSchema.slice(1);
107 else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_TEXT) {
108 schema.recordDelim = aDeprecatedSchema[0];
109 schema.fieldDelim = aDeprecatedSchema[1];
111 oDataSource.responseSchema = schema;
115 // Validate input element
116 if(YAHOO.util.Dom.inDocument(elInput)) {
117 if(YAHOO.lang.isString(elInput)) {
118 this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
119 this._elTextbox = document.getElementById(elInput);
122 this._sName = (elInput.id) ?
123 "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
124 "instance" + YAHOO.widget.AutoComplete._nIndex;
125 this._elTextbox = elInput;
127 YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
130 YAHOO.log("Could not instantiate AutoComplete due to an invalid input element", "error", this.toString());
134 // Validate container element
135 if(YAHOO.util.Dom.inDocument(elContainer)) {
136 if(YAHOO.lang.isString(elContainer)) {
137 this._elContainer = document.getElementById(elContainer);
140 this._elContainer = elContainer;
142 if(this._elContainer.style.display == "none") {
143 YAHOO.log("The container may not display properly if display is set to \"none\" in CSS", "warn", this.toString());
147 var elParent = this._elContainer.parentNode;
148 var elTag = elParent.tagName.toLowerCase();
150 YAHOO.util.Dom.addClass(elParent, "yui-ac");
153 YAHOO.log("Could not find the wrapper element for skinning", "warn", this.toString());
157 YAHOO.log("Could not instantiate AutoComplete due to an invalid container element", "error", this.toString());
161 // Default applyLocalFilter setting is to enable for local sources
162 if(this.dataSource.dataType === YAHOO.util.DataSourceBase.TYPE_LOCAL) {
163 this.applyLocalFilter = true;
166 // Set any config params passed in to override defaults
167 if(oConfigs && (oConfigs.constructor == Object)) {
168 for(var sConfig in oConfigs) {
170 this[sConfig] = oConfigs[sConfig];
175 // Initialization sequence
176 this._initContainerEl();
179 this._initContainerHelperEls();
183 var elTextbox = this._elTextbox;
186 YAHOO.util.Event.addListener(elTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
187 YAHOO.util.Event.addListener(elTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
188 YAHOO.util.Event.addListener(elTextbox,"focus",oSelf._onTextboxFocus,oSelf);
189 YAHOO.util.Event.addListener(elTextbox,"blur",oSelf._onTextboxBlur,oSelf);
190 YAHOO.util.Event.addListener(elContainer,"mouseover",oSelf._onContainerMouseover,oSelf);
191 YAHOO.util.Event.addListener(elContainer,"mouseout",oSelf._onContainerMouseout,oSelf);
192 YAHOO.util.Event.addListener(elContainer,"click",oSelf._onContainerClick,oSelf);
193 YAHOO.util.Event.addListener(elContainer,"scroll",oSelf._onContainerScroll,oSelf);
194 YAHOO.util.Event.addListener(elContainer,"resize",oSelf._onContainerResize,oSelf);
195 YAHOO.util.Event.addListener(elTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
196 YAHOO.util.Event.addListener(window,"unload",oSelf._onWindowUnload,oSelf);
199 this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
200 this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
201 this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
202 this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
203 this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
204 this.containerPopulateEvent = new YAHOO.util.CustomEvent("containerPopulate", this);
205 this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
206 this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
207 this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
208 this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
209 this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
210 this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
211 this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
212 this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
213 this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
214 this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
215 this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
216 this.textboxChangeEvent = new YAHOO.util.CustomEvent("textboxChange", this);
219 elTextbox.setAttribute("autocomplete","off");
220 YAHOO.widget.AutoComplete._nIndex++;
221 YAHOO.log("AutoComplete initialized","info",this.toString());
223 // Required arguments were not found
225 YAHOO.log("Could not instantiate AutoComplete due invalid arguments", "error", this.toString());
229 /////////////////////////////////////////////////////////////////////////////
231 // Public member variables
233 /////////////////////////////////////////////////////////////////////////////
236 * The DataSource object that encapsulates the data used for auto completion.
237 * This object should be an inherited object from YAHOO.widget.DataSource.
239 * @property dataSource
240 * @type YAHOO.widget.DataSource
242 YAHOO.widget.AutoComplete.prototype.dataSource = null;
245 * By default, results from local DataSources will pass through the filterResults
246 * method to apply a client-side matching algorithm.
248 * @property applyLocalFilter
250 * @default true for local arrays and json, otherwise false
252 YAHOO.widget.AutoComplete.prototype.applyLocalFilter = null;
255 * When applyLocalFilter is true, the local filtering algorthim can have case sensitivity
258 * @property queryMatchCase
262 YAHOO.widget.AutoComplete.prototype.queryMatchCase = false;
265 * When applyLocalFilter is true, results can be locally filtered to return
266 * matching strings that "contain" the query string rather than simply "start with"
269 * @property queryMatchContains
273 YAHOO.widget.AutoComplete.prototype.queryMatchContains = false;
276 * Enables query subset matching. When the DataSource's cache is enabled and queryMatchSubset is
277 * true, substrings of queries will return matching cached results. For
278 * instance, if the first query is for "abc" susequent queries that start with
279 * "abc", like "abcd", will be queried against the cache, and not the live data
280 * source. Recommended only for DataSources that return comprehensive results
281 * for queries with very few characters.
283 * @property queryMatchSubset
288 YAHOO.widget.AutoComplete.prototype.queryMatchSubset = false;
291 * Number of characters that must be entered before querying for results. A negative value
292 * effectively turns off the widget. A value of 0 allows queries of null or empty string
295 * @property minQueryLength
299 YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
302 * Maximum number of results to display in results container.
304 * @property maxResultsDisplayed
308 YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
311 * Number of seconds to delay before submitting a query request. If a query
312 * request is received before a previous one has completed its delay, the
313 * previous request is cancelled and the new request is set to the delay. If
314 * typeAhead is also enabled, this value must always be less than the typeAheadDelay
315 * in order to avoid certain race conditions.
317 * @property queryDelay
321 YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
324 * If typeAhead is true, number of seconds to delay before updating input with
325 * typeAhead value. In order to prevent certain race conditions, this value must
326 * always be greater than the queryDelay.
328 * @property typeAheadDelay
332 YAHOO.widget.AutoComplete.prototype.typeAheadDelay = 0.5;
335 * When IME usage is detected, AutoComplete will switch to querying the input
336 * value at the given interval rather than per key event.
338 * @property queryInterval
342 YAHOO.widget.AutoComplete.prototype.queryInterval = 500;
345 * Class name of a highlighted item within results container.
347 * @property highlightClassName
349 * @default "yui-ac-highlight"
351 YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
354 * Class name of a pre-highlighted item within results container.
356 * @property prehighlightClassName
359 YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
362 * Query delimiter. A single character separator for multiple delimited
363 * selections. Multiple delimiter characteres may be defined as an array of
364 * strings. A null value or empty string indicates that query results cannot
365 * be delimited. This feature is not recommended if you need forceSelection to
368 * @property delimChar
369 * @type String | String[]
371 YAHOO.widget.AutoComplete.prototype.delimChar = null;
374 * Whether or not the first item in results container should be automatically highlighted
377 * @property autoHighlight
381 YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
384 * If autohighlight is enabled, whether or not the input field should be automatically updated
385 * with the first query result as the user types, auto-selecting the substring portion
386 * of the first result that the user has not yet typed.
388 * @property typeAhead
392 YAHOO.widget.AutoComplete.prototype.typeAhead = false;
395 * Whether or not to animate the expansion/collapse of the results container in the
396 * horizontal direction.
398 * @property animHoriz
402 YAHOO.widget.AutoComplete.prototype.animHoriz = false;
405 * Whether or not to animate the expansion/collapse of the results container in the
406 * vertical direction.
412 YAHOO.widget.AutoComplete.prototype.animVert = true;
415 * Speed of container expand/collapse animation, in seconds..
417 * @property animSpeed
421 YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
424 * Whether or not to force the user's selection to match one of the query
425 * results. Enabling this feature essentially transforms the input field into a
426 * <select> field. This feature is not recommended with delimiter character(s)
429 * @property forceSelection
433 YAHOO.widget.AutoComplete.prototype.forceSelection = false;
436 * Whether or not to allow browsers to cache user-typed input in the input
437 * field. Disabling this feature will prevent the widget from setting the
438 * autocomplete="off" on the input field. When autocomplete="off"
439 * and users click the back button after form submission, user-typed input can
440 * be prefilled by the browser from its cache. This caching of user input may
441 * not be desired for sensitive data, such as credit card numbers, in which
442 * case, implementers should consider setting allowBrowserAutocomplete to false.
444 * @property allowBrowserAutocomplete
448 YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
451 * Enabling this feature prevents the toggling of the container to a collapsed state.
452 * Setting to true does not automatically trigger the opening of the container.
453 * Implementers are advised to pre-load the container with an explicit "sendQuery()" call.
455 * @property alwaysShowContainer
459 YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
462 * Whether or not to use an iFrame to layer over Windows form elements in
463 * IE. Set to true only when the results container will be on top of a
464 * <select> field in IE and thus exposed to the IE z-index bug (i.e.,
467 * @property useIFrame
471 YAHOO.widget.AutoComplete.prototype.useIFrame = false;
474 * Whether or not the results container should have a shadow.
476 * @property useShadow
480 YAHOO.widget.AutoComplete.prototype.useShadow = false;
483 * Whether or not the input field should be updated with selections.
485 * @property suppressInputUpdate
489 YAHOO.widget.AutoComplete.prototype.suppressInputUpdate = false;
492 * For backward compatibility to pre-2.6.0 formatResults() signatures, setting
493 * resultsTypeList to true will take each object literal result returned by
494 * DataSource and flatten into an array.
496 * @property resultTypeList
500 YAHOO.widget.AutoComplete.prototype.resultTypeList = true;
503 * For XHR DataSources, AutoComplete will automatically insert a "?" between the server URI and
504 * the "query" param/value pair. To prevent this behavior, implementers should
505 * set this value to false. To more fully customize the query syntax, implementers
506 * should override the generateRequest() method.
508 * @property queryQuestionMark
512 YAHOO.widget.AutoComplete.prototype.queryQuestionMark = true;
514 /////////////////////////////////////////////////////////////////////////////
518 /////////////////////////////////////////////////////////////////////////////
521 * Public accessor to the unique name of the AutoComplete instance.
524 * @return {String} Unique name of the AutoComplete instance.
526 YAHOO.widget.AutoComplete.prototype.toString = function() {
527 return "AutoComplete " + this._sName;
531 * Returns DOM reference to input element.
534 * @return {HTMLELement} DOM reference to input element.
536 YAHOO.widget.AutoComplete.prototype.getInputEl = function() {
537 return this._elTextbox;
541 * Returns DOM reference to container element.
543 * @method getContainerEl
544 * @return {HTMLELement} DOM reference to container element.
546 YAHOO.widget.AutoComplete.prototype.getContainerEl = function() {
547 return this._elContainer;
551 * Returns true if widget instance is currently focused.
554 * @return {Boolean} Returns true if widget instance is currently focused.
556 YAHOO.widget.AutoComplete.prototype.isFocused = function() {
557 return (this._bFocused === null) ? false : this._bFocused;
561 * Returns true if container is in an expanded state, false otherwise.
563 * @method isContainerOpen
564 * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
566 YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
567 return this._bContainerOpen;
571 * Public accessor to the <ul> element that displays query results within the results container.
574 * @return {HTMLElement[]} Reference to <ul> element within the results container.
576 YAHOO.widget.AutoComplete.prototype.getListEl = function() {
581 * Public accessor to the matching string associated with a given <li> result.
583 * @method getListItemMatch
584 * @param elListItem {HTMLElement} Reference to <LI> element.
585 * @return {String} Matching string.
587 YAHOO.widget.AutoComplete.prototype.getListItemMatch = function(elListItem) {
588 if(elListItem._sResultMatch) {
589 return elListItem._sResultMatch;
597 * Public accessor to the result data associated with a given <li> result.
599 * @method getListItemData
600 * @param elListItem {HTMLElement} Reference to <LI> element.
601 * @return {Object} Result data.
603 YAHOO.widget.AutoComplete.prototype.getListItemData = function(elListItem) {
604 if(elListItem._oResultData) {
605 return elListItem._oResultData;
613 * Public accessor to the index of the associated with a given <li> result.
615 * @method getListItemIndex
616 * @param elListItem {HTMLElement} Reference to <LI> element.
617 * @return {Number} Index.
619 YAHOO.widget.AutoComplete.prototype.getListItemIndex = function(elListItem) {
620 if(YAHOO.lang.isNumber(elListItem._nItemIndex)) {
621 return elListItem._nItemIndex;
629 * Sets HTML markup for the results container header. This markup will be
630 * inserted within a <div> tag with a class of "yui-ac-hd".
633 * @param sHeader {String} HTML markup for results container header.
635 YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
637 var elHeader = this._elHeader;
639 elHeader.innerHTML = sHeader;
640 elHeader.style.display = "block";
643 elHeader.innerHTML = "";
644 elHeader.style.display = "none";
650 * Sets HTML markup for the results container footer. This markup will be
651 * inserted within a <div> tag with a class of "yui-ac-ft".
654 * @param sFooter {String} HTML markup for results container footer.
656 YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
658 var elFooter = this._elFooter;
660 elFooter.innerHTML = sFooter;
661 elFooter.style.display = "block";
664 elFooter.innerHTML = "";
665 elFooter.style.display = "none";
671 * Sets HTML markup for the results container body. This markup will be
672 * inserted within a <div> tag with a class of "yui-ac-bd".
675 * @param sBody {String} HTML markup for results container body.
677 YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
679 var elBody = this._elBody;
680 YAHOO.util.Event.purgeElement(elBody, true);
682 elBody.innerHTML = sBody;
683 elBody.style.display = "block";
686 elBody.innerHTML = "";
687 elBody.style.display = "none";
694 * A function that converts an AutoComplete query into a request value which is then
695 * passed to the DataSource's sendRequest method in order to retrieve data for
696 * the query. By default, returns a String with the syntax: "query={query}"
697 * Implementers can customize this method for custom request syntaxes.
699 * @method generateRequest
700 * @param sQuery {String} Query string
701 * @return {MIXED} Request
703 YAHOO.widget.AutoComplete.prototype.generateRequest = function(sQuery) {
704 var dataType = this.dataSource.dataType;
706 // Transform query string in to a request for remote data
707 // By default, local data doesn't need a transformation, just passes along the query as is.
708 if(dataType === YAHOO.util.DataSourceBase.TYPE_XHR) {
709 // By default, XHR GET requests look like "{scriptURI}?{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
710 if(!this.dataSource.connMethodPost) {
711 sQuery = (this.queryQuestionMark ? "?" : "") + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery +
712 (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
714 // By default, XHR POST bodies are sent to the {scriptURI} like "{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
716 sQuery = (this.dataSource.scriptQueryParam || "query") + "=" + sQuery +
717 (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
720 // By default, remote script node requests look like "{scriptURI}&{scriptCallbackParam}={callbackString}&{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
721 else if(dataType === YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE) {
722 sQuery = "&" + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery +
723 (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
730 * Makes query request to the DataSource.
733 * @param sQuery {String} Query string.
735 YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
736 // Reset focus for a new interaction
737 this._bFocused = null;
739 // Adjust programatically sent queries to look like they were input by user
740 // when delimiters are enabled
741 var newQuery = (this.delimChar) ? this._elTextbox.value + sQuery : sQuery;
742 this._sendQuery(newQuery);
746 * Collapses container.
748 * @method collapseContainer
750 YAHOO.widget.AutoComplete.prototype.collapseContainer = function() {
751 this._toggleContainer(false);
755 * Handles subset matching for when queryMatchSubset is enabled.
757 * @method getSubsetMatches
758 * @param sQuery {String} Query string.
759 * @return {Object} oParsedResponse or null.
761 YAHOO.widget.AutoComplete.prototype.getSubsetMatches = function(sQuery) {
762 var subQuery, oCachedResponse, subRequest;
763 // Loop through substrings of each cached element's query property...
764 for(var i = sQuery.length; i >= this.minQueryLength ; i--) {
765 subRequest = this.generateRequest(sQuery.substr(0,i));
766 this.dataRequestEvent.fire(this, subQuery, subRequest);
767 YAHOO.log("Searching for query subset \"" + subQuery + "\" in cache", "info", this.toString());
769 // If a substring of the query is found in the cache
770 oCachedResponse = this.dataSource.getCachedResponse(subRequest);
771 if(oCachedResponse) {
772 YAHOO.log("Found match for query subset \"" + subQuery + "\": " + YAHOO.lang.dump(oCachedResponse), "info", this.toString());
773 return this.filterResults.apply(this.dataSource, [sQuery, oCachedResponse, oCachedResponse, {scope:this}]);
776 YAHOO.log("Did not find subset match for query subset \"" + sQuery + "\"" , "info", this.toString());
781 * Executed by DataSource (within DataSource scope via doBeforeParseData()) to
782 * handle responseStripAfter cleanup.
784 * @method preparseRawResponse
785 * @param sQuery {String} Query string.
786 * @return {Object} oParsedResponse or null.
788 YAHOO.widget.AutoComplete.prototype.preparseRawResponse = function(oRequest, oFullResponse, oCallback) {
789 var nEnd = ((this.responseStripAfter !== "") && (oFullResponse.indexOf)) ?
790 oFullResponse.indexOf(this.responseStripAfter) : -1;
792 oFullResponse = oFullResponse.substring(0,nEnd);
794 return oFullResponse;
798 * Executed by DataSource (within DataSource scope via doBeforeCallback()) to
799 * filter results through a simple client-side matching algorithm.
801 * @method filterResults
802 * @param sQuery {String} Original request.
803 * @param oFullResponse {Object} Full response object.
804 * @param oParsedResponse {Object} Parsed response object.
805 * @param oCallback {Object} Callback object.
806 * @return {Object} Filtered response object.
809 YAHOO.widget.AutoComplete.prototype.filterResults = function(sQuery, oFullResponse, oParsedResponse, oCallback) {
810 // If AC has passed a query string value back to itself, grab it
811 if(oCallback && oCallback.argument && oCallback.argument.query) {
812 sQuery = oCallback.argument.query;
815 // Only if a query string is available to match against
816 if(sQuery && sQuery !== "") {
817 // First make a copy of the oParseResponse
818 oParsedResponse = YAHOO.widget.AutoComplete._cloneObject(oParsedResponse);
820 var oAC = oCallback.scope,
822 allResults = oParsedResponse.results, // the array of results
823 filteredResults = [], // container for filtered results
825 bMatchCase = (oDS.queryMatchCase || oAC.queryMatchCase), // backward compat
826 bMatchContains = (oDS.queryMatchContains || oAC.queryMatchContains); // backward compat
828 // Loop through each result object...
829 for(var i = allResults.length-1; i >= 0; i--) {
830 var oResult = allResults[i];
832 // Grab the data to match against from the result object...
835 // Result object is a simple string already
836 if(YAHOO.lang.isString(oResult)) {
839 // Result object is an array of strings
840 else if(YAHOO.lang.isArray(oResult)) {
841 sResult = oResult[0];
844 // Result object is an object literal of strings
845 else if(this.responseSchema.fields) {
846 var key = this.responseSchema.fields[0].key || this.responseSchema.fields[0];
847 sResult = oResult[key];
849 // Backwards compatibility
851 sResult = oResult[this.key];
854 if(YAHOO.lang.isString(sResult)) {
856 var sKeyIndex = (bMatchCase) ?
857 sResult.indexOf(decodeURIComponent(sQuery)) :
858 sResult.toLowerCase().indexOf(decodeURIComponent(sQuery).toLowerCase());
860 // A STARTSWITH match is when the query is found at the beginning of the key string...
861 if((!bMatchContains && (sKeyIndex === 0)) ||
862 // A CONTAINS match is when the query is found anywhere within the key string...
863 (bMatchContains && (sKeyIndex > -1))) {
865 filteredResults.unshift(oResult);
869 oParsedResponse.results = filteredResults;
870 YAHOO.log("Filtered " + filteredResults.length + " results against query \"" + sQuery + "\": " + YAHOO.lang.dump(filteredResults), "info", this.toString());
873 YAHOO.log("Did not filter results against query", "info", this.toString());
876 return oParsedResponse;
880 * Handles response for display. This is the callback function method passed to
881 * YAHOO.util.DataSourceBase#sendRequest so results from the DataSource are
882 * returned to the AutoComplete instance.
884 * @method handleResponse
885 * @param sQuery {String} Original request.
886 * @param oResponse {Object} Response object.
887 * @param oPayload {MIXED} (optional) Additional argument(s)
889 YAHOO.widget.AutoComplete.prototype.handleResponse = function(sQuery, oResponse, oPayload) {
890 if((this instanceof YAHOO.widget.AutoComplete) && this._sName) {
891 this._populateList(sQuery, oResponse, oPayload);
896 * Overridable method called before container is loaded with result data.
898 * @method doBeforeLoadData
899 * @param sQuery {String} Original request.
900 * @param oResponse {Object} Response object.
901 * @param oPayload {MIXED} (optional) Additional argument(s)
902 * @return {Boolean} Return true to continue loading data, false to cancel.
904 YAHOO.widget.AutoComplete.prototype.doBeforeLoadData = function(sQuery, oResponse, oPayload) {
909 * Overridable method that returns HTML markup for one result to be populated
910 * as innerHTML of an <LI> element.
912 * @method formatResult
913 * @param oResultData {Object} Result data object.
914 * @param sQuery {String} The corresponding query string.
915 * @param sResultMatch {HTMLElement} The current query string.
916 * @return {String} HTML markup of formatted result data.
918 YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultData, sQuery, sResultMatch) {
919 var sMarkup = (sResultMatch) ? sResultMatch : "";
924 * Overridable method called before container expands allows implementers to access data
927 * @method doBeforeExpandContainer
928 * @param elTextbox {HTMLElement} The text input box.
929 * @param elContainer {HTMLElement} The container element.
930 * @param sQuery {String} The query string.
931 * @param aResults {Object[]} An array of query results.
932 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
934 YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
940 * Nulls out the entire AutoComplete instance and related objects, removes attached
941 * event listeners, and clears out DOM elements inside the container. After
942 * calling this method, the instance reference should be expliclitly nulled by
943 * implementer, as in myAutoComplete = null. Use with caution!
947 YAHOO.widget.AutoComplete.prototype.destroy = function() {
948 var instanceName = this.toString();
949 var elInput = this._elTextbox;
950 var elContainer = this._elContainer;
952 // Unhook custom events
953 this.textboxFocusEvent.unsubscribeAll();
954 this.textboxKeyEvent.unsubscribeAll();
955 this.dataRequestEvent.unsubscribeAll();
956 this.dataReturnEvent.unsubscribeAll();
957 this.dataErrorEvent.unsubscribeAll();
958 this.containerPopulateEvent.unsubscribeAll();
959 this.containerExpandEvent.unsubscribeAll();
960 this.typeAheadEvent.unsubscribeAll();
961 this.itemMouseOverEvent.unsubscribeAll();
962 this.itemMouseOutEvent.unsubscribeAll();
963 this.itemArrowToEvent.unsubscribeAll();
964 this.itemArrowFromEvent.unsubscribeAll();
965 this.itemSelectEvent.unsubscribeAll();
966 this.unmatchedItemSelectEvent.unsubscribeAll();
967 this.selectionEnforceEvent.unsubscribeAll();
968 this.containerCollapseEvent.unsubscribeAll();
969 this.textboxBlurEvent.unsubscribeAll();
970 this.textboxChangeEvent.unsubscribeAll();
973 YAHOO.util.Event.purgeElement(elInput, true);
974 YAHOO.util.Event.purgeElement(elContainer, true);
976 // Remove DOM elements
977 elContainer.innerHTML = "";
980 for(var key in this) {
981 if(YAHOO.lang.hasOwnProperty(this, key)) {
986 YAHOO.log("AutoComplete instance destroyed: " + instanceName);
989 /////////////////////////////////////////////////////////////////////////////
993 /////////////////////////////////////////////////////////////////////////////
996 * Fired when the input field receives focus.
998 * @event textboxFocusEvent
999 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1001 YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
1004 * Fired when the input field receives key input.
1006 * @event textboxKeyEvent
1007 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1008 * @param nKeycode {Number} The keycode number.
1010 YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
1013 * Fired when the AutoComplete instance makes a request to the DataSource.
1015 * @event dataRequestEvent
1016 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1017 * @param sQuery {String} The query string.
1018 * @param oRequest {Object} The request.
1020 YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
1023 * Fired when the AutoComplete instance receives query results from the data
1026 * @event dataReturnEvent
1027 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1028 * @param sQuery {String} The query string.
1029 * @param aResults {Object[]} Results array.
1031 YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
1034 * Fired when the AutoComplete instance does not receive query results from the
1035 * DataSource due to an error.
1037 * @event dataErrorEvent
1038 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1039 * @param sQuery {String} The query string.
1041 YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
1044 * Fired when the results container is populated.
1046 * @event containerPopulateEvent
1047 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1049 YAHOO.widget.AutoComplete.prototype.containerPopulateEvent = null;
1052 * Fired when the results container is expanded.
1054 * @event containerExpandEvent
1055 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1057 YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
1060 * Fired when the input field has been prefilled by the type-ahead
1063 * @event typeAheadEvent
1064 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1065 * @param sQuery {String} The query string.
1066 * @param sPrefill {String} The prefill string.
1068 YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
1071 * Fired when result item has been moused over.
1073 * @event itemMouseOverEvent
1074 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1075 * @param elItem {HTMLElement} The <li> element item moused to.
1077 YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
1080 * Fired when result item has been moused out.
1082 * @event itemMouseOutEvent
1083 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1084 * @param elItem {HTMLElement} The <li> element item moused from.
1086 YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
1089 * Fired when result item has been arrowed to.
1091 * @event itemArrowToEvent
1092 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1093 * @param elItem {HTMLElement} The <li> element item arrowed to.
1095 YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
1098 * Fired when result item has been arrowed away from.
1100 * @event itemArrowFromEvent
1101 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1102 * @param elItem {HTMLElement} The <li> element item arrowed from.
1104 YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
1107 * Fired when an item is selected via mouse click, ENTER key, or TAB key.
1109 * @event itemSelectEvent
1110 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1111 * @param elItem {HTMLElement} The selected <li> element item.
1112 * @param oData {Object} The data returned for the item, either as an object,
1113 * or mapped from the schema into an array.
1115 YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
1118 * Fired when a user selection does not match any of the displayed result items.
1120 * @event unmatchedItemSelectEvent
1121 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1122 * @param sSelection {String} The selected string.
1124 YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
1127 * Fired if forceSelection is enabled and the user's input has been cleared
1128 * because it did not match one of the returned query results.
1130 * @event selectionEnforceEvent
1131 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1132 * @param sClearedValue {String} The cleared value (including delimiters if applicable).
1134 YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
1137 * Fired when the results container is collapsed.
1139 * @event containerCollapseEvent
1140 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1142 YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
1145 * Fired when the input field loses focus.
1147 * @event textboxBlurEvent
1148 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1150 YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
1153 * Fired when the input field value has changed when it loses focus.
1155 * @event textboxChangeEvent
1156 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1158 YAHOO.widget.AutoComplete.prototype.textboxChangeEvent = null;
1160 /////////////////////////////////////////////////////////////////////////////
1162 // Private member variables
1164 /////////////////////////////////////////////////////////////////////////////
1167 * Internal class variable to index multiple AutoComplete instances.
1174 YAHOO.widget.AutoComplete._nIndex = 0;
1177 * Name of AutoComplete instance.
1183 YAHOO.widget.AutoComplete.prototype._sName = null;
1186 * Text input field DOM element.
1188 * @property _elTextbox
1192 YAHOO.widget.AutoComplete.prototype._elTextbox = null;
1195 * Container DOM element.
1197 * @property _elContainer
1201 YAHOO.widget.AutoComplete.prototype._elContainer = null;
1204 * Reference to content element within container element.
1206 * @property _elContent
1210 YAHOO.widget.AutoComplete.prototype._elContent = null;
1213 * Reference to header element within content element.
1215 * @property _elHeader
1219 YAHOO.widget.AutoComplete.prototype._elHeader = null;
1222 * Reference to body element within content element.
1228 YAHOO.widget.AutoComplete.prototype._elBody = null;
1231 * Reference to footer element within content element.
1233 * @property _elFooter
1237 YAHOO.widget.AutoComplete.prototype._elFooter = null;
1240 * Reference to shadow element within container element.
1242 * @property _elShadow
1246 YAHOO.widget.AutoComplete.prototype._elShadow = null;
1249 * Reference to iframe element within container element.
1251 * @property _elIFrame
1255 YAHOO.widget.AutoComplete.prototype._elIFrame = null;
1258 * Whether or not the input field is currently in focus. If query results come back
1259 * but the user has already moved on, do not proceed with auto complete behavior.
1261 * @property _bFocused
1265 YAHOO.widget.AutoComplete.prototype._bFocused = null;
1268 * Animation instance for container expand/collapse.
1274 YAHOO.widget.AutoComplete.prototype._oAnim = null;
1277 * Whether or not the results container is currently open.
1279 * @property _bContainerOpen
1283 YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
1286 * Whether or not the mouse is currently over the results
1287 * container. This is necessary in order to prevent clicks on container items
1288 * from being text input field blur events.
1290 * @property _bOverContainer
1294 YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
1297 * Internal reference to <ul> elements that contains query results within the
1298 * results container.
1304 YAHOO.widget.AutoComplete.prototype._elList = null;
1307 * Array of <li> elements references that contain query results within the
1308 * results container.
1310 * @property _aListItemEls
1311 * @type HTMLElement[]
1314 //YAHOO.widget.AutoComplete.prototype._aListItemEls = null;
1317 * Number of <li> elements currently displayed in results container.
1319 * @property _nDisplayedItems
1323 YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
1326 * Internal count of <li> elements displayed and hidden in results container.
1328 * @property _maxResultsDisplayed
1332 //YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
1335 * Current query string
1337 * @property _sCurQuery
1341 YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
1344 * Selections from previous queries (for saving delimited queries).
1346 * @property _sPastSelections
1351 YAHOO.widget.AutoComplete.prototype._sPastSelections = "";
1354 * Stores initial input value used to determine if textboxChangeEvent should be fired.
1356 * @property _sInitInputValue
1360 YAHOO.widget.AutoComplete.prototype._sInitInputValue = null;
1363 * Pointer to the currently highlighted <li> element in the container.
1365 * @property _elCurListItem
1369 YAHOO.widget.AutoComplete.prototype._elCurListItem = null;
1372 * Whether or not an item has been selected since the container was populated
1373 * with results. Reset to false by _populateList, and set to true when item is
1376 * @property _bItemSelected
1380 YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
1383 * Key code of the last key pressed in textbox.
1385 * @property _nKeyCode
1389 YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
1394 * @property _nDelayID
1398 YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
1401 * TypeAhead delay timeout ID.
1403 * @property _nTypeAheadDelayID
1407 YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID = -1;
1410 * Src to iFrame used when useIFrame = true. Supports implementations over SSL
1413 * @property _iFrameSrc
1417 YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
1420 * For users typing via certain IMEs, queries must be triggered by intervals,
1421 * since key events yet supported across all browsers for all IMEs.
1423 * @property _queryInterval
1427 YAHOO.widget.AutoComplete.prototype._queryInterval = null;
1430 * Internal tracker to last known textbox value, used to determine whether or not
1431 * to trigger a query via interval for certain IME users.
1433 * @event _sLastTextboxValue
1437 YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
1439 /////////////////////////////////////////////////////////////////////////////
1443 /////////////////////////////////////////////////////////////////////////////
1446 * Updates and validates latest public config properties.
1448 * @method __initProps
1451 YAHOO.widget.AutoComplete.prototype._initProps = function() {
1452 // Correct any invalid values
1453 var minQueryLength = this.minQueryLength;
1454 if(!YAHOO.lang.isNumber(minQueryLength)) {
1455 this.minQueryLength = 1;
1457 var maxResultsDisplayed = this.maxResultsDisplayed;
1458 if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
1459 this.maxResultsDisplayed = 10;
1461 var queryDelay = this.queryDelay;
1462 if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
1463 this.queryDelay = 0.2;
1465 var typeAheadDelay = this.typeAheadDelay;
1466 if(!YAHOO.lang.isNumber(typeAheadDelay) || (typeAheadDelay < 0)) {
1467 this.typeAheadDelay = 0.2;
1469 var delimChar = this.delimChar;
1470 if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
1471 this.delimChar = [delimChar];
1473 else if(!YAHOO.lang.isArray(delimChar)) {
1474 this.delimChar = null;
1476 var animSpeed = this.animSpeed;
1477 if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
1478 if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
1479 this.animSpeed = 0.3;
1482 this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
1485 this._oAnim.duration = this.animSpeed;
1488 if(this.forceSelection && delimChar) {
1489 YAHOO.log("The forceSelection feature has been enabled with delimChar defined.","warn", this.toString());
1494 * Initializes the results container helpers if they are enabled and do
1497 * @method _initContainerHelperEls
1500 YAHOO.widget.AutoComplete.prototype._initContainerHelperEls = function() {
1501 if(this.useShadow && !this._elShadow) {
1502 var elShadow = document.createElement("div");
1503 elShadow.className = "yui-ac-shadow";
1504 elShadow.style.width = 0;
1505 elShadow.style.height = 0;
1506 this._elShadow = this._elContainer.appendChild(elShadow);
1508 if(this.useIFrame && !this._elIFrame) {
1509 var elIFrame = document.createElement("iframe");
1510 elIFrame.src = this._iFrameSrc;
1511 elIFrame.frameBorder = 0;
1512 elIFrame.scrolling = "no";
1513 elIFrame.style.position = "absolute";
1514 elIFrame.style.width = 0;
1515 elIFrame.style.height = 0;
1516 elIFrame.tabIndex = -1;
1517 elIFrame.style.padding = 0;
1518 this._elIFrame = this._elContainer.appendChild(elIFrame);
1523 * Initializes the results container once at object creation
1525 * @method _initContainerEl
1528 YAHOO.widget.AutoComplete.prototype._initContainerEl = function() {
1529 YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
1531 if(!this._elContent) {
1532 // The elContent div is assigned DOM listeners and
1533 // helps size the iframe and shadow properly
1534 var elContent = document.createElement("div");
1535 elContent.className = "yui-ac-content";
1536 elContent.style.display = "none";
1538 this._elContent = this._elContainer.appendChild(elContent);
1540 var elHeader = document.createElement("div");
1541 elHeader.className = "yui-ac-hd";
1542 elHeader.style.display = "none";
1543 this._elHeader = this._elContent.appendChild(elHeader);
1545 var elBody = document.createElement("div");
1546 elBody.className = "yui-ac-bd";
1547 this._elBody = this._elContent.appendChild(elBody);
1549 var elFooter = document.createElement("div");
1550 elFooter.className = "yui-ac-ft";
1551 elFooter.style.display = "none";
1552 this._elFooter = this._elContent.appendChild(elFooter);
1555 YAHOO.log("Could not initialize the container","warn",this.toString());
1560 * Clears out contents of container body and creates up to
1561 * YAHOO.widget.AutoComplete#maxResultsDisplayed <li> elements in an
1562 * <ul> element.
1564 * @method _initListEl
1567 YAHOO.widget.AutoComplete.prototype._initListEl = function() {
1568 var nListLength = this.maxResultsDisplayed;
1570 var elList = this._elList || document.createElement("ul");
1572 while(elList.childNodes.length < nListLength) {
1573 elListItem = document.createElement("li");
1574 elListItem.style.display = "none";
1575 elListItem._nItemIndex = elList.childNodes.length;
1576 elList.appendChild(elListItem);
1579 var elBody = this._elBody;
1580 YAHOO.util.Event.purgeElement(elBody, true);
1581 elBody.innerHTML = "";
1582 this._elList = elBody.appendChild(elList);
1588 * Focuses input field.
1593 YAHOO.widget.AutoComplete.prototype._focus = function() {
1594 // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
1596 setTimeout(function() {
1598 oSelf._elTextbox.focus();
1606 * Enables interval detection for IME support.
1608 * @method _enableIntervalDetection
1611 YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() {
1613 if(!oSelf._queryInterval && oSelf.queryInterval) {
1614 oSelf._queryInterval = setInterval(function() { oSelf._onInterval(); }, oSelf.queryInterval);
1615 YAHOO.log("Interval set", "info", this.toString());
1620 * Enables query triggers based on text input detection by intervals (rather
1621 * than by key events).
1623 * @method _onInterval
1626 YAHOO.widget.AutoComplete.prototype._onInterval = function() {
1627 var currValue = this._elTextbox.value;
1628 var lastValue = this._sLastTextboxValue;
1629 if(currValue != lastValue) {
1630 this._sLastTextboxValue = currValue;
1631 this._sendQuery(currValue);
1636 * Cancels text input detection by intervals.
1638 * @method _clearInterval
1639 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1642 YAHOO.widget.AutoComplete.prototype._clearInterval = function() {
1643 if(this._queryInterval) {
1644 clearInterval(this._queryInterval);
1645 this._queryInterval = null;
1646 YAHOO.log("Interval cleared", "info", this.toString());
1651 * Whether or not key is functional or should be ignored. Note that the right
1652 * arrow key is NOT an ignored key since it triggers queries for certain intl
1655 * @method _isIgnoreKey
1656 * @param nKeycode {Number} Code of key pressed.
1657 * @return {Boolean} True if key should be ignored, false otherwise.
1660 YAHOO.widget.AutoComplete.prototype._isIgnoreKey = function(nKeyCode) {
1661 if((nKeyCode == 9) || (nKeyCode == 13) || // tab, enter
1662 (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl
1663 (nKeyCode >= 18 && nKeyCode <= 20) || // alt, pause/break,caps lock
1664 (nKeyCode == 27) || // esc
1665 (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
1666 /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
1667 (nKeyCode == 40) || // down*/
1668 (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
1669 (nKeyCode >= 44 && nKeyCode <= 45) || // print screen,insert
1670 (nKeyCode == 229) // Bug 2041973: Korean XP fires 2 keyup events, the key and 229
1678 * Makes query request to the DataSource.
1680 * @method _sendQuery
1681 * @param sQuery {String} Query string.
1684 YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
1685 // Widget has been effectively turned off
1686 if(this.minQueryLength < 0) {
1687 this._toggleContainer(false);
1688 YAHOO.log("Property minQueryLength is less than 0", "info", this.toString());
1691 // Delimiter has been enabled
1692 if(this.delimChar) {
1693 var extraction = this._extractQuery(sQuery);
1694 // Here is the query itself
1695 sQuery = extraction.query;
1696 // ...and save the rest of the string for later
1697 this._sPastSelections = extraction.previous;
1700 // Don't search queries that are too short
1701 if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
1702 if(this._nDelayID != -1) {
1703 clearTimeout(this._nDelayID);
1705 this._toggleContainer(false);
1706 YAHOO.log("Query \"" + sQuery + "\" is too short", "info", this.toString());
1710 sQuery = encodeURIComponent(sQuery);
1711 this._nDelayID = -1; // Reset timeout ID because request is being made
1714 if(this.dataSource.queryMatchSubset || this.queryMatchSubset) { // backward compat
1715 var oResponse = this.getSubsetMatches(sQuery);
1717 this.handleResponse(sQuery, oResponse, {query: sQuery});
1722 if(this.responseStripAfter) {
1723 this.dataSource.doBeforeParseData = this.preparseRawResponse;
1725 if(this.applyLocalFilter) {
1726 this.dataSource.doBeforeCallback = this.filterResults;
1729 var sRequest = this.generateRequest(sQuery);
1730 this.dataRequestEvent.fire(this, sQuery, sRequest);
1731 YAHOO.log("Sending query \"" + sRequest + "\"", "info", this.toString());
1733 this.dataSource.sendRequest(sRequest, {
1734 success : this.handleResponse,
1735 failure : this.handleResponse,
1744 * Populates the array of <li> elements in the container with query
1747 * @method _populateList
1748 * @param sQuery {String} Original request.
1749 * @param oResponse {Object} Response object.
1750 * @param oPayload {MIXED} (optional) Additional argument(s)
1753 YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, oResponse, oPayload) {
1754 // Clear previous timeout
1755 if(this._nTypeAheadDelayID != -1) {
1756 clearTimeout(this._nTypeAheadDelayID);
1759 sQuery = (oPayload && oPayload.query) ? oPayload.query : sQuery;
1761 // Pass data through abstract method for any transformations
1762 var ok = this.doBeforeLoadData(sQuery, oResponse, oPayload);
1765 if(ok && !oResponse.error) {
1766 this.dataReturnEvent.fire(this, sQuery, oResponse.results);
1768 // Continue only if instance is still focused (i.e., user hasn't already moved on)
1769 // Null indicates initialized state, which is ok too
1770 if(this._bFocused || (this._bFocused === null)) {
1772 //TODO: is this still necessary?
1773 /*var isOpera = (YAHOO.env.ua.opera);
1774 var contentStyle = this._elContent.style;
1775 contentStyle.width = (!isOpera) ? null : "";
1776 contentStyle.height = (!isOpera) ? null : "";*/
1778 // Store state for this interaction
1779 var sCurQuery = decodeURIComponent(sQuery);
1780 this._sCurQuery = sCurQuery;
1781 this._bItemSelected = false;
1783 var allResults = oResponse.results,
1784 nItemsToShow = Math.min(allResults.length,this.maxResultsDisplayed),
1785 sMatchKey = (this.dataSource.responseSchema.fields) ?
1786 (this.dataSource.responseSchema.fields[0].key || this.dataSource.responseSchema.fields[0]) : 0;
1788 if(nItemsToShow > 0) {
1789 // Make sure container and helpers are ready to go
1790 if(!this._elList || (this._elList.childNodes.length < nItemsToShow)) {
1793 this._initContainerHelperEls();
1795 var allListItemEls = this._elList.childNodes;
1796 // Fill items with data from the bottom up
1797 for(var i = nItemsToShow-1; i >= 0; i--) {
1798 var elListItem = allListItemEls[i],
1799 oResult = allResults[i];
1801 // Backward compatibility
1802 if(this.resultTypeList) {
1803 // Results need to be converted back to an array
1805 // Match key is first
1806 aResult[0] = (YAHOO.lang.isString(oResult)) ? oResult : oResult[sMatchKey] || oResult[this.key];
1807 // Add additional data to the result array
1808 var fields = this.dataSource.responseSchema.fields;
1809 if(YAHOO.lang.isArray(fields) && (fields.length > 1)) {
1810 for(var k=1, len=fields.length; k<len; k++) {
1811 aResult[aResult.length] = oResult[fields[k].key || fields[k]];
1814 // No specific fields defined, so pass along entire data object
1817 if(YAHOO.lang.isArray(oResult)) {
1821 else if(YAHOO.lang.isString(oResult)) {
1822 aResult = [oResult];
1826 aResult[1] = oResult;
1832 // The matching value, including backward compatibility for array format and safety net
1833 elListItem._sResultMatch = (YAHOO.lang.isString(oResult)) ? oResult : (YAHOO.lang.isArray(oResult)) ? oResult[0] : (oResult[sMatchKey] || "");
1834 elListItem._oResultData = oResult; // Additional data
1835 elListItem.innerHTML = this.formatResult(oResult, sCurQuery, elListItem._sResultMatch);
1836 elListItem.style.display = "";
1839 // Clear out extraneous items
1840 if(nItemsToShow < allListItemEls.length) {
1842 for(var j = allListItemEls.length-1; j >= nItemsToShow; j--) {
1843 extraListItem = allListItemEls[j];
1844 extraListItem.style.display = "none";
1848 this._nDisplayedItems = nItemsToShow;
1850 this.containerPopulateEvent.fire(this, sQuery, allResults);
1852 // Highlight the first item
1853 if(this.autoHighlight) {
1854 var elFirstListItem = this._elList.firstChild;
1855 this._toggleHighlight(elFirstListItem,"to");
1856 this.itemArrowToEvent.fire(this, elFirstListItem);
1857 YAHOO.log("Arrowed to first item", "info", this.toString());
1858 this._typeAhead(elFirstListItem,sQuery);
1860 // Unhighlight any previous time
1862 this._toggleHighlight(this._elCurListItem,"from");
1865 // Expand the container
1866 ok = this.doBeforeExpandContainer(this._elTextbox, this._elContainer, sQuery, allResults);
1867 this._toggleContainer(ok);
1870 this._toggleContainer(false);
1873 YAHOO.log("Container populated with " + nItemsToShow + " list items", "info", this.toString());
1879 this.dataErrorEvent.fire(this, sQuery);
1882 YAHOO.log("Could not populate list", "info", this.toString());
1886 * When forceSelection is true and the user attempts
1887 * leave the text input box without selecting an item from the query results,
1888 * the user selection is cleared.
1890 * @method _clearSelection
1893 YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
1894 var extraction = (this.delimChar) ? this._extractQuery(this._elTextbox.value) :
1895 {previous:"",query:this._elTextbox.value};
1896 this._elTextbox.value = extraction.previous;
1897 this.selectionEnforceEvent.fire(this, extraction.query);
1898 YAHOO.log("Selection enforced", "info", this.toString());
1902 * Whether or not user-typed value in the text input box matches any of the
1905 * @method _textMatchesOption
1906 * @return {HTMLElement} Matching list item element if user-input text matches
1907 * a result, null otherwise.
1910 YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
1913 for(var i=0; i<this._nDisplayedItems; i++) {
1914 var elListItem = this._elList.childNodes[i];
1915 var sMatch = ("" + elListItem._sResultMatch).toLowerCase();
1916 if(sMatch == this._sCurQuery.toLowerCase()) {
1917 elMatch = elListItem;
1925 * Updates in the text input box with the first query result as the user types,
1926 * selecting the substring that the user has not typed.
1928 * @method _typeAhead
1929 * @param elListItem {HTMLElement} The <li> element item whose data populates the input field.
1930 * @param sQuery {String} Query string.
1933 YAHOO.widget.AutoComplete.prototype._typeAhead = function(elListItem, sQuery) {
1934 // Don't typeAhead if turned off or is backspace
1935 if(!this.typeAhead || (this._nKeyCode == 8)) {
1940 elTextbox = this._elTextbox;
1942 // Only if text selection is supported
1943 if(elTextbox.setSelectionRange || elTextbox.createTextRange) {
1944 // Set and store timeout for this typeahead
1945 this._nTypeAheadDelayID = setTimeout(function() {
1946 // Select the portion of text that the user has not typed
1947 var nStart = elTextbox.value.length; // any saved queries plus what user has typed
1948 oSelf._updateValue(elListItem);
1949 var nEnd = elTextbox.value.length;
1950 oSelf._selectText(elTextbox,nStart,nEnd);
1951 var sPrefill = elTextbox.value.substr(nStart,nEnd);
1952 oSelf.typeAheadEvent.fire(oSelf,sQuery,sPrefill);
1953 YAHOO.log("Typeahead occured with prefill string \"" + sPrefill + "\"", "info", oSelf.toString());
1954 },(this.typeAheadDelay*1000));
1959 * Selects text in the input field.
1961 * @method _selectText
1962 * @param elTextbox {HTMLElement} Text input box element in which to select text.
1963 * @param nStart {Number} Starting index of text string to select.
1964 * @param nEnd {Number} Ending index of text selection.
1967 YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
1968 if(elTextbox.setSelectionRange) { // For Mozilla
1969 elTextbox.setSelectionRange(nStart,nEnd);
1971 else if(elTextbox.createTextRange) { // For IE
1972 var oTextRange = elTextbox.createTextRange();
1973 oTextRange.moveStart("character", nStart);
1974 oTextRange.moveEnd("character", nEnd-elTextbox.value.length);
1975 oTextRange.select();
1983 * Extracts rightmost query from delimited string.
1985 * @method _extractQuery
1986 * @param sQuery {String} String to parse
1987 * @return {Object} Object literal containing properties "query" and "previous".
1990 YAHOO.widget.AutoComplete.prototype._extractQuery = function(sQuery) {
1991 var aDelimChar = this.delimChar,
1993 nNewIndex, nQueryStart,
1994 i = aDelimChar.length-1,
1997 // Loop through all possible delimiters and find the rightmost one in the query
1998 // A " " may be a false positive if they are defined as delimiters AND
1999 // are used to separate delimited queries
2000 for(; i >= 0; i--) {
2001 nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
2002 if(nNewIndex > nDelimIndex) {
2003 nDelimIndex = nNewIndex;
2006 // If we think the last delimiter is a space (" "), make sure it is NOT
2007 // a false positive by also checking the char directly before it
2008 if(aDelimChar[i] == " ") {
2009 for (var j = aDelimChar.length-1; j >= 0; j--) {
2010 if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
2016 // A delimiter has been found in the query so extract the latest query from past selections
2017 if(nDelimIndex > -1) {
2018 nQueryStart = nDelimIndex + 1;
2019 // Trim any white space from the beginning...
2020 while(sQuery.charAt(nQueryStart) == " ") {
2023 // ...and save the rest of the string for later
2024 sPrevious = sQuery.substring(0,nQueryStart);
2025 // Here is the query itself
2026 sQuery = sQuery.substr(nQueryStart);
2028 // No delimiter found in the query, so there are no selections from past queries
2034 previous: sPrevious,
2040 * Syncs results container with its helpers.
2042 * @method _toggleContainerHelpers
2043 * @param bShow {Boolean} True if container is expanded, false if collapsed
2046 YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
2047 var width = this._elContent.offsetWidth + "px";
2048 var height = this._elContent.offsetHeight + "px";
2050 if(this.useIFrame && this._elIFrame) {
2051 var elIFrame = this._elIFrame;
2053 elIFrame.style.width = width;
2054 elIFrame.style.height = height;
2055 elIFrame.style.padding = "";
2056 YAHOO.log("Iframe expanded", "info", this.toString());
2059 elIFrame.style.width = 0;
2060 elIFrame.style.height = 0;
2061 elIFrame.style.padding = 0;
2062 YAHOO.log("Iframe collapsed", "info", this.toString());
2065 if(this.useShadow && this._elShadow) {
2066 var elShadow = this._elShadow;
2068 elShadow.style.width = width;
2069 elShadow.style.height = height;
2070 YAHOO.log("Shadow expanded", "info", this.toString());
2073 elShadow.style.width = 0;
2074 elShadow.style.height = 0;
2075 YAHOO.log("Shadow collapsed", "info", this.toString());
2081 * Animates expansion or collapse of the container.
2083 * @method _toggleContainer
2084 * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
2087 YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
2088 YAHOO.log("Toggling container " + ((bShow) ? "open" : "closed"), "info", this.toString());
2090 var elContainer = this._elContainer;
2092 // If implementer has container always open and it's already open, don't mess with it
2093 // Container is initialized with display "none" so it may need to be shown first time through
2094 if(this.alwaysShowContainer && this._bContainerOpen) {
2100 this._toggleHighlight(this._elCurListItem,"from");
2101 this._nDisplayedItems = 0;
2102 this._sCurQuery = null;
2104 // Container is already closed, so don't bother with changing the UI
2105 if(this._elContent.style.display == "none") {
2110 // If animation is enabled...
2111 var oAnim = this._oAnim;
2112 if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
2113 if(oAnim.isAnimated()) {
2117 // Clone container to grab current size offscreen
2118 var oClone = this._elContent.cloneNode(true);
2119 elContainer.appendChild(oClone);
2120 oClone.style.top = "-9000px";
2121 oClone.style.width = "";
2122 oClone.style.height = "";
2123 oClone.style.display = "";
2125 // Current size of the container is the EXPANDED size
2126 var wExp = oClone.offsetWidth;
2127 var hExp = oClone.offsetHeight;
2129 // Calculate COLLAPSED sizes based on horiz and vert anim
2130 var wColl = (this.animHoriz) ? 0 : wExp;
2131 var hColl = (this.animVert) ? 0 : hExp;
2133 // Set animation sizes
2134 oAnim.attributes = (bShow) ?
2135 {width: { to: wExp }, height: { to: hExp }} :
2136 {width: { to: wColl}, height: { to: hColl }};
2138 // If opening anew, set to a collapsed size...
2139 if(bShow && !this._bContainerOpen) {
2140 this._elContent.style.width = wColl+"px";
2141 this._elContent.style.height = hColl+"px";
2143 // Else, set it to its last known size.
2145 this._elContent.style.width = wExp+"px";
2146 this._elContent.style.height = hExp+"px";
2149 elContainer.removeChild(oClone);
2153 var onAnimComplete = function() {
2154 // Finish the collapse
2155 oAnim.onComplete.unsubscribeAll();
2158 oSelf._toggleContainerHelpers(true);
2159 oSelf._bContainerOpen = bShow;
2160 oSelf.containerExpandEvent.fire(oSelf);
2161 YAHOO.log("Container expanded", "info", oSelf.toString());
2164 oSelf._elContent.style.display = "none";
2165 oSelf._bContainerOpen = bShow;
2166 oSelf.containerCollapseEvent.fire(oSelf);
2167 YAHOO.log("Container collapsed", "info", oSelf.toString());
2171 // Display container and animate it
2172 this._toggleContainerHelpers(false); // Bug 1424486: Be early to hide, late to show;
2173 this._elContent.style.display = "";
2174 oAnim.onComplete.subscribe(onAnimComplete);
2177 // Else don't animate, just show or hide
2180 this._elContent.style.display = "";
2181 this._toggleContainerHelpers(true);
2182 this._bContainerOpen = bShow;
2183 this.containerExpandEvent.fire(this);
2184 YAHOO.log("Container expanded", "info", this.toString());
2187 this._toggleContainerHelpers(false);
2188 this._elContent.style.display = "none";
2189 this._bContainerOpen = bShow;
2190 this.containerCollapseEvent.fire(this);
2191 YAHOO.log("Container collapsed", "info", this.toString());
2198 * Toggles the highlight on or off for an item in the container, and also cleans
2199 * up highlighting of any previous item.
2201 * @method _toggleHighlight
2202 * @param elNewListItem {HTMLElement} The <li> element item to receive highlight behavior.
2203 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2206 YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(elNewListItem, sType) {
2208 var sHighlight = this.highlightClassName;
2209 if(this._elCurListItem) {
2210 // Remove highlight from old item
2211 YAHOO.util.Dom.removeClass(this._elCurListItem, sHighlight);
2212 this._elCurListItem = null;
2215 if((sType == "to") && sHighlight) {
2216 // Apply highlight to new item
2217 YAHOO.util.Dom.addClass(elNewListItem, sHighlight);
2218 this._elCurListItem = elNewListItem;
2224 * Toggles the pre-highlight on or off for an item in the container.
2226 * @method _togglePrehighlight
2227 * @param elNewListItem {HTMLElement} The <li> element item to receive highlight behavior.
2228 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2231 YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(elNewListItem, sType) {
2232 if(elNewListItem == this._elCurListItem) {
2236 var sPrehighlight = this.prehighlightClassName;
2237 if((sType == "mouseover") && sPrehighlight) {
2238 // Apply prehighlight to new item
2239 YAHOO.util.Dom.addClass(elNewListItem, sPrehighlight);
2242 // Remove prehighlight from old item
2243 YAHOO.util.Dom.removeClass(elNewListItem, sPrehighlight);
2248 * Updates the text input box value with selected query result. If a delimiter
2249 * has been defined, then the value gets appended with the delimiter.
2251 * @method _updateValue
2252 * @param elListItem {HTMLElement} The <li> element item with which to update the value.
2255 YAHOO.widget.AutoComplete.prototype._updateValue = function(elListItem) {
2256 if(!this.suppressInputUpdate) {
2257 var elTextbox = this._elTextbox;
2258 var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
2259 var sResultMatch = elListItem._sResultMatch;
2261 // Calculate the new value
2264 // Preserve selections from past queries
2265 sNewValue = this._sPastSelections;
2266 // Add new selection plus delimiter
2267 sNewValue += sResultMatch + sDelimChar;
2268 if(sDelimChar != " ") {
2273 sNewValue = sResultMatch;
2276 // Update input field
2277 elTextbox.value = sNewValue;
2279 // Scroll to bottom of textarea if necessary
2280 if(elTextbox.type == "textarea") {
2281 elTextbox.scrollTop = elTextbox.scrollHeight;
2284 // Move cursor to end
2285 var end = elTextbox.value.length;
2286 this._selectText(elTextbox,end,end);
2288 this._elCurListItem = elListItem;
2293 * Selects a result item from the container
2295 * @method _selectItem
2296 * @param elListItem {HTMLElement} The selected <li> element item.
2299 YAHOO.widget.AutoComplete.prototype._selectItem = function(elListItem) {
2300 this._bItemSelected = true;
2301 this._updateValue(elListItem);
2302 this._sPastSelections = this._elTextbox.value;
2303 this._clearInterval();
2304 this.itemSelectEvent.fire(this, elListItem, elListItem._oResultData);
2305 YAHOO.log("Item selected: " + YAHOO.lang.dump(elListItem._oResultData), "info", this.toString());
2306 this._toggleContainer(false);
2310 * If an item is highlighted in the container, the right arrow key jumps to the
2311 * end of the textbox and selects the highlighted item, otherwise the container
2314 * @method _jumpSelection
2317 YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
2318 if(this._elCurListItem) {
2319 this._selectItem(this._elCurListItem);
2322 this._toggleContainer(false);
2327 * Triggered by up and down arrow keys, changes the current highlighted
2328 * <li> element item. Scrolls container if necessary.
2330 * @method _moveSelection
2331 * @param nKeyCode {Number} Code of key pressed.
2334 YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
2335 if(this._bContainerOpen) {
2336 // Determine current item's id number
2337 var elCurListItem = this._elCurListItem,
2341 nCurItemIndex = elCurListItem._nItemIndex;
2344 var nNewItemIndex = (nKeyCode == 40) ?
2345 (nCurItemIndex + 1) : (nCurItemIndex - 1);
2348 if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
2353 // Unhighlight current item
2354 this._toggleHighlight(elCurListItem, "from");
2355 this.itemArrowFromEvent.fire(this, elCurListItem);
2356 YAHOO.log("Item arrowed from: " + elCurListItem._nItemIndex, "info", this.toString());
2358 if(nNewItemIndex == -1) {
2359 // Go back to query (remove type-ahead string)
2360 if(this.delimChar) {
2361 this._elTextbox.value = this._sPastSelections + this._sCurQuery;
2364 this._elTextbox.value = this._sCurQuery;
2368 if(nNewItemIndex == -2) {
2370 this._toggleContainer(false);
2374 var elNewListItem = this._elList.childNodes[nNewItemIndex],
2376 // Scroll the container if necessary
2377 elContent = this._elContent,
2378 sOF = YAHOO.util.Dom.getStyle(elContent,"overflow"),
2379 sOFY = YAHOO.util.Dom.getStyle(elContent,"overflowY"),
2380 scrollOn = ((sOF == "auto") || (sOF == "scroll") || (sOFY == "auto") || (sOFY == "scroll"));
2381 if(scrollOn && (nNewItemIndex > -1) &&
2382 (nNewItemIndex < this._nDisplayedItems)) {
2383 // User is keying down
2384 if(nKeyCode == 40) {
2385 // Bottom of selected item is below scroll area...
2386 if((elNewListItem.offsetTop+elNewListItem.offsetHeight) > (elContent.scrollTop + elContent.offsetHeight)) {
2387 // Set bottom of scroll area to bottom of selected item
2388 elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2390 // Bottom of selected item is above scroll area...
2391 else if((elNewListItem.offsetTop+elNewListItem.offsetHeight) < elContent.scrollTop) {
2392 // Set top of selected item to top of scroll area
2393 elContent.scrollTop = elNewListItem.offsetTop;
2397 // User is keying up
2399 // Top of selected item is above scroll area
2400 if(elNewListItem.offsetTop < elContent.scrollTop) {
2401 // Set top of scroll area to top of selected item
2402 this._elContent.scrollTop = elNewListItem.offsetTop;
2404 // Top of selected item is below scroll area
2405 else if(elNewListItem.offsetTop > (elContent.scrollTop + elContent.offsetHeight)) {
2406 // Set bottom of selected item to bottom of scroll area
2407 this._elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2412 this._toggleHighlight(elNewListItem, "to");
2413 this.itemArrowToEvent.fire(this, elNewListItem);
2414 YAHOO.log("Item arrowed to " + elNewListItem._nItemIndex, "info", this.toString());
2415 if(this.typeAhead) {
2416 this._updateValue(elNewListItem);
2421 /////////////////////////////////////////////////////////////////////////////
2423 // Private event handlers
2425 /////////////////////////////////////////////////////////////////////////////
2428 * Handles container mouseover events.
2430 * @method _onContainerMouseover
2431 * @param v {HTMLEvent} The mouseover event.
2432 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2435 YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
2436 var elTarget = YAHOO.util.Event.getTarget(v);
2437 var elTag = elTarget.nodeName.toLowerCase();
2438 while(elTarget && (elTag != "table")) {
2443 if(oSelf.prehighlightClassName) {
2444 oSelf._togglePrehighlight(elTarget,"mouseover");
2447 oSelf._toggleHighlight(elTarget,"to");
2450 oSelf.itemMouseOverEvent.fire(oSelf, elTarget);
2451 YAHOO.log("Item moused over " + elTarget._nItemIndex, "info", oSelf.toString());
2454 if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2455 oSelf._bOverContainer = true;
2463 elTarget = elTarget.parentNode;
2465 elTag = elTarget.nodeName.toLowerCase();
2471 * Handles container mouseout events.
2473 * @method _onContainerMouseout
2474 * @param v {HTMLEvent} The mouseout event.
2475 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2478 YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
2479 var elTarget = YAHOO.util.Event.getTarget(v);
2480 var elTag = elTarget.nodeName.toLowerCase();
2481 while(elTarget && (elTag != "table")) {
2486 if(oSelf.prehighlightClassName) {
2487 oSelf._togglePrehighlight(elTarget,"mouseout");
2490 oSelf._toggleHighlight(elTarget,"from");
2493 oSelf.itemMouseOutEvent.fire(oSelf, elTarget);
2494 YAHOO.log("Item moused out " + elTarget._nItemIndex, "info", oSelf.toString());
2497 oSelf._toggleHighlight(oSelf._elCurListItem,"to");
2500 if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2501 oSelf._bOverContainer = false;
2509 elTarget = elTarget.parentNode;
2511 elTag = elTarget.nodeName.toLowerCase();
2517 * Handles container click events.
2519 * @method _onContainerClick
2520 * @param v {HTMLEvent} The click event.
2521 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2524 YAHOO.widget.AutoComplete.prototype._onContainerClick = function(v,oSelf) {
2525 var elTarget = YAHOO.util.Event.getTarget(v);
2526 var elTag = elTarget.nodeName.toLowerCase();
2527 while(elTarget && (elTag != "table")) {
2532 // In case item has not been moused over
2533 oSelf._toggleHighlight(elTarget,"to");
2534 oSelf._selectItem(elTarget);
2540 elTarget = elTarget.parentNode;
2542 elTag = elTarget.nodeName.toLowerCase();
2549 * Handles container scroll events.
2551 * @method _onContainerScroll
2552 * @param v {HTMLEvent} The scroll event.
2553 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2556 YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
2561 * Handles container resize events.
2563 * @method _onContainerResize
2564 * @param v {HTMLEvent} The resize event.
2565 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2568 YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
2569 oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
2574 * Handles textbox keydown events of functional keys, mainly for UI behavior.
2576 * @method _onTextboxKeyDown
2577 * @param v {HTMLEvent} The keydown event.
2578 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2581 YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
2582 var nKeyCode = v.keyCode;
2585 if(oSelf._nTypeAheadDelayID != -1) {
2586 clearTimeout(oSelf._nTypeAheadDelayID);
2591 if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2592 // select an item or clear out
2593 if(oSelf._elCurListItem) {
2594 if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
2595 if(oSelf._bContainerOpen) {
2596 YAHOO.util.Event.stopEvent(v);
2599 oSelf._selectItem(oSelf._elCurListItem);
2602 oSelf._toggleContainer(false);
2607 if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2608 if(oSelf._elCurListItem) {
2609 if(oSelf._nKeyCode != nKeyCode) {
2610 if(oSelf._bContainerOpen) {
2611 YAHOO.util.Event.stopEvent(v);
2614 oSelf._selectItem(oSelf._elCurListItem);
2617 oSelf._toggleContainer(false);
2622 oSelf._toggleContainer(false);
2625 oSelf._jumpSelection();
2628 if(oSelf._bContainerOpen) {
2629 YAHOO.util.Event.stopEvent(v);
2630 oSelf._moveSelection(nKeyCode);
2634 if(oSelf._bContainerOpen) {
2635 YAHOO.util.Event.stopEvent(v);
2636 oSelf._moveSelection(nKeyCode);
2640 oSelf._bItemSelected = false;
2641 oSelf._toggleHighlight(oSelf._elCurListItem, "from");
2643 oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
2644 YAHOO.log("Textbox keyed", "info", oSelf.toString());
2648 if(nKeyCode === 18){
2649 oSelf._enableIntervalDetection();
2651 oSelf._nKeyCode = nKeyCode;
2655 * Handles textbox keypress events.
2656 * @method _onTextboxKeyPress
2657 * @param v {HTMLEvent} The keypress event.
2658 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2661 YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
2662 var nKeyCode = v.keyCode;
2664 // Expose only to non SF3 (bug 1978549) Mac browsers (bug 790337) and Opera browsers (bug 583531),
2665 // where stopEvent is ineffective on keydown events
2666 if(YAHOO.env.ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") != -1) && (YAHOO.env.ua.webkit < 420)) {
2669 // select an item or clear out
2670 if(oSelf._bContainerOpen) {
2671 if(oSelf.delimChar) {
2672 YAHOO.util.Event.stopEvent(v);
2674 if(oSelf._elCurListItem) {
2675 oSelf._selectItem(oSelf._elCurListItem);
2678 oSelf._toggleContainer(false);
2683 if(oSelf._bContainerOpen) {
2684 YAHOO.util.Event.stopEvent(v);
2685 if(oSelf._elCurListItem) {
2686 oSelf._selectItem(oSelf._elCurListItem);
2689 oSelf._toggleContainer(false);
2698 //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
2699 // Korean IME detected
2700 else if(nKeyCode == 229) {
2701 oSelf._enableIntervalDetection();
2706 * Handles textbox keyup events to trigger queries.
2708 * @method _onTextboxKeyUp
2709 * @param v {HTMLEvent} The keyup event.
2710 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2713 YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
2714 var sText = this.value; //string in textbox
2716 // Check to see if any of the public properties have been updated
2719 // Filter out chars that don't trigger queries
2720 var nKeyCode = v.keyCode;
2721 if(oSelf._isIgnoreKey(nKeyCode)) {
2725 // Clear previous timeout
2726 /*if(oSelf._nTypeAheadDelayID != -1) {
2727 clearTimeout(oSelf._nTypeAheadDelayID);
2729 if(oSelf._nDelayID != -1) {
2730 clearTimeout(oSelf._nDelayID);
2734 oSelf._nDelayID = setTimeout(function(){
2735 oSelf._sendQuery(sText);
2736 },(oSelf.queryDelay * 1000));
2740 // No delay so send request immediately
2741 //oSelf._sendQuery(sText);
2746 * Handles text input box receiving focus.
2748 * @method _onTextboxFocus
2749 * @param v {HTMLEvent} The focus event.
2750 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2753 YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
2754 // Start of a new interaction
2755 if(!oSelf._bFocused) {
2756 oSelf._elTextbox.setAttribute("autocomplete","off");
2757 oSelf._bFocused = true;
2758 oSelf._sInitInputValue = oSelf._elTextbox.value;
2759 oSelf.textboxFocusEvent.fire(oSelf);
2760 YAHOO.log("Textbox focused", "info", oSelf.toString());
2765 * Handles text input box losing focus.
2767 * @method _onTextboxBlur
2768 * @param v {HTMLEvent} The focus event.
2769 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2772 YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
2774 if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
2775 // Current query needs to be validated as a selection
2776 if(!oSelf._bItemSelected) {
2777 var elMatchListItem = oSelf._textMatchesOption();
2778 // Container is closed or current query doesn't match any result
2779 if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (elMatchListItem === null))) {
2780 // Force selection is enabled so clear the current query
2781 if(oSelf.forceSelection) {
2782 oSelf._clearSelection();
2784 // Treat current query as a valid selection
2786 oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery);
2787 YAHOO.log("Unmatched item selected: " + oSelf._sCurQuery, "info", oSelf.toString());
2790 // Container is open and current query matches a result
2792 // Force a selection when textbox is blurred with a match
2793 if(oSelf.forceSelection) {
2794 oSelf._selectItem(elMatchListItem);
2799 oSelf._clearInterval();
2800 oSelf._bFocused = false;
2801 if(oSelf._sInitInputValue !== oSelf._elTextbox.value) {
2802 oSelf.textboxChangeEvent.fire(oSelf);
2804 oSelf.textboxBlurEvent.fire(oSelf);
2805 YAHOO.log("Textbox blurred", "info", oSelf.toString());
2807 oSelf._toggleContainer(false);
2809 // Not a true blur if it was a selection via mouse click
2816 * Handles window unload event.
2818 * @method _onWindowUnload
2819 * @param v {HTMLEvent} The unload event.
2820 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2823 YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
2824 if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
2825 oSelf._elTextbox.setAttribute("autocomplete","on");
2829 /////////////////////////////////////////////////////////////////////////////
2831 // Deprecated for Backwards Compatibility
2833 /////////////////////////////////////////////////////////////////////////////
2835 * @method doBeforeSendQuery
2836 * @deprecated Use generateRequest.
2838 YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
2839 return this.generateRequest(sQuery);
2843 * @method getListItems
2844 * @deprecated Use getListEl().childNodes.
2846 YAHOO.widget.AutoComplete.prototype.getListItems = function() {
2847 var allListItemEls = [],
2848 els = this._elList.childNodes;
2849 for(var i=els.length-1; i>=0; i--) {
2850 allListItemEls[i] = els[i];
2852 return allListItemEls;
2855 /////////////////////////////////////////////////////////////////////////
2857 // Private static methods
2859 /////////////////////////////////////////////////////////////////////////
2862 * Clones object literal or array of object literals.
2864 * @method AutoComplete._cloneObject
2865 * @param o {Object} Object.
2869 YAHOO.widget.AutoComplete._cloneObject = function(o) {
2870 if(!YAHOO.lang.isValue(o)) {
2876 if(YAHOO.lang.isFunction(o)) {
2879 else if(YAHOO.lang.isArray(o)) {
2881 for(var i=0,len=o.length;i<len;i++) {
2882 array[i] = YAHOO.widget.AutoComplete._cloneObject(o[i]);
2886 else if(YAHOO.lang.isObject(o)) {
2888 if(YAHOO.lang.hasOwnProperty(o, x)) {
2889 if(YAHOO.lang.isValue(o[x]) && YAHOO.lang.isObject(o[x]) || YAHOO.lang.isArray(o[x])) {
2890 copy[x] = YAHOO.widget.AutoComplete._cloneObject(o[x]);
2908 YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.7.0", build: "1799"});