Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('yui2-autocomplete', function(Y) {
2
    var YAHOO    = Y.YUI2;
3
    /*
4
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
5
Code licensed under the BSD License:
6
http://developer.yahoo.com/yui/license.html
7
version: 2.9.0
8
*/
9
/////////////////////////////////////////////////////////////////////////////
10
//
11
// YAHOO.widget.DataSource Backwards Compatibility
12
//
13
/////////////////////////////////////////////////////////////////////////////
14
 
15
YAHOO.widget.DS_JSArray = YAHOO.util.LocalDataSource;
16
 
17
YAHOO.widget.DS_JSFunction = YAHOO.util.FunctionDataSource;
18
 
19
YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
20
    var DS = new YAHOO.util.XHRDataSource(sScriptURI, oConfigs);
21
    DS._aDeprecatedSchema = aSchema;
22
    return DS;
23
};
24
 
25
YAHOO.widget.DS_ScriptNode = function(sScriptURI, aSchema, oConfigs) {
26
    var DS = new YAHOO.util.ScriptNodeDataSource(sScriptURI, oConfigs);
27
    DS._aDeprecatedSchema = aSchema;
28
    return DS;
29
};
30
 
31
YAHOO.widget.DS_XHR.TYPE_JSON = YAHOO.util.DataSourceBase.TYPE_JSON;
32
YAHOO.widget.DS_XHR.TYPE_XML = YAHOO.util.DataSourceBase.TYPE_XML;
33
YAHOO.widget.DS_XHR.TYPE_FLAT = YAHOO.util.DataSourceBase.TYPE_TEXT;
34
 
35
// TODO: widget.DS_ScriptNode.scriptCallbackParam
36
 
37
 
38
 
39
 /**
40
 * The AutoComplete control provides the front-end logic for text-entry suggestion and
41
 * completion functionality.
42
 *
43
 * @module autocomplete
44
 * @requires yahoo, dom, event, datasource
45
 * @optional animation
46
 * @namespace YAHOO.widget
47
 * @title AutoComplete Widget
48
 */
49
 
50
/****************************************************************************/
51
/****************************************************************************/
52
/****************************************************************************/
53
 
54
/**
55
 * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
56
 * auto completion widget.  Some key features:
57
 * <ul>
58
 * <li>Navigate with up/down arrow keys and/or mouse to pick a selection</li>
59
 * <li>The drop down container can "roll down" or "fly out" via configurable
60
 * animation</li>
61
 * <li>UI look-and-feel customizable through CSS, including container
62
 * attributes, borders, position, fonts, etc</li>
63
 * </ul>
64
 *
65
 * @class AutoComplete
66
 * @constructor
67
 * @param elInput {HTMLElement} DOM element reference of an input field.
68
 * @param elInput {String} String ID of an input field.
69
 * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
70
 * @param elContainer {String} String ID of an existing DIV.
71
 * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
72
 * @param oConfigs {Object} (optional) Object literal of configuration params.
73
 */
74
YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
75
    if(elInput && elContainer && oDataSource) {
76
        // Validate DataSource
77
        if(oDataSource && YAHOO.lang.isFunction(oDataSource.sendRequest)) {
78
            this.dataSource = oDataSource;
79
        }
80
        else {
81
            YAHOO.log("Could not instantiate AutoComplete due to an invalid DataSource", "error", this.toString());
82
            return;
83
        }
84
 
85
        // YAHOO.widget.DataSource schema backwards compatibility
86
        // Converted deprecated schema into supported schema
87
        // First assume key data is held in position 0 of results array
88
        this.key = 0;
89
        var schema = oDataSource.responseSchema;
90
        // An old school schema has been defined in the deprecated DataSource constructor
91
        if(oDataSource._aDeprecatedSchema) {
92
            var aDeprecatedSchema = oDataSource._aDeprecatedSchema;
93
            if(YAHOO.lang.isArray(aDeprecatedSchema)) {
94
 
95
                if((oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_JSON) ||
96
                (oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_UNKNOWN)) { // Used to default to unknown
97
                    // Store the resultsList
98
                    schema.resultsList = aDeprecatedSchema[0];
99
                    // Store the key
100
                    this.key = aDeprecatedSchema[1];
101
                    // Only resultsList and key are defined, so grab all the data
102
                    schema.fields = (aDeprecatedSchema.length < 3) ? null : aDeprecatedSchema.slice(1);
103
                }
104
                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_XML) {
105
                    schema.resultNode = aDeprecatedSchema[0];
106
                    this.key = aDeprecatedSchema[1];
107
                    schema.fields = aDeprecatedSchema.slice(1);
108
                }
109
                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_TEXT) {
110
                    schema.recordDelim = aDeprecatedSchema[0];
111
                    schema.fieldDelim = aDeprecatedSchema[1];
112
                }
113
                oDataSource.responseSchema = schema;
114
            }
115
        }
116
 
117
        // Validate input element
118
        if(YAHOO.util.Dom.inDocument(elInput)) {
119
            if(YAHOO.lang.isString(elInput)) {
120
                    this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
121
                    this._elTextbox = document.getElementById(elInput);
122
            }
123
            else {
124
                this._sName = (elInput.id) ?
125
                    "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
126
                    "instance" + YAHOO.widget.AutoComplete._nIndex;
127
                this._elTextbox = elInput;
128
            }
129
            YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
130
        }
131
        else {
132
            YAHOO.log("Could not instantiate AutoComplete due to an invalid input element", "error", this.toString());
133
            return;
134
        }
135
 
136
        // Validate container element
137
        if(YAHOO.util.Dom.inDocument(elContainer)) {
138
            if(YAHOO.lang.isString(elContainer)) {
139
                    this._elContainer = document.getElementById(elContainer);
140
            }
141
            else {
142
                this._elContainer = elContainer;
143
            }
144
            if(this._elContainer.style.display == "none") {
145
                YAHOO.log("The container may not display properly if display is set to \"none\" in CSS", "warn", this.toString());
146
            }
147
 
148
            // For skinning
149
            var elParent = this._elContainer.parentNode;
150
            var elTag = elParent.tagName.toLowerCase();
151
            if(elTag == "div") {
152
                YAHOO.util.Dom.addClass(elParent, "yui-ac");
153
            }
154
            else {
155
                YAHOO.log("Could not find the wrapper element for skinning", "warn", this.toString());
156
            }
157
        }
158
        else {
159
            YAHOO.log("Could not instantiate AutoComplete due to an invalid container element", "error", this.toString());
160
            return;
161
        }
162
 
163
        // Default applyLocalFilter setting is to enable for local sources
164
        if(this.dataSource.dataType === YAHOO.util.DataSourceBase.TYPE_LOCAL) {
165
            this.applyLocalFilter = true;
166
        }
167
 
168
        // Set any config params passed in to override defaults
169
        if(oConfigs && (oConfigs.constructor == Object)) {
170
            for(var sConfig in oConfigs) {
171
                if(sConfig) {
172
                    this[sConfig] = oConfigs[sConfig];
173
                }
174
            }
175
        }
176
 
177
        // Initialization sequence
178
        this._initContainerEl();
179
        this._initProps();
180
        this._initListEl();
181
        this._initContainerHelperEls();
182
 
183
        // Set up events
184
        var oSelf = this;
185
        var elTextbox = this._elTextbox;
186
 
187
        // Dom events
