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