PageRenderTime 118ms CodeModel.GetById 17ms app.highlight 82ms RepoModel.GetById 1ms app.codeStats 1ms

/src/main/resources/org/apache/struts2/static/autocomplete/autocomplete-debug.js

http://struts2yuiplugin.googlecode.com/
JavaScript | 2908 lines | 1343 code | 279 blank | 1286 comment | 372 complexity | 4c9f18b74e0a2ac25136d6e9e77ad62b MD5 | raw file
   1/*
   2Copyright (c) 2009, Yahoo! Inc. All rights reserved.
   3Code licensed under the BSD License:
   4http://developer.yahoo.net/yui/license.txt
   5version: 2.7.0
   6*/
   7/////////////////////////////////////////////////////////////////////////////
   8//
   9// YAHOO.widget.DataSource Backwards Compatibility
  10//
  11/////////////////////////////////////////////////////////////////////////////
  12
  13YAHOO.widget.DS_JSArray = YAHOO.util.LocalDataSource;
  14
  15YAHOO.widget.DS_JSFunction = YAHOO.util.FunctionDataSource;
  16
  17YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
  18    var DS = new YAHOO.util.XHRDataSource(sScriptURI, oConfigs);
  19    DS._aDeprecatedSchema = aSchema;
  20    return DS;
  21};
  22
  23YAHOO.widget.DS_ScriptNode = function(sScriptURI, aSchema, oConfigs) {
  24    var DS = new YAHOO.util.ScriptNodeDataSource(sScriptURI, oConfigs);
  25    DS._aDeprecatedSchema = aSchema;
  26    return DS;
  27};
  28
  29YAHOO.widget.DS_XHR.TYPE_JSON = YAHOO.util.DataSourceBase.TYPE_JSON;
  30YAHOO.widget.DS_XHR.TYPE_XML = YAHOO.util.DataSourceBase.TYPE_XML;
  31YAHOO.widget.DS_XHR.TYPE_FLAT = YAHOO.util.DataSourceBase.TYPE_TEXT;
  32
  33// TODO: widget.DS_ScriptNode.scriptCallbackParam
  34
  35
  36
  37 /**
  38 * The AutoComplete control provides the front-end logic for text-entry suggestion and
  39 * completion functionality.
  40 *
  41 * @module autocomplete
  42 * @requires yahoo, dom, event, datasource
  43 * @optional animation
  44 * @namespace YAHOO.widget
  45 * @title AutoComplete Widget
  46 */
  47
  48/****************************************************************************/
  49/****************************************************************************/
  50/****************************************************************************/
  51
  52/**
  53 * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
  54 * auto completion widget.  Some key features:
  55 * <ul>
  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
  58 * animation</li>
  59 * <li>UI look-and-feel customizable through CSS, including container
  60 * attributes, borders, position, fonts, etc</li>
  61 * </ul>
  62 *
  63 * @class AutoComplete
  64 * @constructor
  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.
  71 */
  72YAHOO.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;
  77        }
  78        else {
  79            YAHOO.log("Could not instantiate AutoComplete due to an invalid DataSource", "error", this.toString());
  80            return;
  81        }
  82
  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
  86        this.key = 0;
  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)) {
  92                
  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];
  97                    // Store the key
  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);
 101                }
 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);
 106                }                
 107                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_TEXT) {
 108                    schema.recordDelim = aDeprecatedSchema[0];
 109                    schema.fieldDelim = aDeprecatedSchema[1];
 110                }                
 111                oDataSource.responseSchema = schema;
 112            }
 113        }
 114        
 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);
 120            }
 121            else {
 122                this._sName = (elInput.id) ?
 123                    "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
 124                    "instance" + YAHOO.widget.AutoComplete._nIndex;
 125                this._elTextbox = elInput;
 126            }
 127            YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
 128        }
 129        else {
 130            YAHOO.log("Could not instantiate AutoComplete due to an invalid input element", "error", this.toString());
 131            return;
 132        }
 133
 134        // Validate container element
 135        if(YAHOO.util.Dom.inDocument(elContainer)) {
 136            if(YAHOO.lang.isString(elContainer)) {
 137                    this._elContainer = document.getElementById(elContainer);
 138            }
 139            else {
 140                this._elContainer = elContainer;
 141            }
 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());
 144            }
 145            
 146            // For skinning
 147            var elParent = this._elContainer.parentNode;
 148            var elTag = elParent.tagName.toLowerCase();
 149            if(elTag == "div") {
 150                YAHOO.util.Dom.addClass(elParent, "yui-ac");
 151            }
 152            else {
 153                YAHOO.log("Could not find the wrapper element for skinning", "warn", this.toString());
 154            }
 155        }
 156        else {
 157            YAHOO.log("Could not instantiate AutoComplete due to an invalid container element", "error", this.toString());
 158            return;
 159        }
 160
 161        // Default applyLocalFilter setting is to enable for local sources
 162        if(this.dataSource.dataType === YAHOO.util.DataSourceBase.TYPE_LOCAL) {
 163            this.applyLocalFilter = true;
 164        }
 165        
 166        // Set any config params passed in to override defaults
 167        if(oConfigs && (oConfigs.constructor == Object)) {
 168            for(var sConfig in oConfigs) {
 169                if(sConfig) {
 170                    this[sConfig] = oConfigs[sConfig];
 171                }
 172            }
 173        }
 174
 175        // Initialization sequence
 176        this._initContainerEl();
 177        this._initProps();
 178        this._initListEl();
 179        this._initContainerHelperEls();
 180
 181        // Set up events
 182        var oSelf = this;
 183        var elTextbox = this._elTextbox;
 184
 185        // Dom events
 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);
 197
 198        // Custom events
 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);
 217        
 218        // Finish up
 219        elTextbox.setAttribute("autocomplete","off");
 220        YAHOO.widget.AutoComplete._nIndex++;
 221        YAHOO.log("AutoComplete initialized","info",this.toString());
 222    }
 223    // Required arguments were not found
 224    else {
 225        YAHOO.log("Could not instantiate AutoComplete due invalid arguments", "error", this.toString());
 226    }
 227};
 228
 229/////////////////////////////////////////////////////////////////////////////
 230//
 231// Public member variables
 232//
 233/////////////////////////////////////////////////////////////////////////////
 234
 235/**
 236 * The DataSource object that encapsulates the data used for auto completion.
 237 * This object should be an inherited object from YAHOO.widget.DataSource.
 238 *
 239 * @property dataSource
 240 * @type YAHOO.widget.DataSource
 241 */
 242YAHOO.widget.AutoComplete.prototype.dataSource = null;
 243
 244/**
 245 * By default, results from local DataSources will pass through the filterResults
 246 * method to apply a client-side matching algorithm. 
 247 * 
 248 * @property applyLocalFilter
 249 * @type Boolean
 250 * @default true for local arrays and json, otherwise false
 251 */
 252YAHOO.widget.AutoComplete.prototype.applyLocalFilter = null;
 253
 254/**
 255 * When applyLocalFilter is true, the local filtering algorthim can have case sensitivity
 256 * enabled. 
 257 * 
 258 * @property queryMatchCase
 259 * @type Boolean
 260 * @default false
 261 */
 262YAHOO.widget.AutoComplete.prototype.queryMatchCase = false;
 263
 264/**
 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"
 267 * the query string.
 268 * 
 269 * @property queryMatchContains
 270 * @type Boolean
 271 * @default false
 272 */
 273YAHOO.widget.AutoComplete.prototype.queryMatchContains = false;
 274
 275/**
 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.
 282 *
 283 * @property queryMatchSubset
 284 * @type Boolean
 285 * @default false
 286 *
 287 */
 288YAHOO.widget.AutoComplete.prototype.queryMatchSubset = false;
 289
 290/**
 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
 293 * values.
 294 *
 295 * @property minQueryLength
 296 * @type Number
 297 * @default 1
 298 */
 299YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
 300
 301/**
 302 * Maximum number of results to display in results container.
 303 *
 304 * @property maxResultsDisplayed
 305 * @type Number
 306 * @default 10
 307 */
 308YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
 309
 310/**
 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. 
 316 *
 317 * @property queryDelay
 318 * @type Number
 319 * @default 0.2
 320 */
 321YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
 322
 323/**
 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.
 327 *
 328 * @property typeAheadDelay
 329 * @type Number
 330 * @default 0.5
 331 */
 332YAHOO.widget.AutoComplete.prototype.typeAheadDelay = 0.5;
 333
 334/**
 335 * When IME usage is detected, AutoComplete will switch to querying the input
 336 * value at the given interval rather than per key event.
 337 *
 338 * @property queryInterval
 339 * @type Number
 340 * @default 500
 341 */
 342YAHOO.widget.AutoComplete.prototype.queryInterval = 500;
 343
 344/**
 345 * Class name of a highlighted item within results container.
 346 *
 347 * @property highlightClassName
 348 * @type String
 349 * @default "yui-ac-highlight"
 350 */
 351YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
 352
 353/**
 354 * Class name of a pre-highlighted item within results container.
 355 *
 356 * @property prehighlightClassName
 357 * @type String
 358 */
 359YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
 360
 361/**
 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
 366 * be true.
 367 *
 368 * @property delimChar
 369 * @type String | String[]
 370 */
 371YAHOO.widget.AutoComplete.prototype.delimChar = null;
 372
 373/**
 374 * Whether or not the first item in results container should be automatically highlighted
 375 * on expand.
 376 *
 377 * @property autoHighlight
 378 * @type Boolean
 379 * @default true
 380 */
 381YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
 382
 383/**
 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.
 387 *
 388 * @property typeAhead
 389 * @type Boolean
 390 * @default false
 391 */
 392YAHOO.widget.AutoComplete.prototype.typeAhead = false;
 393
 394/**
 395 * Whether or not to animate the expansion/collapse of the results container in the
 396 * horizontal direction.
 397 *
 398 * @property animHoriz
 399 * @type Boolean
 400 * @default false
 401 */
 402YAHOO.widget.AutoComplete.prototype.animHoriz = false;
 403
 404/**
 405 * Whether or not to animate the expansion/collapse of the results container in the
 406 * vertical direction.
 407 *
 408 * @property animVert
 409 * @type Boolean
 410 * @default true
 411 */
 412YAHOO.widget.AutoComplete.prototype.animVert = true;
 413
 414/**
 415 * Speed of container expand/collapse animation, in seconds..
 416 *
 417 * @property animSpeed
 418 * @type Number
 419 * @default 0.3
 420 */
 421YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
 422
 423/**
 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 * &lt;select&gt; field. This feature is not recommended with delimiter character(s)
 427 * defined.
 428 *
 429 * @property forceSelection
 430 * @type Boolean
 431 * @default false
 432 */
 433YAHOO.widget.AutoComplete.prototype.forceSelection = false;
 434
 435/**
 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.
 443 *
 444 * @property allowBrowserAutocomplete
 445 * @type Boolean
 446 * @default true
 447 */
 448YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
 449
 450/**
 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.   
 454 *
 455 * @property alwaysShowContainer
 456 * @type Boolean
 457 * @default false
 458 */
 459YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
 460
 461/**
 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 * &lt;select&gt; field in IE and thus exposed to the IE z-index bug (i.e.,
 465 * 5.5 < IE < 7).
 466 *
 467 * @property useIFrame
 468 * @type Boolean
 469 * @default false
 470 */
 471YAHOO.widget.AutoComplete.prototype.useIFrame = false;
 472
 473/**
 474 * Whether or not the results container should have a shadow.
 475 *
 476 * @property useShadow
 477 * @type Boolean
 478 * @default false
 479 */
 480YAHOO.widget.AutoComplete.prototype.useShadow = false;
 481
 482/**
 483 * Whether or not the input field should be updated with selections.
 484 *
 485 * @property suppressInputUpdate
 486 * @type Boolean
 487 * @default false
 488 */
 489YAHOO.widget.AutoComplete.prototype.suppressInputUpdate = false;
 490
 491/**
 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.  
 495 *
 496 * @property resultTypeList
 497 * @type Boolean
 498 * @default true
 499 */
 500YAHOO.widget.AutoComplete.prototype.resultTypeList = true;
 501
 502/**
 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. 
 507 *
 508 * @property queryQuestionMark
 509 * @type Boolean
 510 * @default true
 511 */
 512YAHOO.widget.AutoComplete.prototype.queryQuestionMark = true;
 513
 514/////////////////////////////////////////////////////////////////////////////
 515//
 516// Public methods
 517//
 518/////////////////////////////////////////////////////////////////////////////
 519
 520 /**
 521 * Public accessor to the unique name of the AutoComplete instance.
 522 *
 523 * @method toString
 524 * @return {String} Unique name of the AutoComplete instance.
 525 */
 526YAHOO.widget.AutoComplete.prototype.toString = function() {
 527    return "AutoComplete " + this._sName;
 528};
 529
 530 /**
 531 * Returns DOM reference to input element.
 532 *
 533 * @method getInputEl
 534 * @return {HTMLELement} DOM reference to input element.
 535 */
 536YAHOO.widget.AutoComplete.prototype.getInputEl = function() {
 537    return this._elTextbox;
 538};
 539
 540 /**
 541 * Returns DOM reference to container element.
 542 *
 543 * @method getContainerEl
 544 * @return {HTMLELement} DOM reference to container element.
 545 */
 546YAHOO.widget.AutoComplete.prototype.getContainerEl = function() {
 547    return this._elContainer;
 548};
 549
 550 /**
 551 * Returns true if widget instance is currently focused.
 552 *
 553 * @method isFocused
 554 * @return {Boolean} Returns true if widget instance is currently focused.
 555 */
 556YAHOO.widget.AutoComplete.prototype.isFocused = function() {
 557    return (this._bFocused === null) ? false : this._bFocused;
 558};
 559
 560 /**
 561 * Returns true if container is in an expanded state, false otherwise.
 562 *
 563 * @method isContainerOpen
 564 * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
 565 */
 566YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
 567    return this._bContainerOpen;
 568};
 569
 570/**
 571 * Public accessor to the &lt;ul&gt; element that displays query results within the results container.
 572 *
 573 * @method getListEl
 574 * @return {HTMLElement[]} Reference to &lt;ul&gt; element within the results container.
 575 */
 576YAHOO.widget.AutoComplete.prototype.getListEl = function() {
 577    return this._elList;
 578};
 579
 580/**
 581 * Public accessor to the matching string associated with a given &lt;li&gt; result.
 582 *
 583 * @method getListItemMatch
 584 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
 585 * @return {String} Matching string.
 586 */
 587YAHOO.widget.AutoComplete.prototype.getListItemMatch = function(elListItem) {
 588    if(elListItem._sResultMatch) {
 589        return elListItem._sResultMatch;
 590    }
 591    else {
 592        return null;
 593    }
 594};
 595
 596/**
 597 * Public accessor to the result data associated with a given &lt;li&gt; result.
 598 *
 599 * @method getListItemData
 600 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
 601 * @return {Object} Result data.
 602 */
 603YAHOO.widget.AutoComplete.prototype.getListItemData = function(elListItem) {
 604    if(elListItem._oResultData) {
 605        return elListItem._oResultData;
 606    }
 607    else {
 608        return null;
 609    }
 610};
 611
 612/**
 613 * Public accessor to the index of the associated with a given &lt;li&gt; result.
 614 *
 615 * @method getListItemIndex
 616 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
 617 * @return {Number} Index.
 618 */
 619YAHOO.widget.AutoComplete.prototype.getListItemIndex = function(elListItem) {
 620    if(YAHOO.lang.isNumber(elListItem._nItemIndex)) {
 621        return elListItem._nItemIndex;
 622    }
 623    else {
 624        return null;
 625    }
 626};
 627
 628/**
 629 * Sets HTML markup for the results container header. This markup will be
 630 * inserted within a &lt;div&gt; tag with a class of "yui-ac-hd".
 631 *
 632 * @method setHeader
 633 * @param sHeader {String} HTML markup for results container header.
 634 */
 635YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
 636    if(this._elHeader) {
 637        var elHeader = this._elHeader;
 638        if(sHeader) {
 639            elHeader.innerHTML = sHeader;
 640            elHeader.style.display = "block";
 641        }
 642        else {
 643            elHeader.innerHTML = "";
 644            elHeader.style.display = "none";
 645        }
 646    }
 647};
 648
 649/**
 650 * Sets HTML markup for the results container footer. This markup will be
 651 * inserted within a &lt;div&gt; tag with a class of "yui-ac-ft".
 652 *
 653 * @method setFooter
 654 * @param sFooter {String} HTML markup for results container footer.
 655 */
 656YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
 657    if(this._elFooter) {
 658        var elFooter = this._elFooter;
 659        if(sFooter) {
 660                elFooter.innerHTML = sFooter;
 661                elFooter.style.display = "block";
 662        }
 663        else {
 664            elFooter.innerHTML = "";
 665            elFooter.style.display = "none";
 666        }
 667    }
 668};
 669
 670/**
 671 * Sets HTML markup for the results container body. This markup will be
 672 * inserted within a &lt;div&gt; tag with a class of "yui-ac-bd".
 673 *
 674 * @method setBody
 675 * @param sBody {String} HTML markup for results container body.
 676 */
 677YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
 678    if(this._elBody) {
 679        var elBody = this._elBody;
 680        YAHOO.util.Event.purgeElement(elBody, true);
 681        if(sBody) {
 682            elBody.innerHTML = sBody;
 683            elBody.style.display = "block";
 684        }
 685        else {
 686            elBody.innerHTML = "";
 687            elBody.style.display = "none";
 688        }
 689        this._elList = null;
 690    }
 691};
 692
 693/**
 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.
 698* 
 699* @method generateRequest
 700* @param sQuery {String} Query string
 701* @return {MIXED} Request
 702*/
 703YAHOO.widget.AutoComplete.prototype.generateRequest = function(sQuery) {
 704    var dataType = this.dataSource.dataType;
 705    
 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) : "");        
 713        }
 714        // By default, XHR POST bodies are sent to the {scriptURI} like "{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
 715        else {
 716            sQuery = (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
 717                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
 718        }
 719    }
 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) : "");    
 724    }
 725    
 726    return sQuery;
 727};
 728
 729/**
 730 * Makes query request to the DataSource.
 731 *
 732 * @method sendQuery
 733 * @param sQuery {String} Query string.
 734 */
 735YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
 736    // Reset focus for a new interaction
 737    this._bFocused = null;
 738    
 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);
 743};
 744
 745/**
 746 * Collapses container.
 747 *
 748 * @method collapseContainer
 749 */
 750YAHOO.widget.AutoComplete.prototype.collapseContainer = function() {
 751    this._toggleContainer(false);
 752};
 753
 754/**
 755 * Handles subset matching for when queryMatchSubset is enabled.
 756 *
 757 * @method getSubsetMatches
 758 * @param sQuery {String} Query string.
 759 * @return {Object} oParsedResponse or null. 
 760 */
 761YAHOO.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());
 768        
 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}]);
 774        }
 775    }
 776    YAHOO.log("Did not find subset match for query subset \"" + sQuery + "\"" , "info", this.toString());
 777    return null;
 778};
 779
 780/**
 781 * Executed by DataSource (within DataSource scope via doBeforeParseData()) to
 782 * handle responseStripAfter cleanup.
 783 *
 784 * @method preparseRawResponse
 785 * @param sQuery {String} Query string.
 786 * @return {Object} oParsedResponse or null. 
 787 */
 788YAHOO.widget.AutoComplete.prototype.preparseRawResponse = function(oRequest, oFullResponse, oCallback) {
 789    var nEnd = ((this.responseStripAfter !== "") && (oFullResponse.indexOf)) ?
 790        oFullResponse.indexOf(this.responseStripAfter) : -1;
 791    if(nEnd != -1) {
 792        oFullResponse = oFullResponse.substring(0,nEnd);
 793    }
 794    return oFullResponse;
 795};
 796
 797/**
 798 * Executed by DataSource (within DataSource scope via doBeforeCallback()) to
 799 * filter results through a simple client-side matching algorithm. 
 800 *
 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.
 807 */
 808
 809YAHOO.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;
 813    }
 814
 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);
 819        
 820        var oAC = oCallback.scope,
 821            oDS = this,
 822            allResults = oParsedResponse.results, // the array of results
 823            filteredResults = [], // container for filtered results
 824            bMatchFound = false,
 825            bMatchCase = (oDS.queryMatchCase || oAC.queryMatchCase), // backward compat
 826            bMatchContains = (oDS.queryMatchContains || oAC.queryMatchContains); // backward compat
 827            
 828        // Loop through each result object...
 829        for(var i = allResults.length-1; i >= 0; i--) {
 830            var oResult = allResults[i];
 831
 832            // Grab the data to match against from the result object...
 833            var sResult = null;
 834            
 835            // Result object is a simple string already
 836            if(YAHOO.lang.isString(oResult)) {
 837                sResult = oResult;
 838            }
 839            // Result object is an array of strings
 840            else if(YAHOO.lang.isArray(oResult)) {
 841                sResult = oResult[0];
 842            
 843            }
 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];
 848            }
 849            // Backwards compatibility
 850            else if(this.key) {
 851                sResult = oResult[this.key];
 852            }
 853            
 854            if(YAHOO.lang.isString(sResult)) {
 855                
 856                var sKeyIndex = (bMatchCase) ?
 857                sResult.indexOf(decodeURIComponent(sQuery)) :
 858                sResult.toLowerCase().indexOf(decodeURIComponent(sQuery).toLowerCase());
 859
 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))) {
 864                    // Stash the match
 865                    filteredResults.unshift(oResult);
 866                }
 867            }
 868        }
 869        oParsedResponse.results = filteredResults;
 870        YAHOO.log("Filtered " + filteredResults.length + " results against query \""  + sQuery + "\": " + YAHOO.lang.dump(filteredResults), "info", this.toString());
 871    }
 872    else {
 873        YAHOO.log("Did not filter results against query", "info", this.toString());
 874    }
 875    
 876    return oParsedResponse;
 877};
 878
 879/**
 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.
 883 *
 884 * @method handleResponse
 885 * @param sQuery {String} Original request.
 886 * @param oResponse {Object} Response object.
 887 * @param oPayload {MIXED} (optional) Additional argument(s)
 888 */
 889YAHOO.widget.AutoComplete.prototype.handleResponse = function(sQuery, oResponse, oPayload) {
 890    if((this instanceof YAHOO.widget.AutoComplete) && this._sName) {
 891        this._populateList(sQuery, oResponse, oPayload);
 892    }
 893};
 894
 895/**
 896 * Overridable method called before container is loaded with result data.
 897 *
 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.
 903 */
 904YAHOO.widget.AutoComplete.prototype.doBeforeLoadData = function(sQuery, oResponse, oPayload) {
 905    return true;
 906};
 907
 908/**
 909 * Overridable method that returns HTML markup for one result to be populated
 910 * as innerHTML of an &lt;LI&gt; element. 
 911 *
 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.
 917 */
 918YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultData, sQuery, sResultMatch) {
 919    var sMarkup = (sResultMatch) ? sResultMatch : "";
 920    return sMarkup;
 921};
 922
 923/**
 924 * Overridable method called before container expands allows implementers to access data
 925 * and DOM elements.
 926 *
 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.
 933 */
 934YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
 935    return true;
 936};
 937
 938
 939/**
 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!
 944 *
 945 * @method destroy
 946 */
 947YAHOO.widget.AutoComplete.prototype.destroy = function() {
 948    var instanceName = this.toString();
 949    var elInput = this._elTextbox;
 950    var elContainer = this._elContainer;
 951
 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();
 971
 972    // Unhook DOM events
 973    YAHOO.util.Event.purgeElement(elInput, true);
 974    YAHOO.util.Event.purgeElement(elContainer, true);
 975
 976    // Remove DOM elements
 977    elContainer.innerHTML = "";
 978
 979    // Null out objects
 980    for(var key in this) {
 981        if(YAHOO.lang.hasOwnProperty(this, key)) {
 982            this[key] = null;
 983        }
 984    }
 985
 986    YAHOO.log("AutoComplete instance destroyed: " + instanceName);
 987};
 988
 989/////////////////////////////////////////////////////////////////////////////
 990//
 991// Public events
 992//
 993/////////////////////////////////////////////////////////////////////////////
 994
 995/**
 996 * Fired when the input field receives focus.
 997 *
 998 * @event textboxFocusEvent
 999 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1000 */