188
        YAHOO.util.Event.addListener(elTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
189
        YAHOO.util.Event.addListener(elTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
190
        YAHOO.util.Event.addListener(elTextbox,"focus",oSelf._onTextboxFocus,oSelf);
191
        YAHOO.util.Event.addListener(elTextbox,"blur",oSelf._onTextboxBlur,oSelf);
192
        YAHOO.util.Event.addListener(elContainer,"mouseover",oSelf._onContainerMouseover,oSelf);
193
        YAHOO.util.Event.addListener(elContainer,"mouseout",oSelf._onContainerMouseout,oSelf);
194
        YAHOO.util.Event.addListener(elContainer,"click",oSelf._onContainerClick,oSelf);
195
        YAHOO.util.Event.addListener(elContainer,"scroll",oSelf._onContainerScroll,oSelf);
196
        YAHOO.util.Event.addListener(elContainer,"resize",oSelf._onContainerResize,oSelf);
197
        YAHOO.util.Event.addListener(elTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
198
        YAHOO.util.Event.addListener(window,"unload",oSelf._onWindowUnload,oSelf);
199
 
200
        // Custom events
201
        this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
202
        this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
203
        this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
204
        this.dataRequestCancelEvent = new YAHOO.util.CustomEvent("dataRequestCancel", this);
205
        this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
206
        this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
207
        this.containerPopulateEvent = new YAHOO.util.CustomEvent("containerPopulate", this);
208
        this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
209
        this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
210
        this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
211
        this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
212
        this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
213
        this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
214
        this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
215
        this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
216
        this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
217
        this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
218
        this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
219
        this.textboxChangeEvent = new YAHOO.util.CustomEvent("textboxChange", this);
220
 
221
        // Finish up
222
        elTextbox.setAttribute("autocomplete","off");
223
        YAHOO.widget.AutoComplete._nIndex++;
224
        YAHOO.log("AutoComplete initialized","info",this.toString());
225
    }
226
    // Required arguments were not found
227
    else {
228
        YAHOO.log("Could not instantiate AutoComplete due invalid arguments", "error", this.toString());
229
    }
230
};
231
 
232
/////////////////////////////////////////////////////////////////////////////
233
//
234
// Public member variables
235
//
236
/////////////////////////////////////////////////////////////////////////////
237
 
238
/**
239
 * The DataSource object that encapsulates the data used for auto completion.
240
 * This object should be an inherited object from YAHOO.widget.DataSource.
241
 *
242
 * @property dataSource
243
 * @type YAHOO.widget.DataSource
244
 */
245
YAHOO.widget.AutoComplete.prototype.dataSource = null;
246
 
247
/**
248
 * By default, results from local DataSources will pass through the filterResults
249
 * method to apply a client-side matching algorithm.
250
 *
251
 * @property applyLocalFilter
252
 * @type Boolean
253
 * @default true for local arrays and json, otherwise false
254
 */
255
YAHOO.widget.AutoComplete.prototype.applyLocalFilter = null;
256
 
257
/**
258
 * When applyLocalFilter is true, the local filtering algorthim can have case sensitivity
259
 * enabled.
260
 *
261
 * @property queryMatchCase
262
 * @type Boolean
263
 * @default false
264
 */
265
YAHOO.widget.AutoComplete.prototype.queryMatchCase = false;
266
 
267
/**
268
 * When applyLocalFilter is true, results can  be locally filtered to return
269
 * matching strings that "contain" the query string rather than simply "start with"
270
 * the query string.
271
 *
272
 * @property queryMatchContains
273
 * @type Boolean
274
 * @default false
275
 */
276
YAHOO.widget.AutoComplete.prototype.queryMatchContains = false;
277
 
278
/**
279
 * Enables query subset matching. When the DataSource's cache is enabled and queryMatchSubset is
280
 * true, substrings of queries will return matching cached results. For
281
 * instance, if the first query is for "abc" susequent queries that start with
282
 * "abc", like "abcd", will be queried against the cache, and not the live data
283
 * source. Recommended only for DataSources that return comprehensive results
284
 * for queries with very few characters.
285
 *
286
 * @property queryMatchSubset
287
 * @type Boolean
288
 * @default false
289
 *
290
 */
291
YAHOO.widget.AutoComplete.prototype.queryMatchSubset = false;
292
 
293
/**
294
 * Number of characters that must be entered before querying for results. A negative value
295
 * effectively turns off the widget. A value of 0 allows queries of null or empty string
296
 * values.
297
 *
298
 * @property minQueryLength
299
 * @type Number
300
 * @default 1
301
 */
302
YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
303
 
304
/**
305
 * Maximum number of results to display in results container.
306
 *
307
 * @property maxResultsDisplayed
308
 * @type Number
309
 * @default 10
310
 */
311
YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
312
 
313
/**
314
 * Number of seconds to delay before submitting a query request.  If a query
315
 * request is received before a previous one has completed its delay, the
316
 * previous request is cancelled and the new request is set to the delay. If
317
 * typeAhead is also enabled, this value must always be less than the typeAheadDelay
318
 * in order to avoid certain race conditions.
319
 *
320
 * @property queryDelay
321
 * @type Number
322
 * @default 0.2
323
 */
324
YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
325
 
326
/**
327
 * If typeAhead is true, number of seconds to delay before updating input with
328
 * typeAhead value. In order to prevent certain race conditions, this value must
329
 * always be greater than the queryDelay.
330
 *
331
 * @property typeAheadDelay
332
 * @type Number
333
 * @default 0.5
334
 */
335
YAHOO.widget.AutoComplete.prototype.typeAheadDelay = 0.5;
336
 
337
/**
338
 * When IME usage is detected or interval detection is explicitly enabled,
339
 * AutoComplete will detect the input value at the given interval and send a
340
 * query if the value has changed.
341
 *
342
 * @property queryInterval
343
 * @type Number
344
 * @default 500
345
 */
346
YAHOO.widget.AutoComplete.prototype.queryInterval = 500;
347
 
348
/**
349
 * Class name of a highlighted item within results container.
350
 *
351
 * @property highlightClassName
352
 * @type String
353
 * @default "yui-ac-highlight"
354
 */
355
YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
356
 
357
/**
358
 * Class name of a pre-highlighted item within results container.
359
 *
360
 * @property prehighlightClassName
361
 * @type String
362
 */
363
YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
364
 
365
/**
366
 * Query delimiter. A single character separator for multiple delimited
367
 * selections. Multiple delimiter characteres may be defined as an array of
368
 * strings. A null value or empty string indicates that query results cannot
369
 * be delimited. This feature is not recommended if you need forceSelection to
370
 * be true.
371
 *
372
 * @property delimChar
373
 * @type String | String[]
374
 */
375
YAHOO.widget.AutoComplete.prototype.delimChar = null;
376
 
377
/**
378
 * Whether or not the first item in results container should be automatically highlighted
379
 * on expand.
380
 *
381
 * @property autoHighlight
382
 * @type Boolean
383
 * @default true
384
 */
385
YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
386
 
387
/**
388
 * If autohighlight is enabled, whether or not the input field should be automatically updated
389
 * with the first query result as the user types, auto-selecting the substring portion
390
 * of the first result that the user has not yet typed.
391
 *
392
 * @property typeAhead
393
 * @type Boolean
394
 * @default false
395
 */
396
YAHOO.widget.AutoComplete.prototype.typeAhead = false;
397
 
398
/**
399
 * Whether or not to animate the expansion/collapse of the results container in the
400
 * horizontal direction.
401
 *
402
 * @property animHoriz
403
 * @type Boolean
404
 * @default false
405
 */
406
YAHOO.widget.AutoComplete.prototype.animHoriz = false;
407
 
408
/**
409
 * Whether or not to animate the expansion/collapse of the results container in the
410
 * vertical direction.
411
 *
412
 * @property animVert
413
 * @type Boolean
414
 * @default true
415
 */
416
YAHOO.widget.AutoComplete.prototype.animVert = true;
417
 
418
/**
419
 * Speed of container expand/collapse animation, in seconds..
420
 *
421
 * @property animSpeed
422
 * @type Number
423
 * @default 0.3
424
 */
425
YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
426
 
427
/**
428
 * Whether or not to force the user's selection to match one of the query
429
 * results. Enabling this feature essentially transforms the input field into a
430
 * &lt;select&gt; field. This feature is not recommended with delimiter character(s)
431
 * defined.
432
 *
433
 * @property forceSelection
434
 * @type Boolean
435
 * @default false
436
 */
437
YAHOO.widget.AutoComplete.prototype.forceSelection = false;
438
 
439
/**
440
 * Whether or not to allow browsers to cache user-typed input in the input
441
 * field. Disabling this feature will prevent the widget from setting the
442
 * autocomplete="off" on the input field. When autocomplete="off"
443
 * and users click the back button after form submission, user-typed input can
444
 * be prefilled by the browser from its cache. This caching of user input may
445
 * not be desired for sensitive data, such as credit card numbers, in which
446
 * case, implementers should consider setting allowBrowserAutocomplete to false.
447
 *
448
 * @property allowBrowserAutocomplete
449
 * @type Boolean
450
 * @default true
451
 */
452
YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
453
 
454
/**
455
 * Enabling this feature prevents the toggling of the container to a collapsed state.
456
 * Setting to true does not automatically trigger the opening of the container.
457
 * Implementers are advised to pre-load the container with an explicit "sendQuery()" call.
458
 *
459
 * @property alwaysShowContainer
460
 * @type Boolean
461
 * @default false
462
 */
463
YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
464
 
465
/**
466
 * Whether or not to use an iFrame to layer over Windows form elements in
467
 * IE. Set to true only when the results container will be on top of a
468
 * &lt;select&gt; field in IE and thus exposed to the IE z-index bug (i.e.,
469
 * 5.5 < IE < 7).
470
 *
471
 * @property useIFrame
472
 * @type Boolean
473
 * @default false
474
 */
475
YAHOO.widget.AutoComplete.prototype.useIFrame = false;
476
 
477
/**
478
 * Whether or not the results container should have a shadow.
479
 *
480
 * @property useShadow
481
 * @type Boolean
482
 * @default false
483
 */
484
YAHOO.widget.AutoComplete.prototype.useShadow = false;
485
 
486
/**
487
 * Whether or not the input field should be updated with selections.
488
 *
489
 * @property suppressInputUpdate
490
 * @type Boolean
491
 * @default false
492
 */
493
YAHOO.widget.AutoComplete.prototype.suppressInputUpdate = false;
494
 
495
/**
496
 * For backward compatibility to pre-2.6.0 formatResults() signatures, setting
497
 * resultsTypeList to true will take each object literal result returned by
498
 * DataSource and flatten into an array.
499
 *
500
 * @property resultTypeList
501
 * @type Boolean
502
 * @default true
503
 */
504
YAHOO.widget.AutoComplete.prototype.resultTypeList = true;
505
 
506
/**
507
 * For XHR DataSources, AutoComplete will automatically insert a "?" between the server URI and
508
 * the "query" param/value pair. To prevent this behavior, implementers should
509
 * set this value to false. To more fully customize the query syntax, implementers
510
 * should override the generateRequest() method.
511
 *
512
 * @property queryQuestionMark
513
 * @type Boolean
514
 * @default true
515
 */
516
YAHOO.widget.AutoComplete.prototype.queryQuestionMark = true;
517
 
518
/**
519
 * If true, before each time the container expands, the container element will be
520
 * positioned to snap to the bottom-left corner of the input element. If
521
 * autoSnapContainer is set to false, this positioning will not be done.
522
 *
523
 * @property autoSnapContainer
524
 * @type Boolean
525
 * @default true
526
 */
527
YAHOO.widget.AutoComplete.prototype.autoSnapContainer = true;
528
 
529
/////////////////////////////////////////////////////////////////////////////
530
//
531
// Public methods
532
//
533
/////////////////////////////////////////////////////////////////////////////
534
 
535
 /**
536
 * Public accessor to the unique name of the AutoComplete instance.
537
 *
538
 * @method toString
539
 * @return {String} Unique name of the AutoComplete instance.
540
 */
541
YAHOO.widget.AutoComplete.prototype.toString = function() {
542
    return "AutoComplete " + this._sName;
543
};
544
 
545
 /**
546
 * Returns DOM reference to input element.
547
 *
548
 * @method getInputEl
549
 * @return {HTMLELement} DOM reference to input element.
550
 */
551
YAHOO.widget.AutoComplete.prototype.getInputEl = function() {
552
    return this._elTextbox;
553
};
554
 
555
 /**
556
 * Returns DOM reference to container element.
557
 *
558
 * @method getContainerEl
559
 * @return {HTMLELement} DOM reference to container element.
560
 */
561
YAHOO.widget.AutoComplete.prototype.getContainerEl = function() {
562
    return this._elContainer;
563
};
564
 
565
 /**
566
 * Returns true if widget instance is currently active.
567
 *
568
 * @method isFocused
569
 * @return {Boolean} Returns true if widget instance is currently active.
570
 */
571
YAHOO.widget.AutoComplete.prototype.isFocused = function() {
572
    return this._bFocused;
573
};
574
 
575
 /**
576
 * Returns true if container is in an expanded state, false otherwise.
577
 *
578
 * @method isContainerOpen
579
 * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
580
 */
581
YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
582
    return this._bContainerOpen;
583
};
584
 
585
/**
586
 * Public accessor to the &lt;ul&gt; element that displays query results within the results container.
587
 *
588
 * @method getListEl
589
 * @return {HTMLElement[]} Reference to &lt;ul&gt; element within the results container.
590
 */
591
YAHOO.widget.AutoComplete.prototype.getListEl = function() {
592
    return this._elList;
593
};
594
 
595
/**
596
 * Public accessor to the matching string associated with a given &lt;li&gt; result.
597
 *
598
 * @method getListItemMatch
599
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
600
 * @return {String} Matching string.
601
 */
602
YAHOO.widget.AutoComplete.prototype.getListItemMatch = function(elListItem) {
603
    if(elListItem._sResultMatch) {
604
        return elListItem._sResultMatch;
605
    }
606
    else {
607
        return null;
608
    }
609
};
610
 
611
/**
612
 * Public accessor to the result data associated with a given &lt;li&gt; result.
613
 *
614
 * @method getListItemData
615
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
616
 * @return {Object} Result data.
617
 */
618
YAHOO.widget.AutoComplete.prototype.getListItemData = function(elListItem) {
619
    if(elListItem._oResultData) {
620
        return elListItem._oResultData;
621
    }
622
    else {
623
        return null;
624
    }
625
};
626
 
627
/**
628
 * Public accessor to the index of the associated with a given &lt;li&gt; result.
629
 *
630
 * @method getListItemIndex
631
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
632
 * @return {Number} Index.
633
 */
634
YAHOO.widget.AutoComplete.prototype.getListItemIndex = function(elListItem) {
635
    if(YAHOO.lang.isNumber(elListItem._nItemIndex)) {
636
        return elListItem._nItemIndex;
637
    }
638
    else {
639
        return null;
640
    }
641
};
642
 
643
/**
644
 * Sets HTML markup for the results container header. This markup will be
645
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-hd".
646
 *
647
 * @method setHeader
648
 * @param sHeader {HTML} HTML markup for results container header.
649
 */
650
YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
651
    if(this._elHeader) {
652
        var elHeader = this._elHeader;
653
        if(sHeader) {
654
            elHeader.innerHTML = sHeader;
655
            elHeader.style.display = "";
656
        }
657
        else {
658
            elHeader.innerHTML = "";
659
            elHeader.style.display = "none";
660
        }
661
    }
662
};
663
 
664
/**
665
 * Sets HTML markup for the results container footer. This markup will be
666
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-ft".
667
 *
668
 * @method setFooter
669
 * @param sFooter {HTML} HTML markup for results container footer.
670
 */
671
YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
672
    if(this._elFooter) {
673
        var elFooter = this._elFooter;
674
        if(sFooter) {
675
                elFooter.innerHTML = sFooter;
676
                elFooter.style.display = "";
677
        }
678
        else {
679
            elFooter.innerHTML = "";
680
            elFooter.style.display = "none";
681
        }
682
    }
683
};
684
 
685
/**
686
 * Sets HTML markup for the results container body. This markup will be
687
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-bd".
688
 *
689
 * @method setBody
690
 * @param sBody {HTML} HTML markup for results container body.
691
 */
692
YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
693
    if(this._elBody) {
694
        var elBody = this._elBody;
695
        YAHOO.util.Event.purgeElement(elBody, true);
696
        if(sBody) {
697
            elBody.innerHTML = sBody;
698
            elBody.style.display = "";
699
        }
700
        else {
701
            elBody.innerHTML = "";
702
            elBody.style.display = "none";
703
        }
704
        this._elList = null;
705
    }
706
};
707
 
708
/**
709
* A function that converts an AutoComplete query into a request value which is then
710
* passed to the DataSource's sendRequest method in order to retrieve data for
711
* the query. By default, returns a String with the syntax: "query={query}"
712
* Implementers can customize this method for custom request syntaxes.
713
*
714
* @method generateRequest
715
* @param sQuery {String} Query string
716
* @return {MIXED} Request
717
*/
718
YAHOO.widget.AutoComplete.prototype.generateRequest = function(sQuery) {
719
    var dataType = this.dataSource.dataType;
720
 
721
    // Transform query string in to a request for remote data
722
    // By default, local data doesn't need a transformation, just passes along the query as is.
723
    if(dataType === YAHOO.util.DataSourceBase.TYPE_XHR) {
724
        // By default, XHR GET requests look like "{scriptURI}?{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
725
        if(!this.dataSource.connMethodPost) {
726
            sQuery = (this.queryQuestionMark ? "?" : "") + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery +
727
                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
728
        }
729
        // By default, XHR POST bodies are sent to the {scriptURI} like "{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
730
        else {
731
            sQuery = (this.dataSource.scriptQueryParam || "query") + "=" + sQuery +
732
                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
733
        }
734
    }
735
    // By default, remote script node requests look like "{scriptURI}&{scriptCallbackParam}={callbackString}&{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
736
    else if(dataType === YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE) {
737
        sQuery = "&" + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery +
738
            (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
739
    }
740
 
741
    return sQuery;
742
};
743
 
744
/**
745
 * Makes query request to the DataSource.
746
 *
747
 * @method sendQuery
748
 * @param sQuery {String} Query string.
749
 */
750
YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
751
    // Activate focus for a new interaction
752
    this._bFocused = true;
753
 
754
    // Adjust programatically sent queries to look like they were input by user
755
    // when delimiters are enabled
756
    var newQuery = (this.delimChar) ? this._elTextbox.value + sQuery : sQuery;
757
    this._sendQuery(newQuery);
758
};
759
 
760
/**
761
 * Snaps container to bottom-left corner of input element
762
 *
763
 * @method snapContainer
764
 */
765
YAHOO.widget.AutoComplete.prototype.snapContainer = function() {
766
    var oTextbox = this._elTextbox,
767
        pos = YAHOO.util.Dom.getXY(oTextbox);
768
    pos[1] += YAHOO.util.Dom.get(oTextbox).offsetHeight + 2;
769
    YAHOO.util.Dom.setXY(this._elContainer,pos);
770
};
771
 
772
/**
773
 * Expands container.
774
 *
775
 * @method expandContainer
776
 */
777
YAHOO.widget.AutoComplete.prototype.expandContainer = function() {
778
    this._toggleContainer(true);
779
};
780
 
781
/**
782
 * Collapses container.
783
 *
784
 * @method collapseContainer
785
 */
786
YAHOO.widget.AutoComplete.prototype.collapseContainer = function() {
787
    this._toggleContainer(false);
788
};
789
 
790
/**
791
 * Clears entire list of suggestions.
792
 *
793
 * @method clearList
794
 */
795
YAHOO.widget.AutoComplete.prototype.clearList = function() {
796
    var allItems = this._elList.childNodes,
797
        i=allItems.length-1;
798
    for(; i>-1; i--) {
799
          allItems[i].style.display = "none";
800
    }
801
};
802
 
803
/**
804
 * Handles subset matching for when queryMatchSubset is enabled.
805
 *
806
 * @method getSubsetMatches
807
 * @param sQuery {String} Query string.
808
 * @return {Object} oParsedResponse or null.
809
 */
810
YAHOO.widget.AutoComplete.prototype.getSubsetMatches = function(sQuery) {
811
    var subQuery, oCachedResponse, subRequest;
812
    // Loop through substrings of each cached element's query property...
813
    for(var i = sQuery.length; i >= this.minQueryLength ; i--) {
814
        subRequest = this.generateRequest(sQuery.substr(0,i));
815
        this.dataRequestEvent.fire(this, subQuery, subRequest);
816
        YAHOO.log("Searching for query subset \"" + subQuery + "\" in cache", "info", this.toString());
817
 
818
        // If a substring of the query is found in the cache
819
        oCachedResponse = this.dataSource.getCachedResponse(subRequest);
820
        if(oCachedResponse) {
821
            YAHOO.log("Found match for query subset \"" + subQuery + "\": " + YAHOO.lang.dump(oCachedResponse), "info", this.toString());
822
            return this.filterResults.apply(this.dataSource, [sQuery, oCachedResponse, oCachedResponse, {scope:this}]);
823
        }
824
    }
825
    YAHOO.log("Did not find subset match for query subset \"" + sQuery + "\"" , "info", this.toString());
826
    return null;
827
};
828
 
829
/**
830
 * Executed by DataSource (within DataSource scope via doBeforeParseData()) to
831
 * handle responseStripAfter cleanup.
832
 *
833
 * @method preparseRawResponse
834
 * @param sQuery {String} Query string.
835
 * @return {Object} oParsedResponse or null.
836
 */
837
YAHOO.widget.AutoComplete.prototype.preparseRawResponse = function(oRequest, oFullResponse, oCallback) {
838
    var nEnd = ((this.responseStripAfter !== "") && (oFullResponse.indexOf)) ?
839
        oFullResponse.indexOf(this.responseStripAfter) : -1;
840
    if(nEnd != -1) {
841
        oFullResponse = oFullResponse.substring(0,nEnd);
842
    }
843
    return oFullResponse;
844
};
845
 
846
/**
847
 * Executed by DataSource (within DataSource scope via doBeforeCallback()) to
848
 * filter results through a simple client-side matching algorithm.
849
 *
850
 * @method filterResults
851
 * @param sQuery {String} Original request.
852
 * @param oFullResponse {Object} Full response object.
853
 * @param oParsedResponse {Object} Parsed response object.
854
 * @param oCallback {Object} Callback object.
855
 * @return {Object} Filtered response object.
856
 */
857
 
858
YAHOO.widget.AutoComplete.prototype.filterResults = function(sQuery, oFullResponse, oParsedResponse, oCallback) {
859
    // If AC has passed a query string value back to itself, grab it
860
    if(oCallback && oCallback.argument && YAHOO.lang.isValue(oCallback.argument.query)) {
861
        sQuery = oCallback.argument.query;
862
    }
863
 
864
    // Only if a query string is available to match against
865
    if(sQuery && sQuery !== "") {
866
        // First make a copy of the oParseResponse
867
        oParsedResponse = YAHOO.widget.AutoComplete._cloneObject(oParsedResponse);
868
 
869
        var oAC = oCallback.scope,
870
            oDS = this,
871
            allResults = oParsedResponse.results, // the array of results
872
            filteredResults = [], // container for filtered results,
873
            nMax = oAC.maxResultsDisplayed, // max to find
874
            bMatchCase = (oDS.queryMatchCase || oAC.queryMatchCase), // backward compat
875
            bMatchContains = (oDS.queryMatchContains || oAC.queryMatchContains); // backward compat
876
 
877
        // Loop through each result object...
878
        for(var i=0, len=allResults.length; i<len; i++) {
879
            var oResult = allResults[i];
880
 
881
            // Grab the data to match against from the result object...
882
            var sResult = null;
883
 
884
            // Result object is a simple string already
885
            if(YAHOO.lang.isString(oResult)) {
886
                sResult = oResult;
887
            }
888
            // Result object is an array of strings
889
            else if(YAHOO.lang.isArray(oResult)) {
890
                sResult = oResult[0];
891
 
892
            }
893
            // Result object is an object literal of strings
894
            else if(this.responseSchema.fields) {
895
                var key = this.responseSchema.fields[0].key || this.responseSchema.fields[0];
896
                sResult = oResult[key];
897
            }
898
            // Backwards compatibility
899
            else if(this.key) {
900
                sResult = oResult[this.key];
901
            }
902
 
903
            if(YAHOO.lang.isString(sResult)) {
904
 
905
                var sKeyIndex = (bMatchCase) ?
906
                sResult.indexOf(decodeURIComponent(sQuery)) :
907
                sResult.toLowerCase().indexOf(decodeURIComponent(sQuery).toLowerCase());
908
 
909
                // A STARTSWITH match is when the query is found at the beginning of the key string...
910
                if((!bMatchContains && (sKeyIndex === 0)) ||
911
                // A CONTAINS match is when the query is found anywhere within the key string...
912
                (bMatchContains && (sKeyIndex > -1))) {
913
                    // Stash the match
914
                    filteredResults.push(oResult);
915
                }
916
            }
917
 
918
            // Filter no more if maxResultsDisplayed is reached
919
            if(len>nMax && filteredResults.length===nMax) {
920
                break;
921
            }
922
        }
923
        oParsedResponse.results = filteredResults;
924
        YAHOO.log("Filtered " + filteredResults.length + " results against query \""  + sQuery + "\": " + YAHOO.lang.dump(filteredResults), "info", this.toString());
925
    }
926
    else {
927
        YAHOO.log("Did not filter results against query", "info", this.toString());
928
    }
929
 
930
    return oParsedResponse;
931
};
932
 
933
/**
934
 * Handles response for display. This is the callback function method passed to
935
 * YAHOO.util.DataSourceBase#sendRequest so results from the DataSource are
936
 * returned to the AutoComplete instance.
937
 *
938
 * @method handleResponse
939
 * @param sQuery {String} Original request.
940
 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
941
 * @param oPayload {MIXED} (optional) Additional argument(s)
942
 */
943
YAHOO.widget.AutoComplete.prototype.handleResponse = function(sQuery, oResponse, oPayload) {
944
    if((this instanceof YAHOO.widget.AutoComplete) && this._sName) {
945
        this._populateList(sQuery, oResponse, oPayload);
946
    }
947
};
948
 
949
/**
950
 * Overridable method called before container is loaded with result data.
951
 *
952
 * @method doBeforeLoadData
953
 * @param sQuery {String} Original request.
954
 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
955
 * @param oPayload {MIXED} (optional) Additional argument(s)
956
 * @return {Boolean} Return true to continue loading data, false to cancel.
957
 */
958
YAHOO.widget.AutoComplete.prototype.doBeforeLoadData = function(sQuery, oResponse, oPayload) {
959
    return true;
960
};
961
 
962
/**
963
 * Overridable method that returns HTML markup for one result to be populated
964
 * as innerHTML of an &lt;LI&gt; element.
965
 *
966
 * @method formatResult
967
 * @param oResultData {Object} Result data object.
968
 * @param sQuery {String} The corresponding query string.
969
 * @param sResultMatch {HTMLElement} The current query string.
970
 * @return {HTML} HTML markup of formatted result data.
971
 */
972
YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultData, sQuery, sResultMatch) {
973
    var sMarkup = (sResultMatch) ? sResultMatch : "";
974
    return sMarkup;
975
};
976
 
977
/**
978
 * An alternative to the formatResult() method, escapes the result data before
979
 * inserting into DOM. Implementers should point to this method when accessing
980
 * data from third-party sources, from user input, or from otherwise
981
 * untrustworthy sources:
982
 * myAutoComplete.formatResult = myAutoComplete.formatEscapedResult;
983
 *
984
 * @method formatEscapedResult
985
 * @param oResultData {Object} Result data object.
986
 * @param sQuery {String} The corresponding query string.
987
 * @param sResultMatch {HTMLElement} The current query string.
988
 * @return {String} Formatted result data.
989
 */
990
YAHOO.widget.AutoComplete.prototype.formatEscapedResult = function(oResultData, sQuery, sResultMatch) {
991
    var sResult = (sResultMatch) ? sResultMatch : "";
992
    return YAHOO.lang.escapeHTML(sResult);
993
};
994
 
995
/**
996
 * Overridable method called before container expands allows implementers to access data
997
 * and DOM elements.
998
 *
999
 * @method doBeforeExpandContainer
1000
 * @param elTextbox {HTMLElement} The text input box.
1001
 * @param elContainer {HTMLElement} The container element.
1002
 * @param sQuery {String} The query string.
1003
 * @param aResults {Object[]}  An array of query results.
1004
 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
1005
 */
1006
YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
1007
    return true;
1008
};
1009
 
1010
 
1011
/**
1012
 * Nulls out the entire AutoComplete instance and related objects, removes attached
1013
 * event listeners, and clears out DOM elements inside the container. After
1014
 * calling this method, the instance reference should be expliclitly nulled by
1015
 * implementer, as in myAutoComplete = null. Use with caution!
1016
 *
1017
 * @method destroy
1018
 */
1019
YAHOO.widget.AutoComplete.prototype.destroy = function() {
1020
    var instanceName = this.toString();
1021
    var elInput = this._elTextbox;
1022
    var elContainer = this._elContainer;
1023
 
1024
    // Unhook custom events
1025
    this.textboxFocusEvent.unsubscribeAll();
1026
    this.textboxKeyEvent.unsubscribeAll();
1027
    this.dataRequestEvent.unsubscribeAll();
1028
    this.dataReturnEvent.unsubscribeAll();
1029
    this.dataErrorEvent.unsubscribeAll();
1030
    this.containerPopulateEvent.unsubscribeAll();
1031
    this.containerExpandEvent.unsubscribeAll();
1032
    this.typeAheadEvent.unsubscribeAll();
1033
    this.itemMouseOverEvent.unsubscribeAll();
1034
    this.itemMouseOutEvent.unsubscribeAll();
1035
    this.itemArrowToEvent.unsubscribeAll();
1036
    this.itemArrowFromEvent.unsubscribeAll();
1037
    this.itemSelectEvent.unsubscribeAll();
1038
    this.unmatchedItemSelectEvent.unsubscribeAll();
1039
    this.selectionEnforceEvent.unsubscribeAll();
1040
    this.containerCollapseEvent.unsubscribeAll();
1041
    this.textboxBlurEvent.unsubscribeAll();
1042
    this.textboxChangeEvent.unsubscribeAll();
1043
 
1044
    // Unhook DOM events
1045
    YAHOO.util.Event.purgeElement(elInput, true);
1046
    YAHOO.util.Event.purgeElement(elContainer, true);
1047
 
1048
    // Remove DOM elements
1049
    elContainer.innerHTML = "";
1050
 
1051
    // Null out objects
1052
    for(var key in this) {
1053
        if(YAHOO.lang.hasOwnProperty(this, key)) {
1054
            this[key] = null;
1055
        }
1056
    }
1057
 
1058
    YAHOO.log("AutoComplete instance destroyed: " + instanceName);
1059
};
1060
 
1061
/////////////////////////////////////////////////////////////////////////////
1062
//
1063
// Public events
1064
//
1065
/////////////////////////////////////////////////////////////////////////////
1066
 
1067
/**
1068
 * Fired when the input field receives focus.
1069
 *
1070
 * @event textboxFocusEvent
1071
 * @param type {String} Name of the event.
1072
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1073
 */
1074
YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
1075
 
1076
/**
1077
 * Fired when the input field receives key input.
1078
 *
1079
 * @event textboxKeyEvent
1080
 * @param type {String} Name of the event.
1081
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1082
 * @param args[1] {Number} The keycode number.
1083
 */
1084
YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
1085
 
1086
/**
1087
 * Fired when the AutoComplete instance makes a request to the DataSource.
1088
 *
1089
 * @event dataRequestEvent
1090
 * @param type {String} Name of the event.
1091
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1092
 * @param args[1] {String} The query string.
1093
 * @param args[2] {Object} The request.
1094
 */
1095
YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
1096
 
1097
/**
1098
 * Fired when the AutoComplete request to the DataSource is canceled.
1099
 *
1100
 * @event dataRequestCancelEvent
1101
 * @param type {String} Name of the event.
1102
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1103
 * @param args[1] {String} The query string.
1104
 */
1105
YAHOO.widget.AutoComplete.prototype.dataRequestCancelEvent = null;
1106
 
1107
/**
1108
 * Fired when the AutoComplete instance receives query results from the data
1109
 * source.
1110
 *
1111
 * @event dataReturnEvent
1112
 * @param type {String} Name of the event.
1113
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1114
 * @param args[1] {String} The query string.
1115
 * @param args[2] {Object[]} Results array.
1116
 */
1117
YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
1118
 
1119
/**
1120
 * Fired when the AutoComplete instance does not receive query results from the
1121
 * DataSource due to an error.
1122
 *
1123
 * @event dataErrorEvent
1124
 * @param type {String} Name of the event.
1125
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1126
 * @param args[1] {String} The query string.
1127
 * @param args[2] {Object} The response object, if available.
1128
 */
1129
YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
1130
 
1131
/**
1132
 * Fired when the results container is populated.
1133
 *
1134
 * @event containerPopulateEvent
1135
 * @param type {String} Name of the event.
1136
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1137
 */
1138
YAHOO.widget.AutoComplete.prototype.containerPopulateEvent = null;
1139
 