1001YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
1002
1003/**
1004 * Fired when the input field receives key input.
1005 *
1006 * @event textboxKeyEvent
1007 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1008 * @param nKeycode {Number} The keycode number.
1009 */
1010YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
1011
1012/**
1013 * Fired when the AutoComplete instance makes a request to the DataSource.
1014 * 
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.
1019 */
1020YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
1021
1022/**
1023 * Fired when the AutoComplete instance receives query results from the data
1024 * source.
1025 *
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.
1030 */
1031YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
1032
1033/**
1034 * Fired when the AutoComplete instance does not receive query results from the
1035 * DataSource due to an error.
1036 *
1037 * @event dataErrorEvent
1038 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1039 * @param sQuery {String} The query string.
1040 */
1041YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
1042
1043/**
1044 * Fired when the results container is populated.
1045 *
1046 * @event containerPopulateEvent
1047 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1048 */
1049YAHOO.widget.AutoComplete.prototype.containerPopulateEvent = null;
1050
1051/**
1052 * Fired when the results container is expanded.
1053 *
1054 * @event containerExpandEvent
1055 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1056 */
1057YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
1058
1059/**
1060 * Fired when the input field has been prefilled by the type-ahead
1061 * feature. 
1062 *
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.
1067 */
1068YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
1069
1070/**
1071 * Fired when result item has been moused over.
1072 *
1073 * @event itemMouseOverEvent
1074 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1075 * @param elItem {HTMLElement} The &lt;li&gt element item moused to.
1076 */
1077YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
1078
1079/**
1080 * Fired when result item has been moused out.
1081 *
1082 * @event itemMouseOutEvent
1083 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1084 * @param elItem {HTMLElement} The &lt;li&gt; element item moused from.
1085 */
1086YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
1087
1088/**
1089 * Fired when result item has been arrowed to. 
1090 *
1091 * @event itemArrowToEvent
1092 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1093 * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed to.
1094 */
1095YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
1096
1097/**
1098 * Fired when result item has been arrowed away from.
1099 *
1100 * @event itemArrowFromEvent
1101 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1102 * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed from.
1103 */
1104YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
1105
1106/**
1107 * Fired when an item is selected via mouse click, ENTER key, or TAB key.
1108 *
1109 * @event itemSelectEvent
1110 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1111 * @param elItem {HTMLElement} The selected &lt;li&gt; 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.
1114 */
1115YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
1116
1117/**
1118 * Fired when a user selection does not match any of the displayed result items.
1119 *
1120 * @event unmatchedItemSelectEvent
1121 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1122 * @param sSelection {String} The selected string.  
1123 */
1124YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
1125
1126/**
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.
1129 *
1130 * @event selectionEnforceEvent
1131 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1132 * @param sClearedValue {String} The cleared value (including delimiters if applicable). 
1133 */
1134YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
1135
1136/**
1137 * Fired when the results container is collapsed.
1138 *
1139 * @event containerCollapseEvent
1140 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1141 */
1142YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
1143
1144/**
1145 * Fired when the input field loses focus.
1146 *
1147 * @event textboxBlurEvent
1148 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1149 */
1150YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
1151
1152/**
1153 * Fired when the input field value has changed when it loses focus.
1154 *
1155 * @event textboxChangeEvent
1156 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1157 */
1158YAHOO.widget.AutoComplete.prototype.textboxChangeEvent = null;
1159
1160/////////////////////////////////////////////////////////////////////////////
1161//
1162// Private member variables
1163//
1164/////////////////////////////////////////////////////////////////////////////
1165
1166/**
1167 * Internal class variable to index multiple AutoComplete instances.
1168 *
1169 * @property _nIndex
1170 * @type Number
1171 * @default 0
1172 * @private
1173 */
1174YAHOO.widget.AutoComplete._nIndex = 0;
1175
1176/**
1177 * Name of AutoComplete instance.
1178 *
1179 * @property _sName
1180 * @type String
1181 * @private
1182 */
1183YAHOO.widget.AutoComplete.prototype._sName = null;
1184
1185/**
1186 * Text input field DOM element.
1187 *
1188 * @property _elTextbox
1189 * @type HTMLElement
1190 * @private
1191 */
1192YAHOO.widget.AutoComplete.prototype._elTextbox = null;
1193
1194/**
1195 * Container DOM element.
1196 *
1197 * @property _elContainer
1198 * @type HTMLElement
1199 * @private
1200 */
1201YAHOO.widget.AutoComplete.prototype._elContainer = null;
1202
1203/**
1204 * Reference to content element within container element.
1205 *
1206 * @property _elContent
1207 * @type HTMLElement
1208 * @private
1209 */
1210YAHOO.widget.AutoComplete.prototype._elContent = null;
1211
1212/**
1213 * Reference to header element within content element.
1214 *
1215 * @property _elHeader
1216 * @type HTMLElement
1217 * @private
1218 */
1219YAHOO.widget.AutoComplete.prototype._elHeader = null;
1220
1221/**
1222 * Reference to body element within content element.
1223 *
1224 * @property _elBody
1225 * @type HTMLElement
1226 * @private
1227 */
1228YAHOO.widget.AutoComplete.prototype._elBody = null;
1229
1230/**
1231 * Reference to footer element within content element.
1232 *
1233 * @property _elFooter
1234 * @type HTMLElement
1235 * @private
1236 */
1237YAHOO.widget.AutoComplete.prototype._elFooter = null;
1238
1239/**
1240 * Reference to shadow element within container element.
1241 *
1242 * @property _elShadow
1243 * @type HTMLElement
1244 * @private
1245 */
1246YAHOO.widget.AutoComplete.prototype._elShadow = null;
1247
1248/**
1249 * Reference to iframe element within container element.
1250 *
1251 * @property _elIFrame
1252 * @type HTMLElement
1253 * @private
1254 */
1255YAHOO.widget.AutoComplete.prototype._elIFrame = null;
1256
1257/**
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.
1260 *
1261 * @property _bFocused
1262 * @type Boolean
1263 * @private
1264 */
1265YAHOO.widget.AutoComplete.prototype._bFocused = null;
1266
1267/**
1268 * Animation instance for container expand/collapse.
1269 *
1270 * @property _oAnim
1271 * @type Boolean
1272 * @private
1273 */
1274YAHOO.widget.AutoComplete.prototype._oAnim = null;
1275
1276/**
1277 * Whether or not the results container is currently open.
1278 *
1279 * @property _bContainerOpen
1280 * @type Boolean
1281 * @private
1282 */
1283YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
1284
1285/**
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.
1289 *
1290 * @property _bOverContainer
1291 * @type Boolean
1292 * @private
1293 */
1294YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
1295
1296/**
1297 * Internal reference to &lt;ul&gt; elements that contains query results within the
1298 * results container.
1299 *
1300 * @property _elList
1301 * @type HTMLElement
1302 * @private
1303 */
1304YAHOO.widget.AutoComplete.prototype._elList = null;
1305
1306/*
1307 * Array of &lt;li&gt; elements references that contain query results within the
1308 * results container.
1309 *
1310 * @property _aListItemEls
1311 * @type HTMLElement[]
1312 * @private
1313 */
1314//YAHOO.widget.AutoComplete.prototype._aListItemEls = null;
1315
1316/**
1317 * Number of &lt;li&gt; elements currently displayed in results container.
1318 *
1319 * @property _nDisplayedItems
1320 * @type Number
1321 * @private
1322 */
1323YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
1324
1325/*
1326 * Internal count of &lt;li&gt; elements displayed and hidden in results container.
1327 *
1328 * @property _maxResultsDisplayed
1329 * @type Number
1330 * @private
1331 */
1332//YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
1333
1334/**
1335 * Current query string
1336 *
1337 * @property _sCurQuery
1338 * @type String
1339 * @private
1340 */
1341YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
1342
1343/**
1344 * Selections from previous queries (for saving delimited queries).
1345 *
1346 * @property _sPastSelections
1347 * @type String
1348 * @default "" 
1349 * @private
1350 */
1351YAHOO.widget.AutoComplete.prototype._sPastSelections = "";
1352
1353/**
1354 * Stores initial input value used to determine if textboxChangeEvent should be fired.
1355 *
1356 * @property _sInitInputValue
1357 * @type String
1358 * @private
1359 */
1360YAHOO.widget.AutoComplete.prototype._sInitInputValue = null;
1361
1362/**
1363 * Pointer to the currently highlighted &lt;li&gt; element in the container.
1364 *
1365 * @property _elCurListItem
1366 * @type HTMLElement
1367 * @private
1368 */
1369YAHOO.widget.AutoComplete.prototype._elCurListItem = null;
1370
1371/**
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
1374 * selected.
1375 *
1376 * @property _bItemSelected
1377 * @type Boolean
1378 * @private
1379 */
1380YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
1381
1382/**
1383 * Key code of the last key pressed in textbox.
1384 *
1385 * @property _nKeyCode
1386 * @type Number
1387 * @private
1388 */
1389YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
1390
1391/**
1392 * Delay timeout ID.
1393 *
1394 * @property _nDelayID
1395 * @type Number
1396 * @private
1397 */
1398YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
1399
1400/**
1401 * TypeAhead delay timeout ID.
1402 *
1403 * @property _nTypeAheadDelayID
1404 * @type Number
1405 * @private
1406 */
1407YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID = -1;
1408
1409/**
1410 * Src to iFrame used when useIFrame = true. Supports implementations over SSL
1411 * as well.
1412 *
1413 * @property _iFrameSrc
1414 * @type String
1415 * @private
1416 */
1417YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
1418
1419/**
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.
1422 *
1423 * @property _queryInterval
1424 * @type Object
1425 * @private
1426 */
1427YAHOO.widget.AutoComplete.prototype._queryInterval = null;
1428
1429/**
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.
1432 *
1433 * @event _sLastTextboxValue
1434 * @type String
1435 * @private
1436 */
1437YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
1438
1439/////////////////////////////////////////////////////////////////////////////
1440//
1441// Private methods
1442//
1443/////////////////////////////////////////////////////////////////////////////
1444
1445/**
1446 * Updates and validates latest public config properties.
1447 *
1448 * @method __initProps
1449 * @private
1450 */
1451YAHOO.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;
1456    }
1457    var maxResultsDisplayed = this.maxResultsDisplayed;
1458    if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
1459        this.maxResultsDisplayed = 10;
1460    }
1461    var queryDelay = this.queryDelay;
1462    if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
1463        this.queryDelay = 0.2;
1464    }
1465    var typeAheadDelay = this.typeAheadDelay;
1466    if(!YAHOO.lang.isNumber(typeAheadDelay) || (typeAheadDelay < 0)) {
1467        this.typeAheadDelay = 0.2;
1468    }
1469    var delimChar = this.delimChar;
1470    if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
1471        this.delimChar = [delimChar];
1472    }
1473    else if(!YAHOO.lang.isArray(delimChar)) {
1474        this.delimChar = null;
1475    }
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;
1480        }
1481        if(!this._oAnim ) {
1482            this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
1483        }
1484        else {
1485            this._oAnim.duration = this.animSpeed;
1486        }
1487    }
1488    if(this.forceSelection && delimChar) {
1489        YAHOO.log("The forceSelection feature has been enabled with delimChar defined.","warn", this.toString());
1490    }
1491};
1492
1493/**
1494 * Initializes the results container helpers if they are enabled and do
1495 * not exist
1496 *
1497 * @method _initContainerHelperEls
1498 * @private
1499 */
1500YAHOO.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);
1507    }
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);
1519    }
1520};
1521
1522/**
1523 * Initializes the results container once at object creation
1524 *
1525 * @method _initContainerEl
1526 * @private
1527 */
1528YAHOO.widget.AutoComplete.prototype._initContainerEl = function() {
1529    YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
1530    
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";
1537
1538        this._elContent = this._elContainer.appendChild(elContent);
1539
1540        var elHeader = document.createElement("div");
1541        elHeader.className = "yui-ac-hd";
1542        elHeader.style.display = "none";
1543        this._elHeader = this._elContent.appendChild(elHeader);
1544
1545        var elBody = document.createElement("div");
1546        elBody.className = "yui-ac-bd";
1547        this._elBody = this._elContent.appendChild(elBody);
1548
1549        var elFooter = document.createElement("div");
1550        elFooter.className = "yui-ac-ft";
1551        elFooter.style.display = "none";
1552        this._elFooter = this._elContent.appendChild(elFooter);
1553    }
1554    else {
1555        YAHOO.log("Could not initialize the container","warn",this.toString());
1556    }
1557};
1558
1559/**
1560 * Clears out contents of container body and creates up to
1561 * YAHOO.widget.AutoComplete#maxResultsDisplayed &lt;li&gt; elements in an
1562 * &lt;ul&gt; element.
1563 *
1564 * @method _initListEl
1565 * @private
1566 */
1567YAHOO.widget.AutoComplete.prototype._initListEl = function() {
1568    var nListLength = this.maxResultsDisplayed;
1569    
1570    var elList = this._elList || document.createElement("ul");
1571    var elListItem;
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);
1577    }
1578    if(!this._elList) {
1579        var elBody = this._elBody;
1580        YAHOO.util.Event.purgeElement(elBody, true);
1581        elBody.innerHTML = "";
1582        this._elList = elBody.appendChild(elList);
1583    }
1584    
1585};
1586
1587/**
1588 * Focuses input field.
1589 *
1590 * @method _focus
1591 * @private
1592 */
1593YAHOO.widget.AutoComplete.prototype._focus = function() {
1594    // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
1595    var oSelf = this;
1596    setTimeout(function() {
1597        try {
1598            oSelf._elTextbox.focus();
1599        }
1600        catch(e) {
1601        }
1602    },0);
1603};
1604
1605/**
1606 * Enables interval detection for IME support.
1607 *
1608 * @method _enableIntervalDetection
1609 * @private
1610 */
1611YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() {
1612    var oSelf = this;
1613    if(!oSelf._queryInterval && oSelf.queryInterval) {
1614        oSelf._queryInterval = setInterval(function() { oSelf._onInterval(); }, oSelf.queryInterval);
1615        YAHOO.log("Interval set", "info", this.toString());
1616    }
1617};
1618
1619/**
1620 * Enables query triggers based on text input detection by intervals (rather
1621 * than by key events).
1622 *
1623 * @method _onInterval
1624 * @private
1625 */
1626YAHOO.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);
1632    }
1633};
1634
1635/**
1636 * Cancels text input detection by intervals.
1637 *
1638 * @method _clearInterval
1639 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1640 * @private
1641 */
1642YAHOO.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());
1647    }
1648};
1649
1650/**
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
1653 * charsets.
1654 *
1655 * @method _isIgnoreKey
1656 * @param nKeycode {Number} Code of key pressed.
1657 * @return {Boolean} True if key should be ignored, false otherwise.
1658 * @private
1659 */
1660YAHOO.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
1671        ) { 
1672        return true;
1673    }
1674    return false;
1675};
1676
1677/**
1678 * Makes query request to the DataSource.
1679 *
1680 * @method _sendQuery
1681 * @param sQuery {String} Query string.
1682 * @private
1683 */
1684YAHOO.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());
1689        return;
1690    }
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;
1698    }
1699
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);
1704        }
1705        this._toggleContainer(false);
1706        YAHOO.log("Query \"" + sQuery + "\" is too short", "info", this.toString());
1707        return;
1708    }
1709
1710    sQuery = encodeURIComponent(sQuery);
1711    this._nDelayID = -1;    // Reset timeout ID because request is being made
1712    
1713    // Subset matching
1714    if(this.dataSource.queryMatchSubset || this.queryMatchSubset) { // backward compat
1715        var oResponse = this.getSubsetMatches(sQuery);
1716        if(oResponse) {
1717            this.handleResponse(sQuery, oResponse, {query: sQuery});
1718            return;
1719        }
1720    }
1721    
1722    if(this.responseStripAfter) {
1723        this.dataSource.doBeforeParseData = this.preparseRawResponse;
1724    }
1725    if(this.applyLocalFilter) {
1726        this.dataSource.doBeforeCallback = this.filterResults;
1727    }
1728    
1729    var sRequest = this.generateRequest(sQuery);
1730    this.dataRequestEvent.fire(this, sQuery, sRequest);
1731    YAHOO.log("Sending query \"" + sRequest + "\"", "info", this.toString());
1732
1733    this.dataSource.sendRequest(sRequest, {
1734            success : this.handleResponse,
1735            failure : this.handleResponse,
1736            scope   : this,
1737            argument: {
1738                query: sQuery
1739            }
1740    });
1741};
1742
1743/**
1744 * Populates the array of &lt;li&gt; elements in the container with query
1745 * results.
1746 *
1747 * @method _populateList
1748 * @param sQuery {String} Original request.
1749 * @param oResponse {Object} Response object.
1750 * @param oPayload {MIXED} (optional) Additional argument(s)
1751 * @private
1752 */
1753YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, oResponse, oPayload) {
1754    // Clear previous timeout
1755    if(this._nTypeAheadDelayID != -1) {
1756        clearTimeout(this._nTypeAheadDelayID);
1757    }
1758        
1759    sQuery = (oPayload && oPayload.query) ? oPayload.query : sQuery;
1760    
1761    // Pass data through abstract method for any transformations
1762    var ok = this.doBeforeLoadData(sQuery, oResponse, oPayload);
1763
1764    // Data is ok
1765    if(ok && !oResponse.error) {
1766        this.dataReturnEvent.fire(this, sQuery, oResponse.results);
1767        
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)) {
1771            
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 : "";*/
1777        
1778            // Store state for this interaction
1779            var sCurQuery = decodeURIComponent(sQuery);
1780            this._sCurQuery = sCurQuery;
1781            this._bItemSelected = false;
1782        
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;
1787            
1788            if(nItemsToShow > 0) {
1789                // Make sure container and helpers are ready to go
1790                if(!this._elList || (this._elList.childNodes.length < nItemsToShow)) {
1791                    this._initListEl();
1792                }
1793                this._initContainerHelperEls();
1794                
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];
1800                    
1801                    // Backward compatibility
1802                    if(this.resultTypeList) {
1803                        // Results need to be converted back to an array
1804                        var aResult = [];
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]];
1812                            }
1813                        }
1814                        // No specific fields defined, so pass along entire data object
1815                        else {
1816                            // Already an array
1817                            if(YAHOO.lang.isArray(oResult)) {
1818                                aResult = oResult;
1819                            }
1820                            // Simple string 
1821                            else if(YAHOO.lang.isString(oResult)) {
1822                                aResult = [oResult];
1823                            }
1824                            // Object
1825                            else {
1826                                aResult[1] = oResult;
1827                            }
1828                        }
1829                        oResult = aResult;
1830                    }
1831
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 = "";
1837                }
1838        
1839                // Clear out extraneous items
1840                if(nItemsToShow < allListItemEls.length) {
1841                    var extraListItem;
1842                    for(var j = allListItemEls.length-1; j >= nItemsToShow; j--) {
1843                        extraListItem = allListItemEls[j];
1844                        extraListItem.style.display = "none";
1845                    }
1846                }
1847                
1848                this._nDisplayedItems = nItemsToShow;
1849                
1850                this.containerPopulateEvent.fire(this, sQuery, allResults);
1851                
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);
1859                }
1860                // Unhighlight any previous time
1861                else {
1862                    this._toggleHighlight(this._elCurListItem,"from");
1863                }
1864        
1865                // Expand the container
1866                ok = this.doBeforeExpandContainer(this._elTextbox, this._elContainer, sQuery, allResults);
1867                this._toggleContainer(ok);
1868            }
1869            else {
1870                this._toggleContainer(false);
1871            }
1872
1873            YAHOO.log("Container populated with " + nItemsToShow +  " list items", "info", this.toString());
1874            return;
1875        }
1876    }
1877    // Error
1878    else {
1879        this.dataErrorEvent.fire(this, sQuery);
1880    }
1881        
1882    YAHOO.log("Could not populate list", "info", this.toString());    
1883};
1884
1885/**
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.
1889 *
1890 * @method _clearSelection
1891 * @private
1892 */
1893YAHOO.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());
1899};
1900
1901/**
1902 * Whether or not user-typed value in the text input box matches any of the
1903 * query results.
1904 *
1905 * @method _textMatchesOption
1906 * @return {HTMLElement} Matching list item element if user-input text matches
1907 * a result, null otherwise.
1908 * @private
1909 */
1910YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
1911    var elMatch = null;
1912
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;
1918            break;
1919        }
1920    }
1921    return(elMatch);
1922};
1923
1924/**
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.
1927 *
1928 * @method _typeAhead
1929 * @param elListItem {HTMLElement} The &lt;li&gt; element item whose data populates the input field.
1930 * @param sQuery {String} Query string.
1931 * @private
1932 */
1933YAHOO.widget.AutoComplete.prototype._typeAhead = function(elListItem, sQuery) {
1934    // Don't typeAhead if turned off or is backspace
1935    if(!this.typeAhead || (this._nKeyCode == 8)) {
1936        return;
1937    }
1938
1939    var oSelf = this,
1940        elTextbox = this._elTextbox;
1941        
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));            
1955    }
1956};
1957
1958/**
1959 * Selects text in the input field.
1960 *
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.
1965 * @private
1966 */
1967YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
1968    if(elTextbox.setSelectionRange) { // For Mozilla
1969        elTextbox.setSelectionRange(nStart,nEnd);
1970    }
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();
1976    }
1977    else {
1978        elTextbox.select();
1979    }
1980};
1981
1982/**
1983 * Extracts rightmost query from delimited string.
1984 *
1985 * @method _extractQuery
1986 * @param sQuery {String} String to parse
1987 * @return {Object} Object literal containing properties "query" and "previous".  
1988 * @private
1989 */
1990YAHOO.widget.AutoComplete.prototype._extractQuery = function(sQuery) {
1991    var aDelimChar = this.delimChar,
1992        nDelimIndex = -1,
1993        nNewIndex, nQueryStart,
1994        i = aDelimChar.length-1,
1995        sPrevious;
1996        
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;
2004        }
2005    }
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]) {
2011                nDelimIndex--;
2012                break;
2013            }
2014        }
2015    }
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) == " ") {
2021            nQueryStart += 1;
2022        }
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);
2027    }
2028    // No delimiter found in the query, so there are no selections from past queries
2029    else {
2030        sPrevious = "";
2031    }
2032    
2033    return {
2034        previous: sPrevious,
2035        query: sQuery
2036    };
2037};
2038
2039/**
2040 * Syncs results container with its helpers.
2041 *
2042 * @method _toggleContainerHelpers
2043 * @param bShow {Boolean} True if container is expanded, false if collapsed
2044 * @private
2045 */
2046YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
2047    var width = this._elContent.offsetWidth + "px";
2048    var height = this._elContent.offsetHeight + "px";
2049
2050    if(this.useIFrame && this._elIFrame) {
2051    var elIFrame = this._elIFrame;
2052        if(bShow) {
2053            elIFrame.style.width = width;
2054            elIFrame.style.height = height;
2055            elIFrame.style.padding = "";
2056            YAHOO.log("Iframe expanded", "info", this.toString());
2057        }
2058        else {
2059            elIFrame.style.width = 0;
2060            elIFrame.style.height = 0;
2061            elIFrame.style.padding = 0;
2062            YAHOO.log("Iframe collapsed", "info", this.toString());
2063        }
2064    }
2065    if(this.useShadow && this._elShadow) {
2066    var elShadow = this._elShadow;
2067        if(bShow) {
2068            elShadow.style.width = width;
2069            elShadow.style.height = height;
2070            YAHOO.log("Shadow expanded", "info", this.toString());
2071        }
2072        else {
2073            elShadow.style.width = 0;
2074            elShadow.style.height = 0;
2075            YAHOO.log("Shadow collapsed", "info", this.toString());
2076        }
2077    }
2078};
2079
2080/**
2081 * Animates expansion or collapse of the container.
2082 *
2083 * @method _toggleContainer
2084 * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
2085 * @private
2086 */
2087YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
2088    YAHOO.log("Toggling container " + ((bShow) ? "open" : "closed"), "info", this.toString());
2089
2090    var elContainer = this._elContainer;
2091
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) {
2095        return;
2096    }
2097    
2098    // Reset states
2099    if(!bShow) {
2100        this._toggleHighlight(this._elCurListItem,"from");
2101        this._nDisplayedItems = 0;
2102        this._sCurQuery = null;
2103        
2104        // Container is already closed, so don't bother with changing the UI
2105        if(this._elContent.style.display == "none") {
2106            return;
2107        }
2108    }
2109
2110    // If animation is enabled...
2111    var oAnim = this._oAnim;
2112    if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
2113        if(oAnim.isAnimated()) {
2114            oAnim.stop(true);
2115        }
2116
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 = "";
2124
2125        // Current size of the container is the EXPANDED size
2126        var wExp = oClone.offsetWidth;
2127        var hExp = oClone.offsetHeight;
2128
2129        // Calculate COLLAPSED sizes based on horiz and vert anim
2130        var wColl = (this.animHoriz) ? 0 : wExp;
2131        var hColl = (this.animVert) ? 0 : hExp;
2132
2133        // Set animation sizes
2134        oAnim.attributes = (bShow) ?
2135            {width: { to: wExp }, height: { to: hExp }} :
2136            {width: { to: wColl}, height: { to: hColl }};
2137
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";
2142        }
2143        // Else, set it to its last known size.
2144        else {
2145            this._elContent.style.width = wExp+"px";
2146            this._elContent.style.height = hExp+"px";
2147        }
2148
2149        elContainer.removeChild(oClone);
2150        oClone = null;
2151
2152    	var oSelf = this;
2153    	var onAnimComplete = function() {
2154            // Finish the collapse
2155    		oAnim.onComplete.unsubscribeAll();
2156
2157            if(bShow) {
2158                oSelf._toggleContainerHelpers(true);
2159                oSelf._bContainerOpen = bShow;
2160                oSelf.containerExpandEvent.fire(oSelf);
2161                YAHOO.log("Container expanded", "info", oSelf.toString());
2162            }
2163            else {
2164                oSelf._elContent.style.display = "none";
2165                oSelf._bContainerOpen = bShow;
2166                oSelf.containerCollapseEvent.fire(oSelf);
2167                YAHOO.log("Container collapsed", "info", oSelf.toString());
2168            }
2169     	};
2170
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);
2175        oAnim.animate();
2176    }
2177    // Else don't animate, just show or hide
2178    else {
2179        if(bShow) {
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());
2185        }
2186        else {
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());
2192        }
2193   }
2194
2195};
2196
2197/**
2198 * Toggles the highlight on or off for an item in the container, and also cleans
2199 * up highlighting of any previous item.
2200 *
2201 * @method _toggleHighlight
2202 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2203 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2204 * @private
2205 */
2206YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(elNewListItem, sType) {
2207    if(elNewListItem) {
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;
2213        }
2214    
2215        if((sType == "to") && sHighlight) {
2216            // Apply highlight to new item
2217            YAHOO.util.Dom.addClass(elNewListItem, sHighlight);
2218            this._elCurListItem = elNewListItem;
2219        }
2220    }
2221};
2222
2223/**
2224 * Toggles the pre-highlight on or off for an item in the container.
2225 *
2226 * @method _togglePrehighlight
2227 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2228 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2229 * @private
2230 */
2231YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(elNewListItem, sType) {
2232    if(elNewListItem == this._elCurListItem) {
2233        return;
2234    }
2235
2236    var sPrehighlight = this.prehighlightClassName;
2237    if((sType == "mouseover") && sPrehighlight) {
2238        // Apply prehighlight to new item
2239        YAHOO.util.Dom.addClass(elNewListItem, sPrehighlight);
2240    }
2241    else {
2242        // Remove prehighlight from old item
2243        YAHOO.util.Dom.removeClass(elNewListItem, sPrehighlight);
2244    }
2245};
2246
2247/**
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.
2250 *
2251 * @method _updateValue
2252 * @param elListItem {HTMLElement} The &lt;li&gt; element item with which to update the value.
2253 * @private
2254 */
2255YAHOO.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;
2260    
2261        // Calculate the new value
2262        var sNewValue = "";
2263        if(sDelimChar) {
2264            // Preserve selections from past queries
2265            sNewValue = this._sPastSelections;
2266            // Add new selection plus delimiter
2267            sNewValue += sResultMatch + sDelimChar;
2268            if(sDelimChar != " ") {
2269                sNewValue += " ";
2270            }
2271        }
2272        else { 
2273            sNewValue = sResultMatch;
2274        }
2275        
2276        // Update input field
2277        elTextbox.value = sNewValue;
2278    
2279        // Scroll to bottom of textarea if necessary
2280        if(elTextbox.type == "textarea") {
2281            elTextbox.scrollTop = elTextbox.scrollHeight;
2282        }
2283    
2284        // Move cursor to end
2285        var end = elTextbox.value.length;
2286        this._selectText(elTextbox,end,end);
2287    
2288        this._elCurListItem = elListItem;
2289    }
2290};
2291
2292/**
2293 * Selects a result item from the container
2294 *
2295 * @method _selectItem
2296 * @param elListItem {HTMLElement} The selected &lt;li&gt; element item.
2297 * @private
2298 */
2299YAHOO.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);
2307};
2308
2309/**
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
2312 * is closed.
2313 *
2314 * @method _jumpSelection
2315 * @private
2316 */
2317YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
2318    if(this._elCurListItem) {
2319        this._selectItem(this._elCurListItem);
2320    }
2321    else {
2322        this._toggleContainer(false);
2323    }
2324};
2325
2326/**
2327 * Triggered by up and down arrow keys, changes the current highlighted
2328 * &lt;li&gt; element item. Scrolls container if necessary.
2329 *
2330 * @method _moveSelection
2331 * @param nKeyCode {Number} Code of key pressed.
2332 * @private
2333 */
2334YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
2335    if(this._bContainerOpen) {
2336        // Determine current item's id number
2337        var elCurListItem = this._elCurListItem,
2338            nCurItemIndex = -1;
2339
2340        if(elCurListItem) {
2341            nCurItemIndex = elCurListItem._nItemIndex;
2342        }
2343
2344        var nNewItemIndex = (nKeyCode == 40) ?
2345                (nCurItemIndex + 1) : (nCurItemIndex - 1);
2346
2347        // Out of bounds
2348        if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
2349            return;
2350        }
2351
2352        if(elCurListItem) {
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());
2357        }
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;
2362            }
2363            else {
2364                this._elTextbox.value = this._sCurQuery;
2365            }
2366            return;
2367        }
2368        if(nNewItemIndex == -2) {
2369            // Close container
2370            this._toggleContainer(false);
2371            return;
2372        }
2373        
2374        var elNewListItem = this._elList.childNodes[nNewItemIndex],
2375
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;
2389                }
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;
2394
2395                }
2396            }
2397            // User is keying up
2398            else {
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;
2403                }
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;
2408                }
2409            }
2410        }
2411
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);
2417        }
2418    }
2419};
2420
2421/////////////////////////////////////////////////////////////////////////////
2422//
2423// Private event handlers
2424//
2425/////////////////////////////////////////////////////////////////////////////
2426
2427/**
2428 * Handles container mouseover events.
2429 *
2430 * @method _onContainerMouseover
2431 * @param v {HTMLEvent} The mouseover event.
2432 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2433 * @private
2434 */
2435YAHOO.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")) {
2439        switch(elTag) {
2440            case "body":
2441                return;
2442            case "li":
2443                if(oSelf.prehighlightClassName) {
2444                    oSelf._togglePrehighlight(elTarget,"mouseover");
2445                }
2446                else {
2447                    oSelf._toggleHighlight(elTarget,"to");
2448                }
2449            
2450                oSelf.itemMouseOverEvent.fire(oSelf, elTarget);
2451                YAHOO.log("Item moused over " + elTarget._nItemIndex, "info", oSelf.toString());
2452                break;
2453            case "div":
2454                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2455                    oSelf._bOverContainer = true;
2456                    return;
2457                }
2458                break;
2459            default:
2460                break;
2461        }
2462        
2463        elTarget = elTarget.parentNode;
2464        if(elTarget) {
2465            elTag = elTarget.nodeName.toLowerCase();
2466        }
2467    }
2468};
2469
2470/**
2471 * Handles container mouseout events.
2472 *
2473 * @method _onContainerMouseout
2474 * @param v {HTMLEvent} The mouseout event.
2475 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2476 * @private
2477 */
2478YAHOO.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")) {
2482        switch(elTag) {
2483            case "body":
2484                return;
2485            case "li":
2486                if(oSelf.prehighlightClassName) {
2487                    oSelf._togglePrehighlight(elTarget,"mouseout");
2488                }
2489                else {
2490                    oSelf._toggleHighlight(elTarget,"from");
2491                }
2492            
2493                oSelf.itemMouseOutEvent.fire(oSelf, elTarget);
2494                YAHOO.log("Item moused out " + elTarget._nItemIndex, "info", oSelf.toString());
2495                break;
2496            case "ul":
2497                oSelf._toggleHighlight(oSelf._elCurListItem,"to");
2498                break;
2499            case "div":
2500                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2501                    oSelf._bOverContainer = false;
2502                    return;
2503                }
2504                break;
2505            default:
2506                break;
2507        }
2508
2509        elTarget = elTarget.parentNode;
2510        if(elTarget) {
2511            elTag = elTarget.nodeName.toLowerCase();
2512        }
2513    }
2514};
2515
2516/**
2517 * Handles container click events.
2518 *
2519 * @method _onContainerClick
2520 * @param v {HTMLEvent} The click event.
2521 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2522 * @private
2523 */
2524YAHOO.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")) {
2528        switch(elTag) {
2529            case "body":
2530                return;
2531            case "li":
2532                // In case item has not been moused over
2533                oSelf._toggleHighlight(elTarget,"to");
2534                oSelf._selectItem(elTarget);
2535                return;
2536            default:
2537                break;
2538        }
2539
2540        elTarget = elTarget.parentNode;
2541        if(elTarget) {
2542            elTag = elTarget.nodeName.toLowerCase();
2543        }
2544    }    
2545};
2546
2547
2548/**
2549 * Handles container scroll events.
2550 *
2551 * @method _onContainerScroll
2552 * @param v {HTMLEvent} The scroll event.
2553 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2554 * @private
2555 */
2556YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
2557    oSelf._focus();
2558};
2559
2560/**
2561 * Handles container resize events.
2562 *
2563 * @method _onContainerResize
2564 * @param v {HTMLEvent} The resize event.
2565 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2566 * @private
2567 */
2568YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
2569    oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
2570};
2571
2572
2573/**
2574 * Handles textbox keydown events of functional keys, mainly for UI behavior.
2575 *
2576 * @method _onTextboxKeyDown
2577 * @param v {HTMLEvent} The keydown event.
2578 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2579 * @private
2580 */
2581YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
2582    var nKeyCode = v.keyCode;
2583
2584    // Clear timeout
2585    if(oSelf._nTypeAheadDelayID != -1) {
2586        clearTimeout(oSelf._nTypeAheadDelayID);
2587    }
2588    
2589    switch (nKeyCode) {
2590        case 9: // tab
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);
2597                        }
2598                    }
2599                    oSelf._selectItem(oSelf._elCurListItem);
2600                }
2601                else {
2602                    oSelf._toggleContainer(false);
2603                }
2604            }
2605            break;
2606        case 13: // enter
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);
2612                        }
2613                    }
2614                    oSelf._selectItem(oSelf._elCurListItem);
2615                }
2616                else {
2617                    oSelf._toggleContainer(false);
2618                }
2619            }
2620            break;
2621        case 27: // esc
2622            oSelf._toggleContainer(false);
2623            return;
2624        case 39: // right
2625            oSelf._jumpSelection();
2626            break;
2627        case 38: // up
2628            if(oSelf._bContainerOpen) {
2629                YAHOO.util.Event.stopEvent(v);
2630                oSelf._moveSelection(nKeyCode);
2631            }
2632            break;
2633        case 40: // down
2634            if(oSelf._bContainerOpen) {
2635                YAHOO.util.Event.stopEvent(v);
2636                oSelf._moveSelection(nKeyCode);
2637            }
2638            break;
2639        default: 
2640            oSelf._bItemSelected = false;
2641            oSelf._toggleHighlight(oSelf._elCurListItem, "from");
2642
2643            oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
2644            YAHOO.log("Textbox keyed", "info", oSelf.toString());
2645            break;
2646    }
2647
2648    if(nKeyCode === 18){
2649        oSelf._enableIntervalDetection();
2650    }    
2651    oSelf._nKeyCode = nKeyCode;
2652};
2653
2654/**
2655 * Handles textbox keypress events.
2656 * @method _onTextboxKeyPress
2657 * @param v {HTMLEvent} The keypress event.
2658 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2659 * @private
2660 */
2661YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
2662    var nKeyCode = v.keyCode;
2663
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)) {
2667            switch (nKeyCode) {
2668            case 9: // tab
2669                // select an item or clear out
2670                if(oSelf._bContainerOpen) {
2671                    if(oSelf.delimChar) {
2672                        YAHOO.util.Event.stopEvent(v);
2673                    }
2674                    if(oSelf._elCurListItem) {
2675                        oSelf._selectItem(oSelf._elCurListItem);
2676                    }
2677                    else {
2678                        oSelf._toggleContainer(false);
2679                    }
2680                }
2681                break;
2682            case 13: // enter
2683                if(oSelf._bContainerOpen) {
2684                    YAHOO.util.Event.stopEvent(v);
2685                    if(oSelf._elCurListItem) {
2686                        oSelf._selectItem(oSelf._elCurListItem);
2687                    }
2688                    else {
2689                        oSelf._toggleContainer(false);
2690                    }
2691                }
2692                break;
2693            default:
2694                break;
2695            }
2696        }
2697
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();
2702        }
2703};
2704
2705/**
2706 * Handles textbox keyup events to trigger queries.
2707 *
2708 * @method _onTextboxKeyUp
2709 * @param v {HTMLEvent} The keyup event.
2710 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2711 * @private
2712 */
2713YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
2714    var sText = this.value; //string in textbox
2715    
2716    // Check to see if any of the public properties have been updated
2717    oSelf._initProps();
2718
2719    // Filter out chars that don't trigger queries
2720    var nKeyCode = v.keyCode;
2721    if(oSelf._isIgnoreKey(nKeyCode)) {
2722        return;
2723    }
2724
2725    // Clear previous timeout
2726    /*if(oSelf._nTypeAheadDelayID != -1) {
2727        clearTimeout(oSelf._nTypeAheadDelayID);
2728    }*/
2729    if(oSelf._nDelayID != -1) {
2730        clearTimeout(oSelf._nDelayID);
2731    }
2732
2733    // Set new timeout
2734    oSelf._nDelayID = setTimeout(function(){
2735            oSelf._sendQuery(sText);
2736        },(oSelf.queryDelay * 1000));
2737
2738     //= nDelayID;
2739    //else {
2740        // No delay so send request immediately
2741        //oSelf._sendQuery(sText);
2742   //}
2743};
2744
2745/**
2746 * Handles text input box receiving focus.
2747 *
2748 * @method _onTextboxFocus
2749 * @param v {HTMLEvent} The focus event.
2750 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2751 * @private
2752 */
2753YAHOO.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());
2761    }
2762};
2763
2764/**
2765 * Handles text input box losing focus.
2766 *
2767 * @method _onTextboxBlur
2768 * @param v {HTMLEvent} The focus event.
2769 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2770 * @private
2771 */
2772YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
2773    // Is a true blur
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();
2783                }
2784                // Treat current query as a valid selection
2785                else {
2786                    oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery);
2787                    YAHOO.log("Unmatched item selected: " + oSelf._sCurQuery, "info", oSelf.toString());
2788                }
2789            }
2790            // Container is open and current query matches a result
2791            else {
2792                // Force a selection when textbox is blurred with a match
2793                if(oSelf.forceSelection) {
2794                    oSelf._selectItem(elMatchListItem);
2795                }
2796            }
2797        }
2798
2799        oSelf._clearInterval();
2800        oSelf._bFocused = false;
2801        if(oSelf._sInitInputValue !== oSelf._elTextbox.value) {
2802            oSelf.textboxChangeEvent.fire(oSelf);
2803        }
2804        oSelf.textboxBlurEvent.fire(oSelf);
2805        YAHOO.log("Textbox blurred", "info", oSelf.toString());
2806
2807        oSelf._toggleContainer(false);
2808    }
2809    // Not a true blur if it was a selection via mouse click
2810    else {
2811        oSelf._focus();
2812    }
2813};
2814
2815/**
2816 * Handles window unload event.
2817 *
2818 * @method _onWindowUnload
2819 * @param v {HTMLEvent} The unload event.
2820 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2821 * @private
2822 */
2823YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
2824    if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
2825        oSelf._elTextbox.setAttribute("autocomplete","on");
2826    }
2827};
2828
2829/////////////////////////////////////////////////////////////////////////////
2830//
2831// Deprecated for Backwards Compatibility
2832//
2833/////////////////////////////////////////////////////////////////////////////
2834/**
2835 * @method doBeforeSendQuery
2836 * @deprecated Use generateRequest.
2837 */
2838YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
2839    return this.generateRequest(sQuery);
2840};
2841
2842/**
2843 * @method getListItems
2844 * @deprecated Use getListEl().childNodes.
2845 */
2846YAHOO.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];
2851    }
2852    return allListItemEls;
2853};
2854
2855/////////////////////////////////////////////////////////////////////////
2856//
2857// Private static methods
2858//
2859/////////////////////////////////////////////////////////////////////////
2860
2861/**
2862 * Clones object literal or array of object literals.
2863 *
2864 * @method AutoComplete._cloneObject
2865 * @param o {Object} Object.
2866 * @private
2867 * @static     
2868 */
2869YAHOO.widget.AutoComplete._cloneObject = function(o) {
2870    if(!YAHOO.lang.isValue(o)) {
2871        return o;
2872    }
2873    
2874    var copy = {};
2875    
2876    if(YAHOO.lang.isFunction(o)) {
2877        copy = o;
2878    }
2879    else if(YAHOO.lang.isArray(o)) {
2880        var array = [];
2881        for(var i=0,len=o.length;i<len;i++) {
2882            array[i] = YAHOO.widget.AutoComplete._cloneObject(o[i]);
2883        }
2884        copy = array;
2885    }
2886    else if(YAHOO.lang.isObject(o)) { 
2887        for (var x in 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]);
2891                }
2892                else {
2893                    copy[x] = o[x];
2894                }
2895            }
2896        }
2897    }
2898    else {
2899        copy = o;
2900    }
2901
2902    return copy;
2903};
2904
2905
2906
2907
2908YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.7.0", build: "1799"});