1140
/**
1141
 * Fired when the results container is expanded.
1142
 *
1143
 * @event containerExpandEvent
1144
 * @param type {String} Name of the event.
1145
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1146
 */
1147
YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
1148
 
1149
/**
1150
 * Fired when the input field has been prefilled by the type-ahead
1151
 * feature.
1152
 *
1153
 * @event typeAheadEvent
1154
 * @param type {String} Name of the event.
1155
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1156
 * @param args[1] {String} The query string.
1157
 * @param args[2] {String} The prefill string.
1158
 */
1159
YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
1160
 
1161
/**
1162
 * Fired when result item has been moused over.
1163
 *
1164
 * @event itemMouseOverEvent
1165
 * @param type {String} Name of the event.
1166
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1167
 * @param args[1] {HTMLElement} The &lt;li&gt element item moused to.
1168
 */
1169
YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
1170
 
1171
/**
1172
 * Fired when result item has been moused out.
1173
 *
1174
 * @event itemMouseOutEvent
1175
 * @param type {String} Name of the event.
1176
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1177
 * @param args[1] {HTMLElement} The &lt;li&gt; element item moused from.
1178
 */
1179
YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
1180
 
1181
/**
1182
 * Fired when result item has been arrowed to.
1183
 *
1184
 * @event itemArrowToEvent
1185
 * @param type {String} Name of the event.
1186
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1187
 * @param args[1] {HTMLElement} The &lt;li&gt; element item arrowed to.
1188
 */
1189
YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
1190
 
1191
/**
1192
 * Fired when result item has been arrowed away from.
1193
 *
1194
 * @event itemArrowFromEvent
1195
 * @param type {String} Name of the event.
1196
 * @param args[0[ {YAHOO.widget.AutoComplete} The AutoComplete instance.
1197
 * @param args[1] {HTMLElement} The &lt;li&gt; element item arrowed from.
1198
 */
1199
YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
1200
 
1201
/**
1202
 * Fired when an item is selected via mouse click, ENTER key, or TAB key.
1203
 *
1204
 * @event itemSelectEvent
1205
 * @param type {String} Name of the event.
1206
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1207
 * @param args[1] {HTMLElement} The selected &lt;li&gt; element item.
1208
 * @param args[2] {Object} The data returned for the item, either as an object,
1209
 * or mapped from the schema into an array.
1210
 */
1211
YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
1212
 
1213
/**
1214
 * Fired when a user selection does not match any of the displayed result items.
1215
 *
1216
 * @event unmatchedItemSelectEvent
1217
 * @param type {String} Name of the event.
1218
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1219
 * @param args[1] {String} The selected string.
1220
 */
1221
YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
1222
 
1223
/**
1224
 * Fired if forceSelection is enabled and the user's input has been cleared
1225
 * because it did not match one of the returned query results.
1226
 *
1227
 * @event selectionEnforceEvent
1228
 * @param type {String} Name of the event.
1229
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1230
 * @param args[1] {String} The cleared value (including delimiters if applicable).
1231
 */
1232
YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
1233
 
1234
/**
1235
 * Fired when the results container is collapsed.
1236
 *
1237
 * @event containerCollapseEvent
1238
 * @param type {String} Name of the event.
1239
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1240
 */
1241
YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
1242
 
1243
/**
1244
 * Fired when the input field loses focus.
1245
 *
1246
 * @event textboxBlurEvent
1247
 * @param type {String} Name of the event.
1248
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1249
 */
1250
YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
1251
 
1252
/**
1253
 * Fired when the input field value has changed when it loses focus.
1254
 *
1255
 * @event textboxChangeEvent
1256
 * @param type {String} Name of the event.
1257
 * @param args[0] {YAHOO.widget.AutoComplete} The AutoComplete instance.
1258
 */
1259
YAHOO.widget.AutoComplete.prototype.textboxChangeEvent = null;
1260
 
1261
/////////////////////////////////////////////////////////////////////////////
1262
//
1263
// Private member variables
1264
//
1265
/////////////////////////////////////////////////////////////////////////////
1266
 
1267
/**
1268
 * Internal class variable to index multiple AutoComplete instances.
1269
 *
1270
 * @property _nIndex
1271
 * @type Number
1272
 * @default 0
1273
 * @private
1274
 */
1275
YAHOO.widget.AutoComplete._nIndex = 0;
1276
 
1277
/**
1278
 * Name of AutoComplete instance.
1279
 *
1280
 * @property _sName
1281
 * @type String
1282
 * @private
1283
 */
1284
YAHOO.widget.AutoComplete.prototype._sName = null;
1285
 
1286
/**
1287
 * Text input field DOM element.
1288
 *
1289
 * @property _elTextbox
1290
 * @type HTMLElement
1291
 * @private
1292
 */
1293
YAHOO.widget.AutoComplete.prototype._elTextbox = null;
1294
 
1295
/**
1296
 * Container DOM element.
1297
 *
1298
 * @property _elContainer
1299
 * @type HTMLElement
1300
 * @private
1301
 */
1302
YAHOO.widget.AutoComplete.prototype._elContainer = null;
1303
 
1304
/**
1305
 * Reference to content element within container element.
1306
 *
1307
 * @property _elContent
1308
 * @type HTMLElement
1309
 * @private
1310
 */
1311
YAHOO.widget.AutoComplete.prototype._elContent = null;
1312
 
1313
/**
1314
 * Reference to header element within content element.
1315
 *
1316
 * @property _elHeader
1317
 * @type HTMLElement
1318
 * @private
1319
 */
1320
YAHOO.widget.AutoComplete.prototype._elHeader = null;
1321
 
1322
/**
1323
 * Reference to body element within content element.
1324
 *
1325
 * @property _elBody
1326
 * @type HTMLElement
1327
 * @private
1328
 */
1329
YAHOO.widget.AutoComplete.prototype._elBody = null;
1330
 
1331
/**
1332
 * Reference to footer element within content element.
1333
 *
1334
 * @property _elFooter
1335
 * @type HTMLElement
1336
 * @private
1337
 */
1338
YAHOO.widget.AutoComplete.prototype._elFooter = null;
1339
 
1340
/**
1341
 * Reference to shadow element within container element.
1342
 *
1343
 * @property _elShadow
1344
 * @type HTMLElement
1345
 * @private
1346
 */
1347
YAHOO.widget.AutoComplete.prototype._elShadow = null;
1348
 
1349
/**
1350
 * Reference to iframe element within container element.
1351
 *
1352
 * @property _elIFrame
1353
 * @type HTMLElement
1354
 * @private
1355
 */
1356
YAHOO.widget.AutoComplete.prototype._elIFrame = null;
1357
 
1358
/**
1359
 * Whether or not the widget instance is currently active. If query results come back
1360
 * but the user has already moved on, do not proceed with auto complete behavior.
1361
 *
1362
 * @property _bFocused
1363
 * @type Boolean
1364
 * @private
1365
 */
1366
YAHOO.widget.AutoComplete.prototype._bFocused = false;
1367
 
1368
/**
1369
 * Animation instance for container expand/collapse.
1370
 *
1371
 * @property _oAnim
1372
 * @type Boolean
1373
 * @private
1374
 */
1375
YAHOO.widget.AutoComplete.prototype._oAnim = null;
1376
 
1377
/**
1378
 * Whether or not the results container is currently open.
1379
 *
1380
 * @property _bContainerOpen
1381
 * @type Boolean
1382
 * @private
1383
 */
1384
YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
1385
 
1386
/**
1387
 * Whether or not the mouse is currently over the results
1388
 * container. This is necessary in order to prevent clicks on container items
1389
 * from being text input field blur events.
1390
 *
1391
 * @property _bOverContainer
1392
 * @type Boolean
1393
 * @private
1394
 */
1395
YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
1396
 
1397
/**
1398
 * Internal reference to &lt;ul&gt; elements that contains query results within the
1399
 * results container.
1400
 *
1401
 * @property _elList
1402
 * @type HTMLElement
1403
 * @private
1404
 */
1405
YAHOO.widget.AutoComplete.prototype._elList = null;
1406
 
1407
/*
1408
 * Array of &lt;li&gt; elements references that contain query results within the
1409
 * results container.
1410
 *
1411
 * @property _aListItemEls
1412
 * @type HTMLElement[]
1413
 * @private
1414
 */
1415
//YAHOO.widget.AutoComplete.prototype._aListItemEls = null;
1416
 
1417
/**
1418
 * Number of &lt;li&gt; elements currently displayed in results container.
1419
 *
1420
 * @property _nDisplayedItems
1421
 * @type Number
1422
 * @private
1423
 */
1424
YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
1425
 
1426
/*
1427
 * Internal count of &lt;li&gt; elements displayed and hidden in results container.
1428
 *
1429
 * @property _maxResultsDisplayed
1430
 * @type Number
1431
 * @private
1432
 */
1433
//YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
1434
 
1435
/**
1436
 * Current query string
1437
 *
1438
 * @property _sCurQuery
1439
 * @type String
1440
 * @private
1441
 */
1442
YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
1443
 
1444
/**
1445
 * Selections from previous queries (for saving delimited queries).
1446
 *
1447
 * @property _sPastSelections
1448
 * @type String
1449
 * @default ""
1450
 * @private
1451
 */
1452
YAHOO.widget.AutoComplete.prototype._sPastSelections = "";
1453
 
1454
/**
1455
 * Stores initial input value used to determine if textboxChangeEvent should be fired.
1456
 *
1457
 * @property _sInitInputValue
1458
 * @type String
1459
 * @private
1460
 */
1461
YAHOO.widget.AutoComplete.prototype._sInitInputValue = null;
1462
 
1463
/**
1464
 * Pointer to the currently highlighted &lt;li&gt; element in the container.
1465
 *
1466
 * @property _elCurListItem
1467
 * @type HTMLElement
1468
 * @private
1469
 */
1470
YAHOO.widget.AutoComplete.prototype._elCurListItem = null;
1471
 
1472
/**
1473
 * Pointer to the currently pre-highlighted &lt;li&gt; element in the container.
1474
 *
1475
 * @property _elCurPrehighlightItem
1476
 * @type HTMLElement
1477
 * @private
1478
 */
1479
YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem = null;
1480
 
1481
/**
1482
 * Whether or not an item has been selected since the container was populated
1483
 * with results. Reset to false by _populateList, and set to true when item is
1484
 * selected.
1485
 *
1486
 * @property _bItemSelected
1487
 * @type Boolean
1488
 * @private
1489
 */
1490
YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
1491
 
1492
/**
1493
 * Key code of the last key pressed in textbox.
1494
 *
1495
 * @property _nKeyCode
1496
 * @type Number
1497
 * @private
1498
 */
1499
YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
1500
 
1501
/**
1502
 * Delay timeout ID.
1503
 *
1504
 * @property _nDelayID
1505
 * @type Number
1506
 * @private
1507
 */
1508
YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
1509
 
1510
/**
1511
 * TypeAhead delay timeout ID.
1512
 *
1513
 * @property _nTypeAheadDelayID
1514
 * @type Number
1515
 * @private
1516
 */
1517
YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID = -1;
1518
 
1519
/**
1520
 * Src to iFrame used when useIFrame = true. Supports implementations over SSL
1521
 * as well.
1522
 *
1523
 * @property _iFrameSrc
1524
 * @type String
1525
 * @private
1526
 */
1527
YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
1528
 
1529
/**
1530
 * For users typing via certain IMEs, queries must be triggered by intervals,
1531
 * since key events yet supported across all browsers for all IMEs.
1532
 *
1533
 * @property _queryInterval
1534
 * @type Object
1535
 * @private
1536
 */
1537
YAHOO.widget.AutoComplete.prototype._queryInterval = null;
1538
 
1539
/**
1540
 * Internal tracker to last known textbox value, used to determine whether or not
1541
 * to trigger a query via interval for certain IME users.
1542
 *
1543
 * @event _sLastTextboxValue
1544
 * @type String
1545
 * @private
1546
 */
1547
YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
1548
 
1549
/////////////////////////////////////////////////////////////////////////////
1550
//
1551
// Private methods
1552
//
1553
/////////////////////////////////////////////////////////////////////////////
1554
 
1555
/**
1556
 * Updates and validates latest public config properties.
1557
 *
1558
 * @method __initProps
1559
 * @private
1560
 */
1561
YAHOO.widget.AutoComplete.prototype._initProps = function() {
1562
    // Correct any invalid values
1563
    var minQueryLength = this.minQueryLength;
1564
    if(!YAHOO.lang.isNumber(minQueryLength)) {
1565
        this.minQueryLength = 1;
1566
    }
1567
    var maxResultsDisplayed = this.maxResultsDisplayed;
1568
    if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
1569
        this.maxResultsDisplayed = 10;
1570
    }
1571
    var queryDelay = this.queryDelay;
1572
    if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
1573
        this.queryDelay = 0.2;
1574
    }
1575
    var typeAheadDelay = this.typeAheadDelay;
1576
    if(!YAHOO.lang.isNumber(typeAheadDelay) || (typeAheadDelay < 0)) {
1577
        this.typeAheadDelay = 0.2;
1578
    }
1579
    var delimChar = this.delimChar;
1580
    if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
1581
        this.delimChar = [delimChar];
1582
    }
1583
    else if(!YAHOO.lang.isArray(delimChar)) {
1584
        this.delimChar = null;
1585
    }
1586
    var animSpeed = this.animSpeed;
1587
    if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
1588
        if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
1589
            this.animSpeed = 0.3;
1590
        }
1591
        if(!this._oAnim ) {
1592
            this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
1593
        }
1594
        else {
1595
            this._oAnim.duration = this.animSpeed;
1596
        }
1597
    }
1598
    if(this.forceSelection && delimChar) {
1599
        YAHOO.log("The forceSelection feature has been enabled with delimChar defined.","warn", this.toString());
1600
    }
1601
};
1602
 
1603
/**
1604
 * Initializes the results container helpers if they are enabled and do
1605
 * not exist
1606
 *
1607
 * @method _initContainerHelperEls
1608
 * @private
1609
 */
1610
YAHOO.widget.AutoComplete.prototype._initContainerHelperEls = function() {
1611
    if(this.useShadow && !this._elShadow) {
1612
        var elShadow = document.createElement("div");
1613
        elShadow.className = "yui-ac-shadow";
1614
        elShadow.style.width = 0;
1615
        elShadow.style.height = 0;
1616
        this._elShadow = this._elContainer.appendChild(elShadow);
1617
    }
1618
    if(this.useIFrame && !this._elIFrame) {
1619
        var elIFrame = document.createElement("iframe");
1620
        elIFrame.src = this._iFrameSrc;
1621
        elIFrame.frameBorder = 0;
1622
        elIFrame.scrolling = "no";
1623
        elIFrame.style.position = "absolute";
1624
        elIFrame.style.width = 0;
1625
        elIFrame.style.height = 0;
1626
        elIFrame.style.padding = 0;
1627
        elIFrame.tabIndex = -1;
1628
        elIFrame.role = "presentation";
1629
        elIFrame.title = "Presentational iframe shim";
1630
        this._elIFrame = this._elContainer.appendChild(elIFrame);
1631
    }
1632
};
1633
 
1634
/**
1635
 * Initializes the results container once at object creation
1636
 *
1637
 * @method _initContainerEl
1638
 * @private
1639
 */
1640
YAHOO.widget.AutoComplete.prototype._initContainerEl = function() {
1641
    YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
1642
 
1643
    if(!this._elContent) {
1644
        // The elContent div is assigned DOM listeners and
1645
        // helps size the iframe and shadow properly
1646
        var elContent = document.createElement("div");
1647
        elContent.className = "yui-ac-content";
1648
        elContent.style.display = "none";
1649
 
1650
        this._elContent = this._elContainer.appendChild(elContent);
1651
 
1652
        var elHeader = document.createElement("div");
1653
        elHeader.className = "yui-ac-hd";
1654
        elHeader.style.display = "none";
1655
        this._elHeader = this._elContent.appendChild(elHeader);
1656
 
1657
        var elBody = document.createElement("div");
1658
        elBody.className = "yui-ac-bd";
1659
        this._elBody = this._elContent.appendChild(elBody);
1660
 
1661
        var elFooter = document.createElement("div");
1662
        elFooter.className = "yui-ac-ft";
1663
        elFooter.style.display = "none";
1664
        this._elFooter = this._elContent.appendChild(elFooter);
1665
    }
1666
    else {
1667
        YAHOO.log("Could not initialize the container","warn",this.toString());
1668
    }
1669
};
1670
 
1671
/**
1672
 * Clears out contents of container body and creates up to
1673
 * YAHOO.widget.AutoComplete#maxResultsDisplayed &lt;li&gt; elements in an
1674
 * &lt;ul&gt; element.
1675
 *
1676
 * @method _initListEl
1677
 * @private
1678
 */
1679
YAHOO.widget.AutoComplete.prototype._initListEl = function() {
1680
    var nListLength = this.maxResultsDisplayed,
1681
        elList = this._elList || document.createElement("ul"),
1682
        elListItem;
1683
 
1684
    while(elList.childNodes.length < nListLength) {
1685
        elListItem = document.createElement("li");
1686
        elListItem.style.display = "none";
1687
        elListItem._nItemIndex = elList.childNodes.length;
1688
        elList.appendChild(elListItem);
1689
    }
1690
    if(!this._elList) {
1691
        var elBody = this._elBody;
1692
        YAHOO.util.Event.purgeElement(elBody, true);
1693
        elBody.innerHTML = "";
1694
        this._elList = elBody.appendChild(elList);
1695
    }
1696
 
1697
    this._elBody.style.display = "";
1698
};
1699
 
1700
/**
1701
 * Focuses input field.
1702
 *
1703
 * @method _focus
1704
 * @private
1705
 */
1706
YAHOO.widget.AutoComplete.prototype._focus = function() {
1707
    // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
1708
    var oSelf = this;
1709
    setTimeout(function() {
1710
        try {
1711
            oSelf._elTextbox.focus();
1712
        }
1713
        catch(e) {
1714
        }
1715
    },0);
1716
};
1717
 
1718
/**
1719
 * Enables interval detection for IME support.
1720
 *
1721
 * @method _enableIntervalDetection
1722
 * @private
1723
 */
1724
YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() {
1725
    var oSelf = this;
1726
    if(!oSelf._queryInterval && oSelf.queryInterval) {
1727
        oSelf._queryInterval = setInterval(function() { oSelf._onInterval(); }, oSelf.queryInterval);
1728
        YAHOO.log("Interval set", "info", this.toString());
1729
    }
1730
};
1731
 
1732
/**
1733
 * Enables interval detection for a less performant but brute force mechanism to
1734
 * detect input values at an interval set by queryInterval and send queries if
1735
 * input value has changed. Needed to support right-click+paste or shift+insert
1736
 * edge cases. Please note that intervals are cleared at the end of each interaction,
1737
 * so enableIntervalDetection must be called for each new interaction. The
1738
 * recommended approach is to call it in response to textboxFocusEvent.
1739
 *
1740
 * @method enableIntervalDetection
1741
 */
1742
YAHOO.widget.AutoComplete.prototype.enableIntervalDetection =
1743
    YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;
1744
 
1745
/**
1746
 * Enables query triggers based on text input detection by intervals (rather
1747
 * than by key events).
1748
 *
1749
 * @method _onInterval
1750
 * @private
1751
 */
1752
YAHOO.widget.AutoComplete.prototype._onInterval = function() {
1753
    var currValue = this._elTextbox.value;
1754
    var lastValue = this._sLastTextboxValue;
1755
    if(currValue != lastValue) {
1756
        this._sLastTextboxValue = currValue;
1757
        this._sendQuery(currValue);
1758
    }
1759
};
1760
 
1761
/**
1762
 * Cancels text input detection by intervals.
1763
 *
1764
 * @method _clearInterval
1765
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1766
 * @private
1767
 */
1768
YAHOO.widget.AutoComplete.prototype._clearInterval = function() {
1769
    if(this._queryInterval) {
1770
        clearInterval(this._queryInterval);
1771
        this._queryInterval = null;
1772
        YAHOO.log("Interval cleared", "info", this.toString());
1773
    }
1774
};
1775
 
1776
/**
1777
 * Whether or not key is functional or should be ignored. Note that the right
1778
 * arrow key is NOT an ignored key since it triggers queries for certain intl
1779
 * charsets.
1780
 *
1781
 * @method _isIgnoreKey
1782
 * @param nKeycode {Number} Code of key pressed.
1783
 * @return {Boolean} True if key should be ignored, false otherwise.
1784
 * @private
1785
 */
1786
YAHOO.widget.AutoComplete.prototype._isIgnoreKey = function(nKeyCode) {
1787
    if((nKeyCode == 9) || (nKeyCode == 13)  || // tab, enter
1788
            (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl
1789
            (nKeyCode >= 18 && nKeyCode <= 20) || // alt, pause/break,caps lock
1790
            (nKeyCode == 27) || // esc
1791
            (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
1792
            /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
1793
            (nKeyCode == 40) || // down*/
1794
            (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
1795
            (nKeyCode >= 44 && nKeyCode <= 45) || // print screen,insert
1796
            (nKeyCode == 229) // Bug 2041973: Korean XP fires 2 keyup events, the key and 229
1797
        ) {
1798
        return true;
1799
    }
1800
    return false;
1801
};
1802
 
1803
/**
1804
 * Makes query request to the DataSource.
1805
 *
1806
 * @method _sendQuery
1807
 * @param sQuery {String} Query string.
1808
 * @private
1809
 */
1810
YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
1811
    // Widget has been effectively turned off
1812
    if(this.minQueryLength < 0) {
1813
        this._toggleContainer(false);
1814
        YAHOO.log("Property minQueryLength is less than 0", "info", this.toString());
1815
        return;
1816
    }
1817
    // Delimiter has been enabled
1818
    if(this.delimChar) {
1819
        var extraction = this._extractQuery(sQuery);
1820
        // Here is the query itself
1821
        sQuery = extraction.query;
1822
        // ...and save the rest of the string for later
1823
        this._sPastSelections = extraction.previous;
1824
    }
1825
 
1826
    // Don't search queries that are too short
1827
    if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
1828
        if(this._nDelayID != -1) {
1829
            clearTimeout(this._nDelayID);
1830
        }
1831
        this._toggleContainer(false);
1832
        YAHOO.log("Query \"" + sQuery + "\" is too short", "info", this.toString());
1833
        return;
1834
    }
1835
 
1836
    sQuery = encodeURIComponent(sQuery);
1837
    this._nDelayID = -1;    // Reset timeout ID because request is being made
1838
 
1839
    // Subset matching
1840
    if(this.dataSource.queryMatchSubset || this.queryMatchSubset) { // backward compat
1841
        var oResponse = this.getSubsetMatches(sQuery);
1842
        if(oResponse) {
1843
            this.handleResponse(sQuery, oResponse, {query: sQuery});
1844
            return;
1845
        }
1846
    }
1847
 
1848
    if(this.dataSource.responseStripAfter) {
1849
        this.dataSource.doBeforeParseData = this.preparseRawResponse;
1850
    }
1851
    if(this.applyLocalFilter) {
1852
        this.dataSource.doBeforeCallback = this.filterResults;
1853
    }
1854
 
1855
    var sRequest = this.generateRequest(sQuery);
1856
 
1857
    if(sRequest !== undefined) {
1858
        this.dataRequestEvent.fire(this, sQuery, sRequest);
1859
        YAHOO.log("Sending query \"" + sRequest + "\"", "info", this.toString());
1860
 
1861
        this.dataSource.sendRequest(sRequest, {
1862
                success : this.handleResponse,
1863
                failure : this.handleResponse,
1864
                scope   : this,
1865
                argument: {
1866
                    query: sQuery
1867
                }
1868
        });
1869
    }
1870
    else {
1871
        this.dataRequestCancelEvent.fire(this, sQuery);
1872
        YAHOO.log("Canceled query \"" + sQuery + "\"", "info", this.toString());
1873
    }
1874
};
1875
 
1876
/**
1877
 * Populates the given &lt;li&gt; element with return value from formatResult().
1878
 *
1879
 * @method _populateListItem
1880
 * @param elListItem {HTMLElement} The LI element.
1881
 * @param oResult {Object} The result object.
1882
 * @param sCurQuery {String} The query string.
1883
 * @private
1884
 */
1885
YAHOO.widget.AutoComplete.prototype._populateListItem = function(elListItem, oResult, sQuery) {
1886
    elListItem.innerHTML = this.formatResult(oResult, sQuery, elListItem._sResultMatch);
1887
};
1888
 
1889
/**
1890
 * Populates the array of &lt;li&gt; elements in the container with query
1891
 * results.
1892
 *
1893
 * @method _populateList
1894
 * @param sQuery {String} Original request.
1895
 * @param oResponse {Object} <a href="http://developer.yahoo.com/yui/datasource/#ds_oParsedResponse">Response object</a>.
1896
 * @param oPayload {MIXED} (optional) Additional argument(s)
1897
 * @private
1898
 */
1899
YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, oResponse, oPayload) {
1900
    // Clear previous timeout
1901
    if(this._nTypeAheadDelayID != -1) {
1902
        clearTimeout(this._nTypeAheadDelayID);
1903
    }
1904
 
1905
    sQuery = (oPayload && oPayload.query) ? oPayload.query : sQuery;
1906
 
1907
    // Pass data through abstract method for any transformations
1908
    var ok = this.doBeforeLoadData(sQuery, oResponse, oPayload);
1909
 
1910
    // Data is ok
1911
    if(ok && !oResponse.error) {
1912
        this.dataReturnEvent.fire(this, sQuery, oResponse.results);
1913
 
1914
        // Continue only if instance is still active (i.e., user hasn't already moved on)
1915
        if(this._bFocused) {
1916
            // Store state for this interaction
1917
            var sCurQuery = decodeURIComponent(sQuery);
1918
            this._sCurQuery = sCurQuery;
1919
            this._bItemSelected = false;
1920
 
1921
            var allResults = oResponse.results,
1922
                nItemsToShow = Math.min(allResults.length,this.maxResultsDisplayed),
1923
                sMatchKey = (this.dataSource.responseSchema.fields) ?
1924
                    (this.dataSource.responseSchema.fields[0].key || this.dataSource.responseSchema.fields[0]) : 0;
1925
 
1926
            if(nItemsToShow > 0) {
1927
                // Make sure container and helpers are ready to go
1928
                if(!this._elList || (this._elList.childNodes.length < nItemsToShow)) {
1929
                    this._initListEl();
1930
                }
1931
                this._initContainerHelperEls();
1932
 
1933
                var allListItemEls = this._elList.childNodes;
1934
                // Fill items with data from the bottom up
1935
                for(var i = nItemsToShow-1; i >= 0; i--) {
1936
                    var elListItem = allListItemEls[i],
1937
                    oResult = allResults[i];
1938
 
1939
                    // Backward compatibility
1940
                    if(this.resultTypeList) {
1941
                        // Results need to be converted back to an array
1942
                        var aResult = [];
1943
                        // Match key is first
1944
                        aResult[0] = (YAHOO.lang.isString(oResult)) ? oResult : oResult[sMatchKey] || oResult[this.key];
1945
                        // Add additional data to the result array
1946
                        var fields = this.dataSource.responseSchema.fields;
1947
                        if(YAHOO.lang.isArray(fields) && (fields.length > 1)) {
1948
                            for(var k=1, len=fields.length; k<len; k++) {
1949
                                aResult[aResult.length] = oResult[fields[k].key || fields[k]];
1950
                            }
1951
                        }
1952
                        // No specific fields defined, so pass along entire data object
1953
                        else {
1954
                            // Already an array
1955
                            if(YAHOO.lang.isArray(oResult)) {
1956
                                aResult = oResult;
1957
                            }
1958
                            // Simple string
1959
                            else if(YAHOO.lang.isString(oResult)) {
1960
                                aResult = [oResult];
1961
                            }
1962
                            // Object
1963
                            else {
1964
                                aResult[1] = oResult;
1965
                            }
1966
                        }
1967
                        oResult = aResult;
1968
                    }
1969
 
1970
                    // The matching value, including backward compatibility for array format and safety net
1971
                    elListItem._sResultMatch = (YAHOO.lang.isString(oResult)) ? oResult : (YAHOO.lang.isArray(oResult)) ? oResult[0] : (oResult[sMatchKey] || "");
1972
                    elListItem._oResultData = oResult; // Additional data
1973
                    this._populateListItem(elListItem, oResult, sCurQuery);
1974
                    elListItem.style.display = "";
1975
                }
1976
 
1977
                // Clear out extraneous items
1978
                if(nItemsToShow < allListItemEls.length) {
1979
                    var extraListItem;
1980
                    for(var j = allListItemEls.length-1; j >= nItemsToShow; j--) {
1981
                        extraListItem = allListItemEls[j];
1982
                        extraListItem.style.display = "none";
1983
                    }
1984
                }
1985
 
1986
                this._nDisplayedItems = nItemsToShow;
1987
 
1988
                this.containerPopulateEvent.fire(this, sQuery, allResults);
1989
 
1990
                // Highlight the first item
1991
                if(this.autoHighlight) {
1992
                    var elFirstListItem = this._elList.firstChild;
1993
                    this._toggleHighlight(elFirstListItem,"to");
1994
                    this.itemArrowToEvent.fire(this, elFirstListItem);
1995
                    YAHOO.log("Arrowed to first item", "info", this.toString());
1996
                    this._typeAhead(elFirstListItem,sQuery);
1997
                }
1998
                // Unhighlight any previous time
1999
                else {
2000
                    this._toggleHighlight(this._elCurListItem,"from");
2001
                }
2002
 
2003
                // Pre-expansion stuff
2004
                ok = this._doBeforeExpandContainer(this._elTextbox, this._elContainer, sQuery, allResults);
2005
 
2006
                // Expand the container
2007
                this._toggleContainer(ok);
2008
            }
2009
            else {
2010
                this._toggleContainer(false);
2011
            }
2012
 
2013
            YAHOO.log("Container populated with " + nItemsToShow +  " list items", "info", this.toString());
2014
            return;
2015
        }
2016
    }
2017
    // Error
2018
    else {
2019
        this.dataErrorEvent.fire(this, sQuery, oResponse);
2020
    }
2021
 
2022
    YAHOO.log("Could not populate list", "info", this.toString());
2023
};
2024
 
2025
/**
2026
 * Called before container expands, by default snaps container to the
2027
 * bottom-left corner of the input element, then calls public overrideable method.
2028
 *
2029
 * @method _doBeforeExpandContainer
2030
 * @param elTextbox {HTMLElement} The text input box.
2031
 * @param elContainer {HTMLElement} The container element.
2032
 * @param sQuery {String} The query string.
2033
 * @param aResults {Object[]}  An array of query results.
2034
 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
2035
 * @private
2036
 */
2037
YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
2038
    if(this.autoSnapContainer) {
2039
        this.snapContainer();
2040
    }
2041
 
2042
    return this.doBeforeExpandContainer(elTextbox, elContainer, sQuery, aResults);
2043
};
2044
 
2045
/**
2046
 * When forceSelection is true and the user attempts
2047
 * leave the text input box without selecting an item from the query results,
2048
 * the user selection is cleared.
2049
 *
2050
 * @method _clearSelection
2051
 * @private
2052
 */
2053
YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
2054
    var extraction = (this.delimChar) ? this._extractQuery(this._elTextbox.value) :
2055
            {previous:"",query:this._elTextbox.value};
2056
    this._elTextbox.value = extraction.previous;
2057
    this.selectionEnforceEvent.fire(this, extraction.query);
2058
    YAHOO.log("Selection enforced", "info", this.toString());
2059
};
2060
 
2061
/**
2062
 * Whether or not user-typed value in the text input box matches any of the
2063
 * query results.
2064
 *
2065
 * @method _textMatchesOption
2066
 * @return {HTMLElement} Matching list item element if user-input text matches
2067
 * a result, null otherwise.
2068
 * @private
2069
 */
2070
YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
2071
    var elMatch = null;
2072
 
2073
    for(var i=0; i<this._nDisplayedItems; i++) {
2074
        var elListItem = this._elList.childNodes[i];
2075
        var sMatch = ("" + elListItem._sResultMatch).toLowerCase();
2076
        if(sMatch == this._sCurQuery.toLowerCase()) {
2077
            elMatch = elListItem;
2078
            break;
2079
        }
2080
    }
2081
    return(elMatch);
2082
};
2083
 
2084
/**
2085
 * Updates in the text input box with the first query result as the user types,
2086
 * selecting the substring that the user has not typed.
2087
 *
2088
 * @method _typeAhead
2089
 * @param elListItem {HTMLElement} The &lt;li&gt; element item whose data populates the input field.
2090
 * @param sQuery {String} Query string.
2091
 * @private
2092
 */
2093
YAHOO.widget.AutoComplete.prototype._typeAhead = function(elListItem, sQuery) {
2094
    // Don't typeAhead if turned off or is backspace
2095
    if(!this.typeAhead || (this._nKeyCode == 8)) {
2096
        return;
2097
    }
2098
 
2099
    var oSelf = this,
2100
        elTextbox = this._elTextbox;
2101
 
2102
    // Only if text selection is supported
2103
    if(elTextbox.setSelectionRange || elTextbox.createTextRange) {
2104
        // Set and store timeout for this typeahead
2105
        this._nTypeAheadDelayID = setTimeout(function() {
2106
                // Select the portion of text that the user has not typed
2107
                var nStart = elTextbox.value.length; // any saved queries plus what user has typed
2108
                oSelf._updateValue(elListItem);
2109
                var nEnd = elTextbox.value.length;
2110
                oSelf._selectText(elTextbox,nStart,nEnd);
2111
                var sPrefill = elTextbox.value.substr(nStart,nEnd);
2112
                // Bug 2528552: Store as a selection
2113
                oSelf._sCurQuery = elListItem._sResultMatch;
2114
                oSelf.typeAheadEvent.fire(oSelf,sQuery,sPrefill);
2115
                YAHOO.log("Typeahead occured with prefill string \"" + sPrefill + "\"", "info", oSelf.toString());
2116
            },(this.typeAheadDelay*1000));
2117
    }
2118
};
2119
 
2120
/**
2121
 * Selects text in the input field.
2122
 *
2123
 * @method _selectText
2124
 * @param elTextbox {HTMLElement} Text input box element in which to select text.
2125
 * @param nStart {Number} Starting index of text string to select.
2126
 * @param nEnd {Number} Ending index of text selection.
2127
 * @private
2128
 */
2129
YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
2130
    if(elTextbox.setSelectionRange) { // For Mozilla
2131
        elTextbox.setSelectionRange(nStart,nEnd);
2132
    }
2133
    else if(elTextbox.createTextRange) { // For IE
2134
        var oTextRange = elTextbox.createTextRange();
2135
        oTextRange.moveStart("character", nStart);
2136
        oTextRange.moveEnd("character", nEnd-elTextbox.value.length);
2137
        oTextRange.select();
2138
    }
2139
    else {
2140
        elTextbox.select();
2141
    }
2142
};
2143
 
2144
/**
2145
 * Extracts rightmost query from delimited string.
2146
 *
2147
 * @method _extractQuery
2148
 * @param sQuery {String} String to parse
2149
 * @return {Object} Object literal containing properties "query" and "previous".
2150
 * @private
2151
 */
2152
YAHOO.widget.AutoComplete.prototype._extractQuery = function(sQuery) {
2153
    var aDelimChar = this.delimChar,
2154
        nDelimIndex = -1,
2155
        nNewIndex, nQueryStart,
2156
        i = aDelimChar.length-1,
2157
        sPrevious;
2158
 
2159
    // Loop through all possible delimiters and find the rightmost one in the query
2160
    // A " " may be a false positive if they are defined as delimiters AND
2161
    // are used to separate delimited queries
2162
    for(; i >= 0; i--) {
2163
        nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
2164
        if(nNewIndex > nDelimIndex) {
2165
            nDelimIndex = nNewIndex;
2166
        }
2167
    }
2168
    // If we think the last delimiter is a space (" "), make sure it is NOT
2169
    // a false positive by also checking the char directly before it
2170
    if(aDelimChar[i] == " ") {
2171
        for (var j = aDelimChar.length-1; j >= 0; j--) {
2172
            if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
2173
                nDelimIndex--;
2174
                break;
2175
            }
2176
        }
2177
    }
2178
    // A delimiter has been found in the query so extract the latest query from past selections
2179
    if(nDelimIndex > -1) {
2180
        nQueryStart = nDelimIndex + 1;
2181
        // Trim any white space from the beginning...
2182
        while(sQuery.charAt(nQueryStart) == " ") {
2183
            nQueryStart += 1;
2184
        }
2185
        // ...and save the rest of the string for later
2186
        sPrevious = sQuery.substring(0,nQueryStart);
2187
        // Here is the query itself
2188
        sQuery = sQuery.substr(nQueryStart);
2189
    }
2190
    // No delimiter found in the query, so there are no selections from past queries
2191
    else {
2192
        sPrevious = "";
2193
    }
2194
 
2195
    return {
2196
        previous: sPrevious,
2197
        query: sQuery
2198
    };
2199
};
2200
 
2201
/**
2202
 * Syncs results container with its helpers.
2203
 *
2204
 * @method _toggleContainerHelpers
2205
 * @param bShow {Boolean} True if container is expanded, false if collapsed
2206
 * @private
2207
 */
2208
YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
2209
    var width = this._elContent.offsetWidth + "px";
2210
    var height = this._elContent.offsetHeight + "px";
2211
 
2212
    if(this.useIFrame && this._elIFrame) {
2213
    var elIFrame = this._elIFrame;
2214
        if(bShow) {
2215
            elIFrame.style.width = width;
2216
            elIFrame.style.height = height;
2217
            elIFrame.style.padding = "";
2218
            YAHOO.log("Iframe expanded", "info", this.toString());
2219
        }
2220
        else {
2221
            elIFrame.style.width = 0;
2222
            elIFrame.style.height = 0;
2223
            elIFrame.style.padding = 0;
2224
            YAHOO.log("Iframe collapsed", "info", this.toString());
2225
        }
2226
    }
2227
    if(this.useShadow && this._elShadow) {
2228
    var elShadow = this._elShadow;
2229
        if(bShow) {
2230
            elShadow.style.width = width;
2231
            elShadow.style.height = height;
2232
            YAHOO.log("Shadow expanded", "info", this.toString());
2233
        }
2234
        else {
2235
            elShadow.style.width = 0;
2236
            elShadow.style.height = 0;
2237
            YAHOO.log("Shadow collapsed", "info", this.toString());
2238
        }
2239
    }
2240
};
2241
 
2242
/**
2243
 * Animates expansion or collapse of the container.
2244
 *
2245
 * @method _toggleContainer
2246
 * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
2247
 * @private
2248
 */
2249
YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
2250
    YAHOO.log("Toggling container " + ((bShow) ? "open" : "closed"), "info", this.toString());
2251
 
2252
    var elContainer = this._elContainer;
2253
 
2254
    // If implementer has container always open and it's already open, don't mess with it
2255
    // Container is initialized with display "none" so it may need to be shown first time through
2256
    if(this.alwaysShowContainer && this._bContainerOpen) {
2257
        return;
2258
    }
2259
 
2260
    // Reset states
2261
    if(!bShow) {
2262
        this._toggleHighlight(this._elCurListItem,"from");
2263
        this._nDisplayedItems = 0;
2264
        this._sCurQuery = null;
2265
 
2266
        // Container is already closed, so don't bother with changing the UI
2267
        if(this._elContent.style.display == "none") {
2268
            return;
2269
        }
2270
    }
2271
 
2272
    // If animation is enabled...
2273
    var oAnim = this._oAnim;
2274
    if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
2275
        if(oAnim.isAnimated()) {
2276
            oAnim.stop(true);
2277
        }
2278
 
2279
        // Clone container to grab current size offscreen
2280
        var oClone = this._elContent.cloneNode(true);
2281
        elContainer.appendChild(oClone);
2282
        oClone.style.top = "-9000px";
2283
        oClone.style.width = "";
2284
        oClone.style.height = "";
2285
        oClone.style.display = "";
2286
 
2287
        // Current size of the container is the EXPANDED size
2288
        var wExp = oClone.offsetWidth;
2289
        var hExp = oClone.offsetHeight;
2290
 
2291
        // Calculate COLLAPSED sizes based on horiz and vert anim
2292
        var wColl = (this.animHoriz) ? 0 : wExp;
2293
        var hColl = (this.animVert) ? 0 : hExp;
2294
 
2295
        // Set animation sizes
2296
        oAnim.attributes = (bShow) ?
2297
            {width: { to: wExp }, height: { to: hExp }} :
2298
            {width: { to: wColl}, height: { to: hColl }};
2299
 
2300
        // If opening anew, set to a collapsed size...
2301
        if(bShow && !this._bContainerOpen) {
2302
            this._elContent.style.width = wColl+"px";
2303
            this._elContent.style.height = hColl+"px";
2304
        }
2305
        // Else, set it to its last known size.
2306
        else {
2307
            this._elContent.style.width = wExp+"px";
2308
            this._elContent.style.height = hExp+"px";
2309
        }
2310
 
2311
        elContainer.removeChild(oClone);
2312
        oClone = null;
2313
 
2314
    	var oSelf = this;
2315
    	var onAnimComplete = function() {
2316
            // Finish the collapse
2317
    		oAnim.onComplete.unsubscribeAll();
2318
 
2319
            if(bShow) {
2320
                oSelf._toggleContainerHelpers(true);
2321
                oSelf._bContainerOpen = bShow;
2322
                oSelf.containerExpandEvent.fire(oSelf);
2323
                YAHOO.log("Container expanded", "info", oSelf.toString());
2324
            }
2325
            else {
2326
                oSelf._elContent.style.display = "none";
2327
                oSelf._bContainerOpen = bShow;
2328
                oSelf.containerCollapseEvent.fire(oSelf);
2329
                YAHOO.log("Container collapsed", "info", oSelf.toString());
2330
            }
2331
     	};
2332
 
2333
        // Display container and animate it
2334
        this._toggleContainerHelpers(false); // Bug 1424486: Be early to hide, late to show;
2335
        this._elContent.style.display = "";
2336
        oAnim.onComplete.subscribe(onAnimComplete);
2337
        oAnim.animate();
2338
    }
2339
    // Else don't animate, just show or hide
2340
    else {
2341
        if(bShow) {
2342
            this._elContent.style.display = "";
2343
            this._toggleContainerHelpers(true);
2344
            this._bContainerOpen = bShow;
2345
            this.containerExpandEvent.fire(this);
2346
            YAHOO.log("Container expanded", "info", this.toString());
2347
        }
2348
        else {
2349
            this._toggleContainerHelpers(false);
2350
            this._elContent.style.display = "none";
2351
            this._bContainerOpen = bShow;
2352
            this.containerCollapseEvent.fire(this);
2353
            YAHOO.log("Container collapsed", "info", this.toString());
2354
        }
2355
   }
2356
 
2357
};
2358
 
2359
/**
2360
 * Toggles the highlight on or off for an item in the container, and also cleans
2361
 * up highlighting of any previous item.
2362
 *
2363
 * @method _toggleHighlight
2364
 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2365
 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2366
 * @private
2367
 */
2368
YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(elNewListItem, sType) {
2369
    if(elNewListItem) {
2370
        var sHighlight = this.highlightClassName;
2371
        if(this._elCurListItem) {
2372
            // Remove highlight from old item
2373
            YAHOO.util.Dom.removeClass(this._elCurListItem, sHighlight);
2374
            this._elCurListItem = null;
2375
        }
2376
 
2377
        if((sType == "to") && sHighlight) {
2378
            // Apply highlight to new item
2379
            YAHOO.util.Dom.addClass(elNewListItem, sHighlight);
2380
            this._elCurListItem = elNewListItem;
2381
        }
2382
    }
2383
};
2384
 
2385
/**
2386
 * Toggles the pre-highlight on or off for an item in the container, and also cleans
2387
 * up pre-highlighting of any previous item.
2388
 *
2389
 * @method _togglePrehighlight
2390
 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2391
 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2392
 * @private
2393
 */
2394
YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(elNewListItem, sType) {
2395
    var sPrehighlight = this.prehighlightClassName;
2396
 
2397
    if(this._elCurPrehighlightItem) {
2398
        YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem, sPrehighlight);
2399
    }
2400
    if(elNewListItem == this._elCurListItem) {
2401
        return;
2402
    }
2403
 
2404
    if((sType == "mouseover") && sPrehighlight) {
2405
        // Apply prehighlight to new item
2406
        YAHOO.util.Dom.addClass(elNewListItem, sPrehighlight);
2407
        this._elCurPrehighlightItem = elNewListItem;
2408
    }
2409
    else {
2410
        // Remove prehighlight from old item
2411
        YAHOO.util.Dom.removeClass(elNewListItem, sPrehighlight);
2412
    }
2413
};
2414
 
2415
/**
2416
 * Updates the text input box value with selected query result. If a delimiter
2417
 * has been defined, then the value gets appended with the delimiter.
2418
 *
2419
 * @method _updateValue
2420
 * @param elListItem {HTMLElement} The &lt;li&gt; element item with which to update the value.
2421
 * @private
2422
 */
2423
YAHOO.widget.AutoComplete.prototype._updateValue = function(elListItem) {
2424
    if(!this.suppressInputUpdate) {
2425
        var elTextbox = this._elTextbox;
2426
        var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
2427
        var sResultMatch = elListItem._sResultMatch;
2428
 
2429
        // Calculate the new value
2430
        var sNewValue = "";
2431
        if(sDelimChar) {
2432
            // Preserve selections from past queries
2433
            sNewValue = this._sPastSelections;
2434
            // Add new selection plus delimiter
2435
            sNewValue += sResultMatch + sDelimChar;
2436
            if(sDelimChar != " ") {
2437
                sNewValue += " ";
2438
            }
2439
        }
2440
        else {
2441
            sNewValue = sResultMatch;
2442
        }
2443
 
2444
        // Update input field
2445
        elTextbox.value = sNewValue;
2446
 
2447
        // Scroll to bottom of textarea if necessary
2448
        if(elTextbox.type == "textarea") {
2449
            elTextbox.scrollTop = elTextbox.scrollHeight;
2450
        }
2451
 
2452
        // Move cursor to end
2453
        var end = elTextbox.value.length;
2454
        this._selectText(elTextbox,end,end);
2455
 
2456
        this._elCurListItem = elListItem;
2457
    }
2458
};
2459
 
2460
/**
2461
 * Selects a result item from the container
2462
 *
2463
 * @method _selectItem
2464
 * @param elListItem {HTMLElement} The selected &lt;li&gt; element item.
2465
 * @private
2466
 */
2467
YAHOO.widget.AutoComplete.prototype._selectItem = function(elListItem) {
2468
    this._bItemSelected = true;
2469
    this._updateValue(elListItem);
2470
    this._sPastSelections = this._elTextbox.value;
2471
    this._clearInterval();
2472
    this.itemSelectEvent.fire(this, elListItem, elListItem._oResultData);
2473
    YAHOO.log("Item selected: " + YAHOO.lang.dump(elListItem._oResultData), "info", this.toString());
2474
    this._toggleContainer(false);
2475
};
2476
 
2477
/**
2478
 * If an item is highlighted in the container, the right arrow key jumps to the
2479
 * end of the textbox and selects the highlighted item, otherwise the container
2480
 * is closed.
2481
 *
2482
 * @method _jumpSelection
2483
 * @private
2484
 */
2485
YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
2486
    if(this._elCurListItem) {
2487
        this._selectItem(this._elCurListItem);
2488
    }
2489
    else {
2490
        this._toggleContainer(false);
2491
    }
2492
};
2493
 
2494
/**
2495
 * Triggered by up and down arrow keys, changes the current highlighted
2496
 * &lt;li&gt; element item. Scrolls container if necessary.
2497
 *
2498
 * @method _moveSelection
2499
 * @param nKeyCode {Number} Code of key pressed.
2500
 * @private
2501
 */
2502
YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
2503
    if(this._bContainerOpen) {
2504
        // Determine current item's id number
2505
        var elCurListItem = this._elCurListItem,
2506
            nCurItemIndex = -1;
2507
 
2508
        if(elCurListItem) {
2509
            nCurItemIndex = elCurListItem._nItemIndex;
2510
        }
2511
 
2512
        var nNewItemIndex = (nKeyCode == 40) ?
2513
                (nCurItemIndex + 1) : (nCurItemIndex - 1);
2514
 
2515
        // Out of bounds
2516
        if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
2517
            return;
2518
        }
2519
 
2520
        if(elCurListItem) {
2521
            // Unhighlight current item
2522
            this._toggleHighlight(elCurListItem, "from");
2523
            this.itemArrowFromEvent.fire(this, elCurListItem);
2524
            YAHOO.log("Item arrowed from: " + elCurListItem._nItemIndex, "info", this.toString());
2525
        }
2526
        if(nNewItemIndex == -1) {
2527
           // Go back to query (remove type-ahead string)
2528
            if(this.delimChar) {
2529
                this._elTextbox.value = this._sPastSelections + this._sCurQuery;
2530
            }
2531
            else {
2532
                this._elTextbox.value = this._sCurQuery;
2533
            }
2534
            return;
2535
        }
2536
        if(nNewItemIndex == -2) {
2537
            // Close container
2538
            this._toggleContainer(false);
2539
            return;
2540
        }
2541
 
2542
        var elNewListItem = this._elList.childNodes[nNewItemIndex],
2543
 
2544
        // Scroll the container if necessary
2545
            elContent = this._elContent,
2546
            sOF = YAHOO.util.Dom.getStyle(elContent,"overflow"),
2547
            sOFY = YAHOO.util.Dom.getStyle(elContent,"overflowY"),
2548
            scrollOn = ((sOF == "auto") || (sOF == "scroll") || (sOFY == "auto") || (sOFY == "scroll"));
2549
        if(scrollOn && (nNewItemIndex > -1) &&
2550
        (nNewItemIndex < this._nDisplayedItems)) {
2551
            // User is keying down
2552
            if(nKeyCode == 40) {
2553
                // Bottom of selected item is below scroll area...
2554
                if((elNewListItem.offsetTop+elNewListItem.offsetHeight) > (elContent.scrollTop + elContent.offsetHeight)) {
2555
                    // Set bottom of scroll area to bottom of selected item
2556
                    elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2557
                }
2558
                // Bottom of selected item is above scroll area...
2559
                else if((elNewListItem.offsetTop+elNewListItem.offsetHeight) < elContent.scrollTop) {
2560
                    // Set top of selected item to top of scroll area
2561
                    elContent.scrollTop = elNewListItem.offsetTop;
2562
 
2563
                }
2564
            }
2565
            // User is keying up
2566
            else {
2567
                // Top of selected item is above scroll area
2568
                if(elNewListItem.offsetTop < elContent.scrollTop) {
2569
                    // Set top of scroll area to top of selected item
2570
                    this._elContent.scrollTop = elNewListItem.offsetTop;
2571
                }
2572
                // Top of selected item is below scroll area
2573
                else if(elNewListItem.offsetTop > (elContent.scrollTop + elContent.offsetHeight)) {
2574
                    // Set bottom of selected item to bottom of scroll area
2575
                    this._elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2576
                }
2577
            }
2578
        }
2579
 
2580
        this._toggleHighlight(elNewListItem, "to");
2581
        this.itemArrowToEvent.fire(this, elNewListItem);
2582
        YAHOO.log("Item arrowed to " + elNewListItem._nItemIndex, "info", this.toString());
2583
        if(this.typeAhead) {
2584
            this._updateValue(elNewListItem);
2585
            // Bug 2528552: Store as a selection
2586
            this._sCurQuery = elNewListItem._sResultMatch;
2587
        }
2588
    }
2589
};
2590
 
2591
/////////////////////////////////////////////////////////////////////////////
2592
//
2593
// Private event handlers
2594
//
2595
/////////////////////////////////////////////////////////////////////////////
2596
 
2597
/**
2598
 * Handles container mouseover events.
2599
 *
2600
 * @method _onContainerMouseover
2601
 * @param v {HTMLEvent} The mouseover event.
2602
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2603
 * @private
2604
 */
2605
YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
2606
    var elTarget = YAHOO.util.Event.getTarget(v);
2607
    var elTag = elTarget.nodeName.toLowerCase();
2608
    while(elTarget && (elTag != "table")) {
2609
        switch(elTag) {
2610
            case "body":
2611
                return;
2612
            case "li":
2613
                if(oSelf.prehighlightClassName) {
2614
                    oSelf._togglePrehighlight(elTarget,"mouseover");
2615
                }
2616
                else {
2617
                    oSelf._toggleHighlight(elTarget,"to");
2618
                }
2619
 
2620
                oSelf.itemMouseOverEvent.fire(oSelf, elTarget);
2621
                YAHOO.log("Item moused over " + elTarget._nItemIndex, "info", oSelf.toString());
2622
                break;
2623
            case "div":
2624
                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2625
                    oSelf._bOverContainer = true;
2626
                    return;
2627
                }
2628
                break;
2629
            default:
2630
                break;
2631
        }
2632
 
2633
        elTarget = elTarget.parentNode;
2634
        if(elTarget) {
2635
            elTag = elTarget.nodeName.toLowerCase();
2636
        }
2637
    }
2638
};
2639
 
2640
/**
2641
 * Handles container mouseout events.
2642
 *
2643
 * @method _onContainerMouseout
2644
 * @param v {HTMLEvent} The mouseout event.
2645
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2646
 * @private
2647
 */
2648
YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
2649
    var elTarget = YAHOO.util.Event.getTarget(v);
2650
    var elTag = elTarget.nodeName.toLowerCase();
2651
    while(elTarget && (elTag != "table")) {
2652
        switch(elTag) {
2653
            case "body":
2654
                return;
2655
            case "li":
2656
                if(oSelf.prehighlightClassName) {
2657
                    oSelf._togglePrehighlight(elTarget,"mouseout");
2658
                }
2659
                else {
2660
                    oSelf._toggleHighlight(elTarget,"from");
2661
                }
2662
 
2663
                oSelf.itemMouseOutEvent.fire(oSelf, elTarget);
2664
                YAHOO.log("Item moused out " + elTarget._nItemIndex, "info", oSelf.toString());
2665
                break;
2666
            case "ul":
2667
                oSelf._toggleHighlight(oSelf._elCurListItem,"to");
2668
                break;
2669
            case "div":
2670
                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2671
                    oSelf._bOverContainer = false;
2672
                    return;
2673
                }
2674
                break;
2675
            default:
2676
                break;
2677
        }
2678
 
2679
        elTarget = elTarget.parentNode;
2680
        if(elTarget) {
2681
            elTag = elTarget.nodeName.toLowerCase();
2682
        }
2683
    }
2684
};
2685
 
2686
/**
2687
 * Handles container click events.
2688
 *
2689
 * @method _onContainerClick
2690
 * @param v {HTMLEvent} The click event.
2691
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2692
 * @private
2693
 */
2694
YAHOO.widget.AutoComplete.prototype._onContainerClick = function(v,oSelf) {
2695
    var elTarget = YAHOO.util.Event.getTarget(v);
2696
    var elTag = elTarget.nodeName.toLowerCase();
2697
    while(elTarget && (elTag != "table")) {
2698
        switch(elTag) {
2699
            case "body":
2700
                return;
2701
            case "li":
2702
                // In case item has not been moused over
2703
                oSelf._toggleHighlight(elTarget,"to");
2704
                oSelf._selectItem(elTarget);
2705
                return;
2706
            default:
2707
                break;
2708
        }
2709
 
2710
        elTarget = elTarget.parentNode;
2711
        if(elTarget) {
2712
            elTag = elTarget.nodeName.toLowerCase();
2713
        }
2714
    }
2715
};
2716
 
2717
 
2718
/**
2719
 * Handles container scroll events.
2720
 *
2721
 * @method _onContainerScroll
2722
 * @param v {HTMLEvent} The scroll event.
2723
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2724
 * @private
2725
 */
2726
YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
2727
    oSelf._focus();
2728
};
2729
 
2730
/**
2731
 * Handles container resize events.
2732
 *
2733
 * @method _onContainerResize
2734
 * @param v {HTMLEvent} The resize event.
2735
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2736
 * @private
2737
 */
2738
YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
2739
    oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
2740
};
2741
 
2742
 
2743
/**
2744
 * Handles textbox keydown events of functional keys, mainly for UI behavior.
2745
 *
2746
 * @method _onTextboxKeyDown
2747
 * @param v {HTMLEvent} The keydown event.
2748
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2749
 * @private
2750
 */
2751
YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
2752
    var nKeyCode = v.keyCode;
2753
 
2754
    // Clear timeout
2755
    if(oSelf._nTypeAheadDelayID != -1) {
2756
        clearTimeout(oSelf._nTypeAheadDelayID);
2757
    }
2758
 
2759
    switch (nKeyCode) {
2760
        case 9: // tab
2761
            if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2762
                // select an item or clear out
2763
                if(oSelf._elCurListItem) {
2764
                    if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
2765
                        if(oSelf._bContainerOpen) {
2766
                            YAHOO.util.Event.stopEvent(v);
2767
                        }
2768
                    }
2769
                    oSelf._selectItem(oSelf._elCurListItem);
2770
                }
2771
                else {
2772
                    oSelf._toggleContainer(false);
2773
                }
2774
            }
2775
            break;
2776
        case 13: // enter
2777
            if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2778
                if(oSelf._elCurListItem) {
2779
                    if(oSelf._nKeyCode != nKeyCode) {
2780
                        if(oSelf._bContainerOpen) {
2781
                            YAHOO.util.Event.stopEvent(v);
2782
                        }
2783
                    }
2784
                    oSelf._selectItem(oSelf._elCurListItem);
2785
                }
2786
                else {
2787
                    oSelf._toggleContainer(false);
2788
                }
2789
            }
2790
            break;
2791
        case 27: // esc
2792
            oSelf._toggleContainer(false);
2793
            return;
2794
        case 39: // right
2795
            oSelf._jumpSelection();
2796
            break;
2797
        case 38: // up
2798
            if(oSelf._bContainerOpen) {
2799
                YAHOO.util.Event.stopEvent(v);
2800
                oSelf._moveSelection(nKeyCode);
2801
            }
2802
            break;
2803
        case 40: // down
2804
            if(oSelf._bContainerOpen) {
2805
                YAHOO.util.Event.stopEvent(v);
2806
                oSelf._moveSelection(nKeyCode);
2807
            }
2808
            break;
2809
        default:
2810
            oSelf._bItemSelected = false;
2811
            oSelf._toggleHighlight(oSelf._elCurListItem, "from");
2812
 
2813
            oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
2814
            YAHOO.log("Textbox keyed", "info", oSelf.toString());
2815
            break;
2816
    }
2817
 
2818
    if(nKeyCode === 18){
2819
        oSelf._enableIntervalDetection();
2820
    }
2821
    oSelf._nKeyCode = nKeyCode;
2822
};
2823
 
2824
/**
2825
 * Handles textbox keypress events.
2826
 * @method _onTextboxKeyPress
2827
 * @param v {HTMLEvent} The keypress event.
2828
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2829
 * @private
2830
 */
2831
YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
2832
    var nKeyCode = v.keyCode;
2833
 
2834
        // Expose only to non SF3 (bug 1978549) Mac browsers (bug 790337) and  Opera browsers (bug 583531),
2835
        // where stopEvent is ineffective on keydown events
2836
        if(YAHOO.env.ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") != -1) && (YAHOO.env.ua.webkit < 420)) {
2837
            switch (nKeyCode) {
2838
            case 9: // tab
2839
                // select an item or clear out
2840
                if(oSelf._bContainerOpen) {
2841
                    if(oSelf.delimChar) {
2842
                        YAHOO.util.Event.stopEvent(v);
2843
                    }
2844
                    if(oSelf._elCurListItem) {
2845
                        oSelf._selectItem(oSelf._elCurListItem);
2846
                    }
2847
                    else {
2848
                        oSelf._toggleContainer(false);
2849
                    }
2850
                }
2851
                break;
2852
            case 13: // enter
2853
                if(oSelf._bContainerOpen) {
2854
                    YAHOO.util.Event.stopEvent(v);
2855
                    if(oSelf._elCurListItem) {
2856
                        oSelf._selectItem(oSelf._elCurListItem);
2857
                    }
2858
                    else {
2859
                        oSelf._toggleContainer(false);
2860
                    }
2861
                }
2862
                break;
2863
            default:
2864
                break;
2865
            }
2866
        }
2867
 
2868
        //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
2869
        // Korean IME detected
2870
        else if(nKeyCode == 229) {
2871
            oSelf._enableIntervalDetection();
2872
        }
2873
};
2874
 
2875
/**
2876
 * Handles textbox keyup events to trigger queries.
2877
 *
2878
 * @method _onTextboxKeyUp
2879
 * @param v {HTMLEvent} The keyup event.
2880
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2881
 * @private
2882
 */
2883
YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
2884
    var sText = this.value; //string in textbox
2885
 
2886
    // Check to see if any of the public properties have been updated
2887
    oSelf._initProps();
2888
 
2889
    // Filter out chars that don't trigger queries
2890
    var nKeyCode = v.keyCode;
2891
    if(oSelf._isIgnoreKey(nKeyCode)) {
2892
        return;
2893
    }
2894
 
2895
    // Clear previous timeout
2896
    if(oSelf._nDelayID != -1) {
2897
        clearTimeout(oSelf._nDelayID);
2898
    }
2899
 
2900
    // Set new timeout
2901
    oSelf._nDelayID = setTimeout(function(){
2902
            oSelf._sendQuery(sText);
2903
        },(oSelf.queryDelay * 1000));
2904
};
2905
 
2906
/**
2907
 * Handles text input box receiving focus.
2908
 *
2909
 * @method _onTextboxFocus
2910
 * @param v {HTMLEvent} The focus event.
2911
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2912
 * @private
2913
 */
2914
YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
2915
    // Start of a new interaction
2916
    if(!oSelf._bFocused) {
2917
        oSelf._elTextbox.setAttribute("autocomplete","off");
2918
        oSelf._bFocused = true;
2919
        oSelf._sInitInputValue = oSelf._elTextbox.value;
2920
        oSelf.textboxFocusEvent.fire(oSelf);
2921
        YAHOO.log("Textbox focused", "info", oSelf.toString());
2922
    }
2923
};
2924
 
2925
/**
2926
 * Handles text input box losing focus.
2927
 *
2928
 * @method _onTextboxBlur
2929
 * @param v {HTMLEvent} The focus event.
2930
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2931
 * @private
2932
 */
2933
YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
2934
    // Is a true blur
2935
    if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
2936
        // Current query needs to be validated as a selection
2937
        if(!oSelf._bItemSelected) {
2938
            var elMatchListItem = oSelf._textMatchesOption();
2939
            // Container is closed or current query doesn't match any result
2940
            if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (elMatchListItem === null))) {
2941
                // Force selection is enabled so clear the current query
2942
                if(oSelf.forceSelection) {
2943
                    oSelf._clearSelection();
2944
                }
2945
                // Treat current query as a valid selection
2946
                else {
2947
                    oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery);
2948
                    YAHOO.log("Unmatched item selected: " + oSelf._sCurQuery, "info", oSelf.toString());
2949
                }
2950
            }
2951
            // Container is open and current query matches a result
2952
            else {
2953
                // Force a selection when textbox is blurred with a match
2954
                if(oSelf.forceSelection) {
2955
                    oSelf._selectItem(elMatchListItem);
2956
                }
2957
            }
2958
        }
2959
 
2960
        oSelf._clearInterval();
2961
        oSelf._bFocused = false;
2962
        if(oSelf._sInitInputValue !== oSelf._elTextbox.value) {
2963
            oSelf.textboxChangeEvent.fire(oSelf);
2964
        }
2965
        oSelf.textboxBlurEvent.fire(oSelf);
2966
        YAHOO.log("Textbox blurred", "info", oSelf.toString());
2967
 
2968
        oSelf._toggleContainer(false);
2969
    }
2970
    // Not a true blur if it was a selection via mouse click
2971
    else {
2972
        oSelf._focus();
2973
    }
2974
};
2975
 
2976
/**
2977
 * Handles window unload event.
2978
 *
2979
 * @method _onWindowUnload
2980
 * @param v {HTMLEvent} The unload event.
2981
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2982
 * @private
2983
 */
2984
YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
2985
    if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
2986
        oSelf._elTextbox.setAttribute("autocomplete","on");
2987
    }
2988
};
2989
 
2990
/////////////////////////////////////////////////////////////////////////////
2991
//
2992
// Deprecated for Backwards Compatibility
2993
//
2994
/////////////////////////////////////////////////////////////////////////////
2995
/**
2996
 * @method doBeforeSendQuery
2997
 * @deprecated Use generateRequest.
2998
 */
2999
YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
3000
    return this.generateRequest(sQuery);
3001
};
3002
 
3003
/**
3004
 * @method getListItems
3005
 * @deprecated Use getListEl().childNodes.
3006
 */
3007
YAHOO.widget.AutoComplete.prototype.getListItems = function() {
3008
    var allListItemEls = [],
3009
        els = this._elList.childNodes;
3010
    for(var i=els.length-1; i>=0; i--) {
3011
        allListItemEls[i] = els[i];
3012
    }
3013
    return allListItemEls;
3014
};
3015
 
3016
/////////////////////////////////////////////////////////////////////////
3017
//
3018
// Private static methods
3019
//
3020
/////////////////////////////////////////////////////////////////////////
3021
 
3022
/**
3023
 * Clones object literal or array of object literals.
3024
 *
3025
 * @method AutoComplete._cloneObject
3026
 * @param o {Object} Object.
3027
 * @private
3028
 * @static
3029
 */
3030
YAHOO.widget.AutoComplete._cloneObject = function(o) {
3031
    if(!YAHOO.lang.isValue(o)) {
3032
        return o;
3033
    }
3034
 
3035
    var copy = {};
3036
 
3037
    if(YAHOO.lang.isFunction(o)) {
3038
        copy = o;
3039
    }
3040
    else if(YAHOO.lang.isArray(o)) {
3041
        var array = [];
3042
        for(var i=0,len=o.length;i<len;i++) {
3043
            array[i] = YAHOO.widget.AutoComplete._cloneObject(o[i]);
3044
        }
3045
        copy = array;
3046
    }
3047
    else if(YAHOO.lang.isObject(o)) {
3048
        for (var x in o){
3049
            if(YAHOO.lang.hasOwnProperty(o, x)) {
3050
                if(YAHOO.lang.isValue(o[x]) && YAHOO.lang.isObject(o[x]) || YAHOO.lang.isArray(o[x])) {
3051
                    copy[x] = YAHOO.widget.AutoComplete._cloneObject(o[x]);
3052
                }
3053
                else {
3054
                    copy[x] = o[x];
3055
                }
3056
            }
3057
        }
3058
    }
3059
    else {
3060
        copy = o;
3061
    }
3062
 
3063
    return copy;
3064
};
3065
 
3066
 
3067
 
3068
 
3069
YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.9.0", build: "2800"});
3070
 
3071
}, '2.9.0' ,{"requires": ["yui2-yahoo", "yui2-dom", "yui2-event", "yui2-skin-sam-autocomplete", "yui2-datasource"], "optional": ["yui2-animation", "yui2-connection"]});