View | Details | Raw Unified | Return to bug 7747
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/doc-head-close.inc (-4 lines)
Lines 29-38 Link Here
29
29
30
<!-- yui js --> 
30
<!-- yui js --> 
31
<script type="text/javascript" src="[% yuipath %]/utilities/utilities.js"></script> 
31
<script type="text/javascript" src="[% yuipath %]/utilities/utilities.js"></script> 
32
<script type="text/javascript" src="[% yuipath %]/datasource/datasource-min.js"></script>
33
[% IF ( CircAutocompl ) %]
34
    <script type="text/javascript" src="[% yuipath %]/autocomplete/autocomplete-min.js"></script>
35
[% END %]
36
<script type="text/javascript" src="[% yuipath %]/button/button-min.js"></script> 
32
<script type="text/javascript" src="[% yuipath %]/button/button-min.js"></script> 
37
<script type="text/javascript" src="[% yuipath %]/container/container_core-min.js"></script> 
33
<script type="text/javascript" src="[% yuipath %]/container/container_core-min.js"></script> 
38
<script type="text/javascript" src="[% yuipath %]/menu/menu-min.js"></script> 
34
<script type="text/javascript" src="[% yuipath %]/menu/menu-min.js"></script> 
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/autocomplete/assets/autocomplete-core.css (-7 lines)
Lines 1-7 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
/* This file intentionally left blank */
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/autocomplete/assets/skins/sam/autocomplete-skin.css (-57 lines)
Lines 1-57 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
/* styles for entire widget */
8
.yui-skin-sam .yui-ac {
9
    position:relative;font-family:arial;font-size:100%;
10
}
11
12
/* styles for input field */
13
.yui-skin-sam .yui-ac-input {
14
    position:absolute;width:100%;
15
}
16
17
/* styles for results container */
18
.yui-skin-sam .yui-ac-container {
19
    position:absolute;top:1.6em;width:100%;
20
}
21
22
/* styles for header/body/footer wrapper within container */
23
.yui-skin-sam .yui-ac-content {
24
    position:absolute;width:100%;border:1px solid #808080;background:#fff;overflow:hidden;z-index:9050;
25
}
26
27
/* styles for container shadow */
28
.yui-skin-sam .yui-ac-shadow {
29
    position:absolute;margin:.3em;width:100%;background:#000;-moz-opacity: 0.10;opacity:.10;filter:alpha(opacity=10);z-index:9049;
30
}
31
32
/* styles for container iframe */
33
.yui-skin-sam .yui-ac iframe {
34
    opacity:0;filter: alpha(opacity=0);
35
    padding-right:.3em; padding-bottom:.3em; /* Bug 2026798: extend iframe to shim the shadow */
36
}
37
38
/* styles for results list */
39
.yui-skin-sam .yui-ac-content ul{
40
    margin:0;padding:0;width:100%;
41
}
42
43
/* styles for result item */
44
.yui-skin-sam .yui-ac-content li {
45
    margin:0;padding:2px 5px;cursor:default;white-space:nowrap;list-style:none;
46
    zoom:1; /* For IE to trigger mouse events on LI */
47
}
48
49
/* styles for prehighlighted result item */
50
.yui-skin-sam .yui-ac-content li.yui-ac-prehighlight {
51
    background:#B3D4FF;
52
}
53
54
/* styles for highlighted result item */
55
.yui-skin-sam .yui-ac-content li.yui-ac-highlight {
56
    background:#426FD9;color:#FFF;
57
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/autocomplete/assets/skins/sam/autocomplete.css (-7 lines)
Lines 1-7 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
.yui-skin-sam .yui-ac{position:relative;font-family:arial;font-size:100%;}.yui-skin-sam .yui-ac-input{position:absolute;width:100%;}.yui-skin-sam .yui-ac-container{position:absolute;top:1.6em;width:100%;}.yui-skin-sam .yui-ac-content{position:absolute;width:100%;border:1px solid #808080;background:#fff;overflow:hidden;z-index:9050;}.yui-skin-sam .yui-ac-shadow{position:absolute;margin:.3em;width:100%;background:#000;-moz-opacity:.10;opacity:.10;filter:alpha(opacity=10);z-index:9049;}.yui-skin-sam .yui-ac iframe{opacity:0;filter:alpha(opacity=0);padding-right:.3em;padding-bottom:.3em;}.yui-skin-sam .yui-ac-content ul{margin:0;padding:0;width:100%;}.yui-skin-sam .yui-ac-content li{margin:0;padding:2px 5px;cursor:default;white-space:nowrap;list-style:none;zoom:1;}.yui-skin-sam .yui-ac-content li.yui-ac-prehighlight{background:#B3D4FF;}.yui-skin-sam .yui-ac-content li.yui-ac-highlight{background:#426FD9;color:#FFF;}
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/autocomplete/autocomplete-debug.js (-3009 lines)
Lines 1-3009 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
/////////////////////////////////////////////////////////////////////////////
8
//
9
// YAHOO.widget.DataSource Backwards Compatibility
10
//
11
/////////////////////////////////////////////////////////////////////////////
12
13
YAHOO.widget.DS_JSArray = YAHOO.util.LocalDataSource;
14
15
YAHOO.widget.DS_JSFunction = YAHOO.util.FunctionDataSource;
16
17
YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
18
    var DS = new YAHOO.util.XHRDataSource(sScriptURI, oConfigs);
19
    DS._aDeprecatedSchema = aSchema;
20
    return DS;
21
};
22
23
YAHOO.widget.DS_ScriptNode = function(sScriptURI, aSchema, oConfigs) {
24
    var DS = new YAHOO.util.ScriptNodeDataSource(sScriptURI, oConfigs);
25
    DS._aDeprecatedSchema = aSchema;
26
    return DS;
27
};
28
29
YAHOO.widget.DS_XHR.TYPE_JSON = YAHOO.util.DataSourceBase.TYPE_JSON;
30
YAHOO.widget.DS_XHR.TYPE_XML = YAHOO.util.DataSourceBase.TYPE_XML;
31
YAHOO.widget.DS_XHR.TYPE_FLAT = YAHOO.util.DataSourceBase.TYPE_TEXT;
32
33
// TODO: widget.DS_ScriptNode.scriptCallbackParam
34
35
36
37
 /**
38
 * The AutoComplete control provides the front-end logic for text-entry suggestion and
39
 * completion functionality.
40
 *
41
 * @module autocomplete
42
 * @requires yahoo, dom, event, datasource
43
 * @optional animation
44
 * @namespace YAHOO.widget
45
 * @title AutoComplete Widget
46
 */
47
48
/****************************************************************************/
49
/****************************************************************************/
50
/****************************************************************************/
51
52
/**
53
 * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
54
 * auto completion widget.  Some key features:
55
 * <ul>
56
 * <li>Navigate with up/down arrow keys and/or mouse to pick a selection</li>
57
 * <li>The drop down container can "roll down" or "fly out" via configurable
58
 * animation</li>
59
 * <li>UI look-and-feel customizable through CSS, including container
60
 * attributes, borders, position, fonts, etc</li>
61
 * </ul>
62
 *
63
 * @class AutoComplete
64
 * @constructor
65
 * @param elInput {HTMLElement} DOM element reference of an input field.
66
 * @param elInput {String} String ID of an input field.
67
 * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
68
 * @param elContainer {String} String ID of an existing DIV.
69
 * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
70
 * @param oConfigs {Object} (optional) Object literal of configuration params.
71
 */
72
YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
73
    if(elInput && elContainer && oDataSource) {
74
        // Validate DataSource
75
        if(oDataSource && YAHOO.lang.isFunction(oDataSource.sendRequest)) {
76
            this.dataSource = oDataSource;
77
        }
78
        else {
79
            YAHOO.log("Could not instantiate AutoComplete due to an invalid DataSource", "error", this.toString());
80
            return;
81
        }
82
83
        // YAHOO.widget.DataSource schema backwards compatibility
84
        // Converted deprecated schema into supported schema
85
        // First assume key data is held in position 0 of results array
86
        this.key = 0;
87
        var schema = oDataSource.responseSchema;
88
        // An old school schema has been defined in the deprecated DataSource constructor
89
        if(oDataSource._aDeprecatedSchema) {
90
            var aDeprecatedSchema = oDataSource._aDeprecatedSchema;
91
            if(YAHOO.lang.isArray(aDeprecatedSchema)) {
92
                
93
                if((oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_JSON) || 
94
                (oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_UNKNOWN)) { // Used to default to unknown
95
                    // Store the resultsList
96
                    schema.resultsList = aDeprecatedSchema[0];
97
                    // Store the key
98
                    this.key = aDeprecatedSchema[1];
99
                    // Only resultsList and key are defined, so grab all the data
100
                    schema.fields = (aDeprecatedSchema.length < 3) ? null : aDeprecatedSchema.slice(1);
101
                }
102
                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_XML) {
103
                    schema.resultNode = aDeprecatedSchema[0];
104
                    this.key = aDeprecatedSchema[1];
105
                    schema.fields = aDeprecatedSchema.slice(1);
106
                }                
107
                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_TEXT) {
108
                    schema.recordDelim = aDeprecatedSchema[0];
109
                    schema.fieldDelim = aDeprecatedSchema[1];
110
                }                
111
                oDataSource.responseSchema = schema;
112
            }
113
        }
114
        
115
        // Validate input element
116
        if(YAHOO.util.Dom.inDocument(elInput)) {
117
            if(YAHOO.lang.isString(elInput)) {
118
                    this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
119
                    this._elTextbox = document.getElementById(elInput);
120
            }
121
            else {
122
                this._sName = (elInput.id) ?
123
                    "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
124
                    "instance" + YAHOO.widget.AutoComplete._nIndex;
125
                this._elTextbox = elInput;
126
            }
127
            YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
128
        }
129
        else {
130
            YAHOO.log("Could not instantiate AutoComplete due to an invalid input element", "error", this.toString());
131
            return;
132
        }
133
134
        // Validate container element
135
        if(YAHOO.util.Dom.inDocument(elContainer)) {
136
            if(YAHOO.lang.isString(elContainer)) {
137
                    this._elContainer = document.getElementById(elContainer);
138
            }
139
            else {
140
                this._elContainer = elContainer;
141
            }
142
            if(this._elContainer.style.display == "none") {
143
                YAHOO.log("The container may not display properly if display is set to \"none\" in CSS", "warn", this.toString());
144
            }
145
            
146
            // For skinning
147
            var elParent = this._elContainer.parentNode;
148
            var elTag = elParent.tagName.toLowerCase();
149
            if(elTag == "div") {
150
                YAHOO.util.Dom.addClass(elParent, "yui-ac");
151
            }
152
            else {
153
                YAHOO.log("Could not find the wrapper element for skinning", "warn", this.toString());
154
            }
155
        }
156
        else {
157
            YAHOO.log("Could not instantiate AutoComplete due to an invalid container element", "error", this.toString());
158
            return;
159
        }
160
161
        // Default applyLocalFilter setting is to enable for local sources
162
        if(this.dataSource.dataType === YAHOO.util.DataSourceBase.TYPE_LOCAL) {
163
            this.applyLocalFilter = true;
164
        }
165
        
166
        // Set any config params passed in to override defaults
167
        if(oConfigs && (oConfigs.constructor == Object)) {
168
            for(var sConfig in oConfigs) {
169
                if(sConfig) {
170
                    this[sConfig] = oConfigs[sConfig];
171
                }
172
            }
173
        }
174
175
        // Initialization sequence
176
        this._initContainerEl();
177
        this._initProps();
178
        this._initListEl();
179
        this._initContainerHelperEls();
180
181
        // Set up events
182
        var oSelf = this;
183
        var elTextbox = this._elTextbox;
184
185
        // Dom events
186
        YAHOO.util.Event.addListener(elTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
187
        YAHOO.util.Event.addListener(elTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
188
        YAHOO.util.Event.addListener(elTextbox,"focus",oSelf._onTextboxFocus,oSelf);
189
        YAHOO.util.Event.addListener(elTextbox,"blur",oSelf._onTextboxBlur,oSelf);
190
        YAHOO.util.Event.addListener(elContainer,"mouseover",oSelf._onContainerMouseover,oSelf);
191
        YAHOO.util.Event.addListener(elContainer,"mouseout",oSelf._onContainerMouseout,oSelf);
192
        YAHOO.util.Event.addListener(elContainer,"click",oSelf._onContainerClick,oSelf);
193
        YAHOO.util.Event.addListener(elContainer,"scroll",oSelf._onContainerScroll,oSelf);
194
        YAHOO.util.Event.addListener(elContainer,"resize",oSelf._onContainerResize,oSelf);
195
        YAHOO.util.Event.addListener(elTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
196
        YAHOO.util.Event.addListener(window,"unload",oSelf._onWindowUnload,oSelf);
197
198
        // Custom events
199
        this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
200
        this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
201
        this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
202
        this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
203
        this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
204
        this.containerPopulateEvent = new YAHOO.util.CustomEvent("containerPopulate", this);
205
        this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
206
        this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
207
        this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
208
        this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
209
        this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
210
        this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
211
        this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
212
        this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
213
        this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
214
        this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
215
        this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
216
        this.textboxChangeEvent = new YAHOO.util.CustomEvent("textboxChange", this);
217
        
218
        // Finish up
219
        elTextbox.setAttribute("autocomplete","off");
220
        YAHOO.widget.AutoComplete._nIndex++;
221
        YAHOO.log("AutoComplete initialized","info",this.toString());
222
    }
223
    // Required arguments were not found
224
    else {
225
        YAHOO.log("Could not instantiate AutoComplete due invalid arguments", "error", this.toString());
226
    }
227
};
228
229
/////////////////////////////////////////////////////////////////////////////
230
//
231
// Public member variables
232
//
233
/////////////////////////////////////////////////////////////////////////////
234
235
/**
236
 * The DataSource object that encapsulates the data used for auto completion.
237
 * This object should be an inherited object from YAHOO.widget.DataSource.
238
 *
239
 * @property dataSource
240
 * @type YAHOO.widget.DataSource
241
 */
242
YAHOO.widget.AutoComplete.prototype.dataSource = null;
243
244
/**
245
 * By default, results from local DataSources will pass through the filterResults
246
 * method to apply a client-side matching algorithm. 
247
 * 
248
 * @property applyLocalFilter
249
 * @type Boolean
250
 * @default true for local arrays and json, otherwise false
251
 */
252
YAHOO.widget.AutoComplete.prototype.applyLocalFilter = null;
253
254
/**
255
 * When applyLocalFilter is true, the local filtering algorthim can have case sensitivity
256
 * enabled. 
257
 * 
258
 * @property queryMatchCase
259
 * @type Boolean
260
 * @default false
261
 */
262
YAHOO.widget.AutoComplete.prototype.queryMatchCase = false;
263
264
/**
265
 * When applyLocalFilter is true, results can  be locally filtered to return
266
 * matching strings that "contain" the query string rather than simply "start with"
267
 * the query string.
268
 * 
269
 * @property queryMatchContains
270
 * @type Boolean
271
 * @default false
272
 */
273
YAHOO.widget.AutoComplete.prototype.queryMatchContains = false;
274
275
/**
276
 * Enables query subset matching. When the DataSource's cache is enabled and queryMatchSubset is
277
 * true, substrings of queries will return matching cached results. For
278
 * instance, if the first query is for "abc" susequent queries that start with
279
 * "abc", like "abcd", will be queried against the cache, and not the live data
280
 * source. Recommended only for DataSources that return comprehensive results
281
 * for queries with very few characters.
282
 *
283
 * @property queryMatchSubset
284
 * @type Boolean
285
 * @default false
286
 *
287
 */
288
YAHOO.widget.AutoComplete.prototype.queryMatchSubset = false;
289
290
/**
291
 * Number of characters that must be entered before querying for results. A negative value
292
 * effectively turns off the widget. A value of 0 allows queries of null or empty string
293
 * values.
294
 *
295
 * @property minQueryLength
296
 * @type Number
297
 * @default 1
298
 */
299
YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
300
301
/**
302
 * Maximum number of results to display in results container.
303
 *
304
 * @property maxResultsDisplayed
305
 * @type Number
306
 * @default 10
307
 */
308
YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
309
310
/**
311
 * Number of seconds to delay before submitting a query request.  If a query
312
 * request is received before a previous one has completed its delay, the
313
 * previous request is cancelled and the new request is set to the delay. If 
314
 * typeAhead is also enabled, this value must always be less than the typeAheadDelay
315
 * in order to avoid certain race conditions. 
316
 *
317
 * @property queryDelay
318
 * @type Number
319
 * @default 0.2
320
 */
321
YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
322
323
/**
324
 * If typeAhead is true, number of seconds to delay before updating input with
325
 * typeAhead value. In order to prevent certain race conditions, this value must
326
 * always be greater than the queryDelay.
327
 *
328
 * @property typeAheadDelay
329
 * @type Number
330
 * @default 0.5
331
 */
332
YAHOO.widget.AutoComplete.prototype.typeAheadDelay = 0.5;
333
334
/**
335
 * When IME usage is detected or interval detection is explicitly enabled,
336
 * AutoComplete will detect the input value at the given interval and send a
337
 * query if the value has changed.
338
 *
339
 * @property queryInterval
340
 * @type Number
341
 * @default 500
342
 */
343
YAHOO.widget.AutoComplete.prototype.queryInterval = 500;
344
345
/**
346
 * Class name of a highlighted item within results container.
347
 *
348
 * @property highlightClassName
349
 * @type String
350
 * @default "yui-ac-highlight"
351
 */
352
YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
353
354
/**
355
 * Class name of a pre-highlighted item within results container.
356
 *
357
 * @property prehighlightClassName
358
 * @type String
359
 */
360
YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
361
362
/**
363
 * Query delimiter. A single character separator for multiple delimited
364
 * selections. Multiple delimiter characteres may be defined as an array of
365
 * strings. A null value or empty string indicates that query results cannot
366
 * be delimited. This feature is not recommended if you need forceSelection to
367
 * be true.
368
 *
369
 * @property delimChar
370
 * @type String | String[]
371
 */
372
YAHOO.widget.AutoComplete.prototype.delimChar = null;
373
374
/**
375
 * Whether or not the first item in results container should be automatically highlighted
376
 * on expand.
377
 *
378
 * @property autoHighlight
379
 * @type Boolean
380
 * @default true
381
 */
382
YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
383
384
/**
385
 * If autohighlight is enabled, whether or not the input field should be automatically updated
386
 * with the first query result as the user types, auto-selecting the substring portion
387
 * of the first result that the user has not yet typed.
388
 *
389
 * @property typeAhead
390
 * @type Boolean
391
 * @default false
392
 */
393
YAHOO.widget.AutoComplete.prototype.typeAhead = false;
394
395
/**
396
 * Whether or not to animate the expansion/collapse of the results container in the
397
 * horizontal direction.
398
 *
399
 * @property animHoriz
400
 * @type Boolean
401
 * @default false
402
 */
403
YAHOO.widget.AutoComplete.prototype.animHoriz = false;
404
405
/**
406
 * Whether or not to animate the expansion/collapse of the results container in the
407
 * vertical direction.
408
 *
409
 * @property animVert
410
 * @type Boolean
411
 * @default true
412
 */
413
YAHOO.widget.AutoComplete.prototype.animVert = true;
414
415
/**
416
 * Speed of container expand/collapse animation, in seconds..
417
 *
418
 * @property animSpeed
419
 * @type Number
420
 * @default 0.3
421
 */
422
YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
423
424
/**
425
 * Whether or not to force the user's selection to match one of the query
426
 * results. Enabling this feature essentially transforms the input field into a
427
 * &lt;select&gt; field. This feature is not recommended with delimiter character(s)
428
 * defined.
429
 *
430
 * @property forceSelection
431
 * @type Boolean
432
 * @default false
433
 */
434
YAHOO.widget.AutoComplete.prototype.forceSelection = false;
435
436
/**
437
 * Whether or not to allow browsers to cache user-typed input in the input
438
 * field. Disabling this feature will prevent the widget from setting the
439
 * autocomplete="off" on the input field. When autocomplete="off"
440
 * and users click the back button after form submission, user-typed input can
441
 * be prefilled by the browser from its cache. This caching of user input may
442
 * not be desired for sensitive data, such as credit card numbers, in which
443
 * case, implementers should consider setting allowBrowserAutocomplete to false.
444
 *
445
 * @property allowBrowserAutocomplete
446
 * @type Boolean
447
 * @default true
448
 */
449
YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
450
451
/**
452
 * Enabling this feature prevents the toggling of the container to a collapsed state.
453
 * Setting to true does not automatically trigger the opening of the container.
454
 * Implementers are advised to pre-load the container with an explicit "sendQuery()" call.   
455
 *
456
 * @property alwaysShowContainer
457
 * @type Boolean
458
 * @default false
459
 */
460
YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
461
462
/**
463
 * Whether or not to use an iFrame to layer over Windows form elements in
464
 * IE. Set to true only when the results container will be on top of a
465
 * &lt;select&gt; field in IE and thus exposed to the IE z-index bug (i.e.,
466
 * 5.5 < IE < 7).
467
 *
468
 * @property useIFrame
469
 * @type Boolean
470
 * @default false
471
 */
472
YAHOO.widget.AutoComplete.prototype.useIFrame = false;
473
474
/**
475
 * Whether or not the results container should have a shadow.
476
 *
477
 * @property useShadow
478
 * @type Boolean
479
 * @default false
480
 */
481
YAHOO.widget.AutoComplete.prototype.useShadow = false;
482
483
/**
484
 * Whether or not the input field should be updated with selections.
485
 *
486
 * @property suppressInputUpdate
487
 * @type Boolean
488
 * @default false
489
 */
490
YAHOO.widget.AutoComplete.prototype.suppressInputUpdate = false;
491
492
/**
493
 * For backward compatibility to pre-2.6.0 formatResults() signatures, setting
494
 * resultsTypeList to true will take each object literal result returned by
495
 * DataSource and flatten into an array.  
496
 *
497
 * @property resultTypeList
498
 * @type Boolean
499
 * @default true
500
 */
501
YAHOO.widget.AutoComplete.prototype.resultTypeList = true;
502
503
/**
504
 * For XHR DataSources, AutoComplete will automatically insert a "?" between the server URI and 
505
 * the "query" param/value pair. To prevent this behavior, implementers should
506
 * set this value to false. To more fully customize the query syntax, implementers
507
 * should override the generateRequest() method. 
508
 *
509
 * @property queryQuestionMark
510
 * @type Boolean
511
 * @default true
512
 */
513
YAHOO.widget.AutoComplete.prototype.queryQuestionMark = true;
514
515
/**
516
 * If true, before each time the container expands, the container element will be
517
 * positioned to snap to the bottom-left corner of the input element. If
518
 * autoSnapContainer is set to false, this positioning will not be done.  
519
 *
520
 * @property autoSnapContainer
521
 * @type Boolean
522
 * @default true
523
 */
524
YAHOO.widget.AutoComplete.prototype.autoSnapContainer = true;
525
526
/////////////////////////////////////////////////////////////////////////////
527
//
528
// Public methods
529
//
530
/////////////////////////////////////////////////////////////////////////////
531
532
 /**
533
 * Public accessor to the unique name of the AutoComplete instance.
534
 *
535
 * @method toString
536
 * @return {String} Unique name of the AutoComplete instance.
537
 */
538
YAHOO.widget.AutoComplete.prototype.toString = function() {
539
    return "AutoComplete " + this._sName;
540
};
541
542
 /**
543
 * Returns DOM reference to input element.
544
 *
545
 * @method getInputEl
546
 * @return {HTMLELement} DOM reference to input element.
547
 */
548
YAHOO.widget.AutoComplete.prototype.getInputEl = function() {
549
    return this._elTextbox;
550
};
551
552
 /**
553
 * Returns DOM reference to container element.
554
 *
555
 * @method getContainerEl
556
 * @return {HTMLELement} DOM reference to container element.
557
 */
558
YAHOO.widget.AutoComplete.prototype.getContainerEl = function() {
559
    return this._elContainer;
560
};
561
562
 /**
563
 * Returns true if widget instance is currently active.
564
 *
565
 * @method isFocused
566
 * @return {Boolean} Returns true if widget instance is currently active.
567
 */
568
YAHOO.widget.AutoComplete.prototype.isFocused = function() {
569
    return this._bFocused;
570
};
571
572
 /**
573
 * Returns true if container is in an expanded state, false otherwise.
574
 *
575
 * @method isContainerOpen
576
 * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
577
 */
578
YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
579
    return this._bContainerOpen;
580
};
581
582
/**
583
 * Public accessor to the &lt;ul&gt; element that displays query results within the results container.
584
 *
585
 * @method getListEl
586
 * @return {HTMLElement[]} Reference to &lt;ul&gt; element within the results container.
587
 */
588
YAHOO.widget.AutoComplete.prototype.getListEl = function() {
589
    return this._elList;
590
};
591
592
/**
593
 * Public accessor to the matching string associated with a given &lt;li&gt; result.
594
 *
595
 * @method getListItemMatch
596
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
597
 * @return {String} Matching string.
598
 */
599
YAHOO.widget.AutoComplete.prototype.getListItemMatch = function(elListItem) {
600
    if(elListItem._sResultMatch) {
601
        return elListItem._sResultMatch;
602
    }
603
    else {
604
        return null;
605
    }
606
};
607
608
/**
609
 * Public accessor to the result data associated with a given &lt;li&gt; result.
610
 *
611
 * @method getListItemData
612
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
613
 * @return {Object} Result data.
614
 */
615
YAHOO.widget.AutoComplete.prototype.getListItemData = function(elListItem) {
616
    if(elListItem._oResultData) {
617
        return elListItem._oResultData;
618
    }
619
    else {
620
        return null;
621
    }
622
};
623
624
/**
625
 * Public accessor to the index of the associated with a given &lt;li&gt; result.
626
 *
627
 * @method getListItemIndex
628
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
629
 * @return {Number} Index.
630
 */
631
YAHOO.widget.AutoComplete.prototype.getListItemIndex = function(elListItem) {
632
    if(YAHOO.lang.isNumber(elListItem._nItemIndex)) {
633
        return elListItem._nItemIndex;
634
    }
635
    else {
636
        return null;
637
    }
638
};
639
640
/**
641
 * Sets HTML markup for the results container header. This markup will be
642
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-hd".
643
 *
644
 * @method setHeader
645
 * @param sHeader {String} HTML markup for results container header.
646
 */
647
YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
648
    if(this._elHeader) {
649
        var elHeader = this._elHeader;
650
        if(sHeader) {
651
            elHeader.innerHTML = sHeader;
652
            elHeader.style.display = "";
653
        }
654
        else {
655
            elHeader.innerHTML = "";
656
            elHeader.style.display = "none";
657
        }
658
    }
659
};
660
661
/**
662
 * Sets HTML markup for the results container footer. This markup will be
663
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-ft".
664
 *
665
 * @method setFooter
666
 * @param sFooter {String} HTML markup for results container footer.
667
 */
668
YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
669
    if(this._elFooter) {
670
        var elFooter = this._elFooter;
671
        if(sFooter) {
672
                elFooter.innerHTML = sFooter;
673
                elFooter.style.display = "";
674
        }
675
        else {
676
            elFooter.innerHTML = "";
677
            elFooter.style.display = "none";
678
        }
679
    }
680
};
681
682
/**
683
 * Sets HTML markup for the results container body. This markup will be
684
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-bd".
685
 *
686
 * @method setBody
687
 * @param sBody {String} HTML markup for results container body.
688
 */
689
YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
690
    if(this._elBody) {
691
        var elBody = this._elBody;
692
        YAHOO.util.Event.purgeElement(elBody, true);
693
        if(sBody) {
694
            elBody.innerHTML = sBody;
695
            elBody.style.display = "";
696
        }
697
        else {
698
            elBody.innerHTML = "";
699
            elBody.style.display = "none";
700
        }
701
        this._elList = null;
702
    }
703
};
704
705
/**
706
* A function that converts an AutoComplete query into a request value which is then
707
* passed to the DataSource's sendRequest method in order to retrieve data for 
708
* the query. By default, returns a String with the syntax: "query={query}"
709
* Implementers can customize this method for custom request syntaxes.
710
* 
711
* @method generateRequest
712
* @param sQuery {String} Query string
713
* @return {MIXED} Request
714
*/
715
YAHOO.widget.AutoComplete.prototype.generateRequest = function(sQuery) {
716
    var dataType = this.dataSource.dataType;
717
    
718
    // Transform query string in to a request for remote data
719
    // By default, local data doesn't need a transformation, just passes along the query as is.
720
    if(dataType === YAHOO.util.DataSourceBase.TYPE_XHR) {
721
        // By default, XHR GET requests look like "{scriptURI}?{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
722
        if(!this.dataSource.connMethodPost) {
723
            sQuery = (this.queryQuestionMark ? "?" : "") + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
724
                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");        
725
        }
726
        // By default, XHR POST bodies are sent to the {scriptURI} like "{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
727
        else {
728
            sQuery = (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
729
                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
730
        }
731
    }
732
    // By default, remote script node requests look like "{scriptURI}&{scriptCallbackParam}={callbackString}&{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
733
    else if(dataType === YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE) {
734
        sQuery = "&" + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
735
            (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");    
736
    }
737
    
738
    return sQuery;
739
};
740
741
/**
742
 * Makes query request to the DataSource.
743
 *
744
 * @method sendQuery
745
 * @param sQuery {String} Query string.
746
 */
747
YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
748
    // Activate focus for a new interaction
749
    this._bFocused = true;
750
    
751
    // Adjust programatically sent queries to look like they were input by user
752
    // when delimiters are enabled
753
    var newQuery = (this.delimChar) ? this._elTextbox.value + sQuery : sQuery;
754
    this._sendQuery(newQuery);
755
};
756
757
/**
758
 * Snaps container to bottom-left corner of input element
759
 *
760
 * @method snapContainer
761
 */
762
YAHOO.widget.AutoComplete.prototype.snapContainer = function() {
763
    var oTextbox = this._elTextbox,
764
        pos = YAHOO.util.Dom.getXY(oTextbox);
765
    pos[1] += YAHOO.util.Dom.get(oTextbox).offsetHeight + 2;
766
    YAHOO.util.Dom.setXY(this._elContainer,pos);
767
};
768
769
/**
770
 * Expands container.
771
 *
772
 * @method expandContainer
773
 */
774
YAHOO.widget.AutoComplete.prototype.expandContainer = function() {
775
    this._toggleContainer(true);
776
};
777
778
/**
779
 * Collapses container.
780
 *
781
 * @method collapseContainer
782
 */
783
YAHOO.widget.AutoComplete.prototype.collapseContainer = function() {
784
    this._toggleContainer(false);
785
};
786
787
/**
788
 * Clears entire list of suggestions.
789
 *
790
 * @method clearList
791
 */
792
YAHOO.widget.AutoComplete.prototype.clearList = function() {
793
    var allItems = this._elList.childNodes,
794
        i=allItems.length-1;
795
    for(; i>-1; i--) {
796
          allItems[i].style.display = "none";
797
    }
798
};
799
800
/**
801
 * Handles subset matching for when queryMatchSubset is enabled.
802
 *
803
 * @method getSubsetMatches
804
 * @param sQuery {String} Query string.
805
 * @return {Object} oParsedResponse or null. 
806
 */
807
YAHOO.widget.AutoComplete.prototype.getSubsetMatches = function(sQuery) {
808
    var subQuery, oCachedResponse, subRequest;
809
    // Loop through substrings of each cached element's query property...
810
    for(var i = sQuery.length; i >= this.minQueryLength ; i--) {
811
        subRequest = this.generateRequest(sQuery.substr(0,i));
812
        this.dataRequestEvent.fire(this, subQuery, subRequest);
813
        YAHOO.log("Searching for query subset \"" + subQuery + "\" in cache", "info", this.toString());
814
        
815
        // If a substring of the query is found in the cache
816
        oCachedResponse = this.dataSource.getCachedResponse(subRequest);
817
        if(oCachedResponse) {
818
            YAHOO.log("Found match for query subset \"" + subQuery + "\": " + YAHOO.lang.dump(oCachedResponse), "info", this.toString());
819
            return this.filterResults.apply(this.dataSource, [sQuery, oCachedResponse, oCachedResponse, {scope:this}]);
820
        }
821
    }
822
    YAHOO.log("Did not find subset match for query subset \"" + sQuery + "\"" , "info", this.toString());
823
    return null;
824
};
825
826
/**
827
 * Executed by DataSource (within DataSource scope via doBeforeParseData()) to
828
 * handle responseStripAfter cleanup.
829
 *
830
 * @method preparseRawResponse
831
 * @param sQuery {String} Query string.
832
 * @return {Object} oParsedResponse or null. 
833
 */
834
YAHOO.widget.AutoComplete.prototype.preparseRawResponse = function(oRequest, oFullResponse, oCallback) {
835
    var nEnd = ((this.responseStripAfter !== "") && (oFullResponse.indexOf)) ?
836
        oFullResponse.indexOf(this.responseStripAfter) : -1;
837
    if(nEnd != -1) {
838
        oFullResponse = oFullResponse.substring(0,nEnd);
839
    }
840
    return oFullResponse;
841
};
842
843
/**
844
 * Executed by DataSource (within DataSource scope via doBeforeCallback()) to
845
 * filter results through a simple client-side matching algorithm. 
846
 *
847
 * @method filterResults
848
 * @param sQuery {String} Original request.
849
 * @param oFullResponse {Object} Full response object.
850
 * @param oParsedResponse {Object} Parsed response object.
851
 * @param oCallback {Object} Callback object. 
852
 * @return {Object} Filtered response object.
853
 */
854
855
YAHOO.widget.AutoComplete.prototype.filterResults = function(sQuery, oFullResponse, oParsedResponse, oCallback) {
856
    // If AC has passed a query string value back to itself, grab it
857
    if(oCallback && oCallback.argument && oCallback.argument.query) {
858
        sQuery = oCallback.argument.query;
859
    }
860
861
    // Only if a query string is available to match against
862
    if(sQuery && sQuery !== "") {
863
        // First make a copy of the oParseResponse
864
        oParsedResponse = YAHOO.widget.AutoComplete._cloneObject(oParsedResponse);
865
        
866
        var oAC = oCallback.scope,
867
            oDS = this,
868
            allResults = oParsedResponse.results, // the array of results
869
            filteredResults = [], // container for filtered results,
870
            nMax = oAC.maxResultsDisplayed, // max to find
871
            bMatchCase = (oDS.queryMatchCase || oAC.queryMatchCase), // backward compat
872
            bMatchContains = (oDS.queryMatchContains || oAC.queryMatchContains); // backward compat
873
            
874
        // Loop through each result object...
875
        for(var i=0, len=allResults.length; i<len; i++) {
876
            var oResult = allResults[i];
877
878
            // Grab the data to match against from the result object...
879
            var sResult = null;
880
            
881
            // Result object is a simple string already
882
            if(YAHOO.lang.isString(oResult)) {
883
                sResult = oResult;
884
            }
885
            // Result object is an array of strings
886
            else if(YAHOO.lang.isArray(oResult)) {
887
                sResult = oResult[0];
888
            
889
            }
890
            // Result object is an object literal of strings
891
            else if(this.responseSchema.fields) {
892
                var key = this.responseSchema.fields[0].key || this.responseSchema.fields[0];
893
                sResult = oResult[key];
894
            }
895
            // Backwards compatibility
896
            else if(this.key) {
897
                sResult = oResult[this.key];
898
            }
899
            
900
            if(YAHOO.lang.isString(sResult)) {
901
                
902
                var sKeyIndex = (bMatchCase) ?
903
                sResult.indexOf(decodeURIComponent(sQuery)) :
904
                sResult.toLowerCase().indexOf(decodeURIComponent(sQuery).toLowerCase());
905
906
                // A STARTSWITH match is when the query is found at the beginning of the key string...
907
                if((!bMatchContains && (sKeyIndex === 0)) ||
908
                // A CONTAINS match is when the query is found anywhere within the key string...
909
                (bMatchContains && (sKeyIndex > -1))) {
910
                    // Stash the match
911
                    filteredResults.push(oResult);
912
                }
913
            }
914
            
915
            // Filter no more if maxResultsDisplayed is reached
916
            if(len>nMax && filteredResults.length===nMax) {
917
                break;
918
            }
919
        }
920
        oParsedResponse.results = filteredResults;
921
        YAHOO.log("Filtered " + filteredResults.length + " results against query \""  + sQuery + "\": " + YAHOO.lang.dump(filteredResults), "info", this.toString());
922
    }
923
    else {
924
        YAHOO.log("Did not filter results against query", "info", this.toString());
925
    }
926
    
927
    return oParsedResponse;
928
};
929
930
/**
931
 * Handles response for display. This is the callback function method passed to
932
 * YAHOO.util.DataSourceBase#sendRequest so results from the DataSource are
933
 * returned to the AutoComplete instance.
934
 *
935
 * @method handleResponse
936
 * @param sQuery {String} Original request.
937
 * @param oResponse {Object} Response object.
938
 * @param oPayload {MIXED} (optional) Additional argument(s)
939
 */
940
YAHOO.widget.AutoComplete.prototype.handleResponse = function(sQuery, oResponse, oPayload) {
941
    if((this instanceof YAHOO.widget.AutoComplete) && this._sName) {
942
        this._populateList(sQuery, oResponse, oPayload);
943
    }
944
};
945
946
/**
947
 * Overridable method called before container is loaded with result data.
948
 *
949
 * @method doBeforeLoadData
950
 * @param sQuery {String} Original request.
951
 * @param oResponse {Object} Response object.
952
 * @param oPayload {MIXED} (optional) Additional argument(s)
953
 * @return {Boolean} Return true to continue loading data, false to cancel.
954
 */
955
YAHOO.widget.AutoComplete.prototype.doBeforeLoadData = function(sQuery, oResponse, oPayload) {
956
    return true;
957
};
958
959
/**
960
 * Overridable method that returns HTML markup for one result to be populated
961
 * as innerHTML of an &lt;LI&gt; element. 
962
 *
963
 * @method formatResult
964
 * @param oResultData {Object} Result data object.
965
 * @param sQuery {String} The corresponding query string.
966
 * @param sResultMatch {HTMLElement} The current query string. 
967
 * @return {String} HTML markup of formatted result data.
968
 */
969
YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultData, sQuery, sResultMatch) {
970
    var sMarkup = (sResultMatch) ? sResultMatch : "";
971
    return sMarkup;
972
};
973
974
/**
975
 * Overridable method called before container expands allows implementers to access data
976
 * and DOM elements.
977
 *
978
 * @method doBeforeExpandContainer
979
 * @param elTextbox {HTMLElement} The text input box.
980
 * @param elContainer {HTMLElement} The container element.
981
 * @param sQuery {String} The query string.
982
 * @param aResults {Object[]}  An array of query results.
983
 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
984
 */
985
YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
986
    return true;
987
};
988
989
990
/**
991
 * Nulls out the entire AutoComplete instance and related objects, removes attached
992
 * event listeners, and clears out DOM elements inside the container. After
993
 * calling this method, the instance reference should be expliclitly nulled by
994
 * implementer, as in myAutoComplete = null. Use with caution!
995
 *
996
 * @method destroy
997
 */
998
YAHOO.widget.AutoComplete.prototype.destroy = function() {
999
    var instanceName = this.toString();
1000
    var elInput = this._elTextbox;
1001
    var elContainer = this._elContainer;
1002
1003
    // Unhook custom events
1004
    this.textboxFocusEvent.unsubscribeAll();
1005
    this.textboxKeyEvent.unsubscribeAll();
1006
    this.dataRequestEvent.unsubscribeAll();
1007
    this.dataReturnEvent.unsubscribeAll();
1008
    this.dataErrorEvent.unsubscribeAll();
1009
    this.containerPopulateEvent.unsubscribeAll();
1010
    this.containerExpandEvent.unsubscribeAll();
1011
    this.typeAheadEvent.unsubscribeAll();
1012
    this.itemMouseOverEvent.unsubscribeAll();
1013
    this.itemMouseOutEvent.unsubscribeAll();
1014
    this.itemArrowToEvent.unsubscribeAll();
1015
    this.itemArrowFromEvent.unsubscribeAll();
1016
    this.itemSelectEvent.unsubscribeAll();
1017
    this.unmatchedItemSelectEvent.unsubscribeAll();
1018
    this.selectionEnforceEvent.unsubscribeAll();
1019
    this.containerCollapseEvent.unsubscribeAll();
1020
    this.textboxBlurEvent.unsubscribeAll();
1021
    this.textboxChangeEvent.unsubscribeAll();
1022
1023
    // Unhook DOM events
1024
    YAHOO.util.Event.purgeElement(elInput, true);
1025
    YAHOO.util.Event.purgeElement(elContainer, true);
1026
1027
    // Remove DOM elements
1028
    elContainer.innerHTML = "";
1029
1030
    // Null out objects
1031
    for(var key in this) {
1032
        if(YAHOO.lang.hasOwnProperty(this, key)) {
1033
            this[key] = null;
1034
        }
1035
    }
1036
1037
    YAHOO.log("AutoComplete instance destroyed: " + instanceName);
1038
};
1039
1040
/////////////////////////////////////////////////////////////////////////////
1041
//
1042
// Public events
1043
//
1044
/////////////////////////////////////////////////////////////////////////////
1045
1046
/**
1047
 * Fired when the input field receives focus.
1048
 *
1049
 * @event textboxFocusEvent
1050
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1051
 */
1052
YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
1053
1054
/**
1055
 * Fired when the input field receives key input.
1056
 *
1057
 * @event textboxKeyEvent
1058
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1059
 * @param nKeycode {Number} The keycode number.
1060
 */
1061
YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
1062
1063
/**
1064
 * Fired when the AutoComplete instance makes a request to the DataSource.
1065
 * 
1066
 * @event dataRequestEvent
1067
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1068
 * @param sQuery {String} The query string. 
1069
 * @param oRequest {Object} The request.
1070
 */
1071
YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
1072
1073
/**
1074
 * Fired when the AutoComplete instance receives query results from the data
1075
 * source.
1076
 *
1077
 * @event dataReturnEvent
1078
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1079
 * @param sQuery {String} The query string.
1080
 * @param aResults {Object[]} Results array.
1081
 */
1082
YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
1083
1084
/**
1085
 * Fired when the AutoComplete instance does not receive query results from the
1086
 * DataSource due to an error.
1087
 *
1088
 * @event dataErrorEvent
1089
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1090
 * @param sQuery {String} The query string.
1091
 * @param oResponse {Object} The response object, if available.
1092
 */
1093
YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
1094
1095
/**
1096
 * Fired when the results container is populated.
1097
 *
1098
 * @event containerPopulateEvent
1099
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1100
 */
1101
YAHOO.widget.AutoComplete.prototype.containerPopulateEvent = null;
1102
1103
/**
1104
 * Fired when the results container is expanded.
1105
 *
1106
 * @event containerExpandEvent
1107
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1108
 */
1109
YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
1110
1111
/**
1112
 * Fired when the input field has been prefilled by the type-ahead
1113
 * feature. 
1114
 *
1115
 * @event typeAheadEvent
1116
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1117
 * @param sQuery {String} The query string.
1118
 * @param sPrefill {String} The prefill string.
1119
 */
1120
YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
1121
1122
/**
1123
 * Fired when result item has been moused over.
1124
 *
1125
 * @event itemMouseOverEvent
1126
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1127
 * @param elItem {HTMLElement} The &lt;li&gt element item moused to.
1128
 */
1129
YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
1130
1131
/**
1132
 * Fired when result item has been moused out.
1133
 *
1134
 * @event itemMouseOutEvent
1135
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1136
 * @param elItem {HTMLElement} The &lt;li&gt; element item moused from.
1137
 */
1138
YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
1139
1140
/**
1141
 * Fired when result item has been arrowed to. 
1142
 *
1143
 * @event itemArrowToEvent
1144
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1145
 * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed to.
1146
 */
1147
YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
1148
1149
/**
1150
 * Fired when result item has been arrowed away from.
1151
 *
1152
 * @event itemArrowFromEvent
1153
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1154
 * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed from.
1155
 */
1156
YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
1157
1158
/**
1159
 * Fired when an item is selected via mouse click, ENTER key, or TAB key.
1160
 *
1161
 * @event itemSelectEvent
1162
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1163
 * @param elItem {HTMLElement} The selected &lt;li&gt; element item.
1164
 * @param oData {Object} The data returned for the item, either as an object,
1165
 * or mapped from the schema into an array.
1166
 */
1167
YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
1168
1169
/**
1170
 * Fired when a user selection does not match any of the displayed result items.
1171
 *
1172
 * @event unmatchedItemSelectEvent
1173
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1174
 * @param sSelection {String} The selected string.  
1175
 */
1176
YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
1177
1178
/**
1179
 * Fired if forceSelection is enabled and the user's input has been cleared
1180
 * because it did not match one of the returned query results.
1181
 *
1182
 * @event selectionEnforceEvent
1183
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1184
 * @param sClearedValue {String} The cleared value (including delimiters if applicable). 
1185
 */
1186
YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
1187
1188
/**
1189
 * Fired when the results container is collapsed.
1190
 *
1191
 * @event containerCollapseEvent
1192
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1193
 */
1194
YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
1195
1196
/**
1197
 * Fired when the input field loses focus.
1198
 *
1199
 * @event textboxBlurEvent
1200
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1201
 */
1202
YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
1203
1204
/**
1205
 * Fired when the input field value has changed when it loses focus.
1206
 *
1207
 * @event textboxChangeEvent
1208
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1209
 */
1210
YAHOO.widget.AutoComplete.prototype.textboxChangeEvent = null;
1211
1212
/////////////////////////////////////////////////////////////////////////////
1213
//
1214
// Private member variables
1215
//
1216
/////////////////////////////////////////////////////////////////////////////
1217
1218
/**
1219
 * Internal class variable to index multiple AutoComplete instances.
1220
 *
1221
 * @property _nIndex
1222
 * @type Number
1223
 * @default 0
1224
 * @private
1225
 */
1226
YAHOO.widget.AutoComplete._nIndex = 0;
1227
1228
/**
1229
 * Name of AutoComplete instance.
1230
 *
1231
 * @property _sName
1232
 * @type String
1233
 * @private
1234
 */
1235
YAHOO.widget.AutoComplete.prototype._sName = null;
1236
1237
/**
1238
 * Text input field DOM element.
1239
 *
1240
 * @property _elTextbox
1241
 * @type HTMLElement
1242
 * @private
1243
 */
1244
YAHOO.widget.AutoComplete.prototype._elTextbox = null;
1245
1246
/**
1247
 * Container DOM element.
1248
 *
1249
 * @property _elContainer
1250
 * @type HTMLElement
1251
 * @private
1252
 */
1253
YAHOO.widget.AutoComplete.prototype._elContainer = null;
1254
1255
/**
1256
 * Reference to content element within container element.
1257
 *
1258
 * @property _elContent
1259
 * @type HTMLElement
1260
 * @private
1261
 */
1262
YAHOO.widget.AutoComplete.prototype._elContent = null;
1263
1264
/**
1265
 * Reference to header element within content element.
1266
 *
1267
 * @property _elHeader
1268
 * @type HTMLElement
1269
 * @private
1270
 */
1271
YAHOO.widget.AutoComplete.prototype._elHeader = null;
1272
1273
/**
1274
 * Reference to body element within content element.
1275
 *
1276
 * @property _elBody
1277
 * @type HTMLElement
1278
 * @private
1279
 */
1280
YAHOO.widget.AutoComplete.prototype._elBody = null;
1281
1282
/**
1283
 * Reference to footer element within content element.
1284
 *
1285
 * @property _elFooter
1286
 * @type HTMLElement
1287
 * @private
1288
 */
1289
YAHOO.widget.AutoComplete.prototype._elFooter = null;
1290
1291
/**
1292
 * Reference to shadow element within container element.
1293
 *
1294
 * @property _elShadow
1295
 * @type HTMLElement
1296
 * @private
1297
 */
1298
YAHOO.widget.AutoComplete.prototype._elShadow = null;
1299
1300
/**
1301
 * Reference to iframe element within container element.
1302
 *
1303
 * @property _elIFrame
1304
 * @type HTMLElement
1305
 * @private
1306
 */
1307
YAHOO.widget.AutoComplete.prototype._elIFrame = null;
1308
1309
/**
1310
 * Whether or not the widget instance is currently active. If query results come back
1311
 * but the user has already moved on, do not proceed with auto complete behavior.
1312
 *
1313
 * @property _bFocused
1314
 * @type Boolean
1315
 * @private
1316
 */
1317
YAHOO.widget.AutoComplete.prototype._bFocused = false;
1318
1319
/**
1320
 * Animation instance for container expand/collapse.
1321
 *
1322
 * @property _oAnim
1323
 * @type Boolean
1324
 * @private
1325
 */
1326
YAHOO.widget.AutoComplete.prototype._oAnim = null;
1327
1328
/**
1329
 * Whether or not the results container is currently open.
1330
 *
1331
 * @property _bContainerOpen
1332
 * @type Boolean
1333
 * @private
1334
 */
1335
YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
1336
1337
/**
1338
 * Whether or not the mouse is currently over the results
1339
 * container. This is necessary in order to prevent clicks on container items
1340
 * from being text input field blur events.
1341
 *
1342
 * @property _bOverContainer
1343
 * @type Boolean
1344
 * @private
1345
 */
1346
YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
1347
1348
/**
1349
 * Internal reference to &lt;ul&gt; elements that contains query results within the
1350
 * results container.
1351
 *
1352
 * @property _elList
1353
 * @type HTMLElement
1354
 * @private
1355
 */
1356
YAHOO.widget.AutoComplete.prototype._elList = null;
1357
1358
/*
1359
 * Array of &lt;li&gt; elements references that contain query results within the
1360
 * results container.
1361
 *
1362
 * @property _aListItemEls
1363
 * @type HTMLElement[]
1364
 * @private
1365
 */
1366
//YAHOO.widget.AutoComplete.prototype._aListItemEls = null;
1367
1368
/**
1369
 * Number of &lt;li&gt; elements currently displayed in results container.
1370
 *
1371
 * @property _nDisplayedItems
1372
 * @type Number
1373
 * @private
1374
 */
1375
YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
1376
1377
/*
1378
 * Internal count of &lt;li&gt; elements displayed and hidden in results container.
1379
 *
1380
 * @property _maxResultsDisplayed
1381
 * @type Number
1382
 * @private
1383
 */
1384
//YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
1385
1386
/**
1387
 * Current query string
1388
 *
1389
 * @property _sCurQuery
1390
 * @type String
1391
 * @private
1392
 */
1393
YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
1394
1395
/**
1396
 * Selections from previous queries (for saving delimited queries).
1397
 *
1398
 * @property _sPastSelections
1399
 * @type String
1400
 * @default "" 
1401
 * @private
1402
 */
1403
YAHOO.widget.AutoComplete.prototype._sPastSelections = "";
1404
1405
/**
1406
 * Stores initial input value used to determine if textboxChangeEvent should be fired.
1407
 *
1408
 * @property _sInitInputValue
1409
 * @type String
1410
 * @private
1411
 */
1412
YAHOO.widget.AutoComplete.prototype._sInitInputValue = null;
1413
1414
/**
1415
 * Pointer to the currently highlighted &lt;li&gt; element in the container.
1416
 *
1417
 * @property _elCurListItem
1418
 * @type HTMLElement
1419
 * @private
1420
 */
1421
YAHOO.widget.AutoComplete.prototype._elCurListItem = null;
1422
1423
/**
1424
 * Pointer to the currently pre-highlighted &lt;li&gt; element in the container.
1425
 *
1426
 * @property _elCurPrehighlightItem
1427
 * @type HTMLElement
1428
 * @private
1429
 */
1430
YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem = null;
1431
1432
/**
1433
 * Whether or not an item has been selected since the container was populated
1434
 * with results. Reset to false by _populateList, and set to true when item is
1435
 * selected.
1436
 *
1437
 * @property _bItemSelected
1438
 * @type Boolean
1439
 * @private
1440
 */
1441
YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
1442
1443
/**
1444
 * Key code of the last key pressed in textbox.
1445
 *
1446
 * @property _nKeyCode
1447
 * @type Number
1448
 * @private
1449
 */
1450
YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
1451
1452
/**
1453
 * Delay timeout ID.
1454
 *
1455
 * @property _nDelayID
1456
 * @type Number
1457
 * @private
1458
 */
1459
YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
1460
1461
/**
1462
 * TypeAhead delay timeout ID.
1463
 *
1464
 * @property _nTypeAheadDelayID
1465
 * @type Number
1466
 * @private
1467
 */
1468
YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID = -1;
1469
1470
/**
1471
 * Src to iFrame used when useIFrame = true. Supports implementations over SSL
1472
 * as well.
1473
 *
1474
 * @property _iFrameSrc
1475
 * @type String
1476
 * @private
1477
 */
1478
YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
1479
1480
/**
1481
 * For users typing via certain IMEs, queries must be triggered by intervals,
1482
 * since key events yet supported across all browsers for all IMEs.
1483
 *
1484
 * @property _queryInterval
1485
 * @type Object
1486
 * @private
1487
 */
1488
YAHOO.widget.AutoComplete.prototype._queryInterval = null;
1489
1490
/**
1491
 * Internal tracker to last known textbox value, used to determine whether or not
1492
 * to trigger a query via interval for certain IME users.
1493
 *
1494
 * @event _sLastTextboxValue
1495
 * @type String
1496
 * @private
1497
 */
1498
YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
1499
1500
/////////////////////////////////////////////////////////////////////////////
1501
//
1502
// Private methods
1503
//
1504
/////////////////////////////////////////////////////////////////////////////
1505
1506
/**
1507
 * Updates and validates latest public config properties.
1508
 *
1509
 * @method __initProps
1510
 * @private
1511
 */
1512
YAHOO.widget.AutoComplete.prototype._initProps = function() {
1513
    // Correct any invalid values
1514
    var minQueryLength = this.minQueryLength;
1515
    if(!YAHOO.lang.isNumber(minQueryLength)) {
1516
        this.minQueryLength = 1;
1517
    }
1518
    var maxResultsDisplayed = this.maxResultsDisplayed;
1519
    if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
1520
        this.maxResultsDisplayed = 10;
1521
    }
1522
    var queryDelay = this.queryDelay;
1523
    if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
1524
        this.queryDelay = 0.2;
1525
    }
1526
    var typeAheadDelay = this.typeAheadDelay;
1527
    if(!YAHOO.lang.isNumber(typeAheadDelay) || (typeAheadDelay < 0)) {
1528
        this.typeAheadDelay = 0.2;
1529
    }
1530
    var delimChar = this.delimChar;
1531
    if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
1532
        this.delimChar = [delimChar];
1533
    }
1534
    else if(!YAHOO.lang.isArray(delimChar)) {
1535
        this.delimChar = null;
1536
    }
1537
    var animSpeed = this.animSpeed;
1538
    if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
1539
        if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
1540
            this.animSpeed = 0.3;
1541
        }
1542
        if(!this._oAnim ) {
1543
            this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
1544
        }
1545
        else {
1546
            this._oAnim.duration = this.animSpeed;
1547
        }
1548
    }
1549
    if(this.forceSelection && delimChar) {
1550
        YAHOO.log("The forceSelection feature has been enabled with delimChar defined.","warn", this.toString());
1551
    }
1552
};
1553
1554
/**
1555
 * Initializes the results container helpers if they are enabled and do
1556
 * not exist
1557
 *
1558
 * @method _initContainerHelperEls
1559
 * @private
1560
 */
1561
YAHOO.widget.AutoComplete.prototype._initContainerHelperEls = function() {
1562
    if(this.useShadow && !this._elShadow) {
1563
        var elShadow = document.createElement("div");
1564
        elShadow.className = "yui-ac-shadow";
1565
        elShadow.style.width = 0;
1566
        elShadow.style.height = 0;
1567
        this._elShadow = this._elContainer.appendChild(elShadow);
1568
    }
1569
    if(this.useIFrame && !this._elIFrame) {
1570
        var elIFrame = document.createElement("iframe");
1571
        elIFrame.src = this._iFrameSrc;
1572
        elIFrame.frameBorder = 0;
1573
        elIFrame.scrolling = "no";
1574
        elIFrame.style.position = "absolute";
1575
        elIFrame.style.width = 0;
1576
        elIFrame.style.height = 0;
1577
        elIFrame.style.padding = 0;
1578
        elIFrame.tabIndex = -1;
1579
        elIFrame.role = "presentation";
1580
        elIFrame.title = "Presentational iframe shim";
1581
        this._elIFrame = this._elContainer.appendChild(elIFrame);
1582
    }
1583
};
1584
1585
/**
1586
 * Initializes the results container once at object creation
1587
 *
1588
 * @method _initContainerEl
1589
 * @private
1590
 */
1591
YAHOO.widget.AutoComplete.prototype._initContainerEl = function() {
1592
    YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
1593
    
1594
    if(!this._elContent) {
1595
        // The elContent div is assigned DOM listeners and 
1596
        // helps size the iframe and shadow properly
1597
        var elContent = document.createElement("div");
1598
        elContent.className = "yui-ac-content";
1599
        elContent.style.display = "none";
1600
1601
        this._elContent = this._elContainer.appendChild(elContent);
1602
1603
        var elHeader = document.createElement("div");
1604
        elHeader.className = "yui-ac-hd";
1605
        elHeader.style.display = "none";
1606
        this._elHeader = this._elContent.appendChild(elHeader);
1607
1608
        var elBody = document.createElement("div");
1609
        elBody.className = "yui-ac-bd";
1610
        this._elBody = this._elContent.appendChild(elBody);
1611
1612
        var elFooter = document.createElement("div");
1613
        elFooter.className = "yui-ac-ft";
1614
        elFooter.style.display = "none";
1615
        this._elFooter = this._elContent.appendChild(elFooter);
1616
    }
1617
    else {
1618
        YAHOO.log("Could not initialize the container","warn",this.toString());
1619
    }
1620
};
1621
1622
/**
1623
 * Clears out contents of container body and creates up to
1624
 * YAHOO.widget.AutoComplete#maxResultsDisplayed &lt;li&gt; elements in an
1625
 * &lt;ul&gt; element.
1626
 *
1627
 * @method _initListEl
1628
 * @private
1629
 */
1630
YAHOO.widget.AutoComplete.prototype._initListEl = function() {
1631
    var nListLength = this.maxResultsDisplayed,
1632
        elList = this._elList || document.createElement("ul"),
1633
        elListItem;
1634
    
1635
    while(elList.childNodes.length < nListLength) {
1636
        elListItem = document.createElement("li");
1637
        elListItem.style.display = "none";
1638
        elListItem._nItemIndex = elList.childNodes.length;
1639
        elList.appendChild(elListItem);
1640
    }
1641
    if(!this._elList) {
1642
        var elBody = this._elBody;
1643
        YAHOO.util.Event.purgeElement(elBody, true);
1644
        elBody.innerHTML = "";
1645
        this._elList = elBody.appendChild(elList);
1646
    }
1647
    
1648
    this._elBody.style.display = "";
1649
};
1650
1651
/**
1652
 * Focuses input field.
1653
 *
1654
 * @method _focus
1655
 * @private
1656
 */
1657
YAHOO.widget.AutoComplete.prototype._focus = function() {
1658
    // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
1659
    var oSelf = this;
1660
    setTimeout(function() {
1661
        try {
1662
            oSelf._elTextbox.focus();
1663
        }
1664
        catch(e) {
1665
        }
1666
    },0);
1667
};
1668
1669
/**
1670
 * Enables interval detection for IME support.
1671
 *
1672
 * @method _enableIntervalDetection
1673
 * @private
1674
 */
1675
YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() {
1676
    var oSelf = this;
1677
    if(!oSelf._queryInterval && oSelf.queryInterval) {
1678
        oSelf._queryInterval = setInterval(function() { oSelf._onInterval(); }, oSelf.queryInterval);
1679
        YAHOO.log("Interval set", "info", this.toString());
1680
    }
1681
};
1682
1683
/**
1684
 * Enables interval detection for a less performant but brute force mechanism to
1685
 * detect input values at an interval set by queryInterval and send queries if
1686
 * input value has changed. Needed to support right-click+paste or shift+insert
1687
 * edge cases. Please note that intervals are cleared at the end of each interaction,
1688
 * so enableIntervalDetection must be called for each new interaction. The
1689
 * recommended approach is to call it in response to textboxFocusEvent.
1690
 *
1691
 * @method enableIntervalDetection
1692
 */
1693
YAHOO.widget.AutoComplete.prototype.enableIntervalDetection =
1694
    YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;
1695
1696
/**
1697
 * Enables query triggers based on text input detection by intervals (rather
1698
 * than by key events).
1699
 *
1700
 * @method _onInterval
1701
 * @private
1702
 */
1703
YAHOO.widget.AutoComplete.prototype._onInterval = function() {
1704
    var currValue = this._elTextbox.value;
1705
    var lastValue = this._sLastTextboxValue;
1706
    if(currValue != lastValue) {
1707
        this._sLastTextboxValue = currValue;
1708
        this._sendQuery(currValue);
1709
    }
1710
};
1711
1712
/**
1713
 * Cancels text input detection by intervals.
1714
 *
1715
 * @method _clearInterval
1716
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1717
 * @private
1718
 */
1719
YAHOO.widget.AutoComplete.prototype._clearInterval = function() {
1720
    if(this._queryInterval) {
1721
        clearInterval(this._queryInterval);
1722
        this._queryInterval = null;
1723
        YAHOO.log("Interval cleared", "info", this.toString());
1724
    }
1725
};
1726
1727
/**
1728
 * Whether or not key is functional or should be ignored. Note that the right
1729
 * arrow key is NOT an ignored key since it triggers queries for certain intl
1730
 * charsets.
1731
 *
1732
 * @method _isIgnoreKey
1733
 * @param nKeycode {Number} Code of key pressed.
1734
 * @return {Boolean} True if key should be ignored, false otherwise.
1735
 * @private
1736
 */
1737
YAHOO.widget.AutoComplete.prototype._isIgnoreKey = function(nKeyCode) {
1738
    if((nKeyCode == 9) || (nKeyCode == 13)  || // tab, enter
1739
            (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl
1740
            (nKeyCode >= 18 && nKeyCode <= 20) || // alt, pause/break,caps lock
1741
            (nKeyCode == 27) || // esc
1742
            (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
1743
            /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
1744
            (nKeyCode == 40) || // down*/
1745
            (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
1746
            (nKeyCode >= 44 && nKeyCode <= 45) || // print screen,insert
1747
            (nKeyCode == 229) // Bug 2041973: Korean XP fires 2 keyup events, the key and 229
1748
        ) { 
1749
        return true;
1750
    }
1751
    return false;
1752
};
1753
1754
/**
1755
 * Makes query request to the DataSource.
1756
 *
1757
 * @method _sendQuery
1758
 * @param sQuery {String} Query string.
1759
 * @private
1760
 */
1761
YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
1762
    // Widget has been effectively turned off
1763
    if(this.minQueryLength < 0) {
1764
        this._toggleContainer(false);
1765
        YAHOO.log("Property minQueryLength is less than 0", "info", this.toString());
1766
        return;
1767
    }
1768
    // Delimiter has been enabled
1769
    if(this.delimChar) {
1770
        var extraction = this._extractQuery(sQuery);
1771
        // Here is the query itself
1772
        sQuery = extraction.query;
1773
        // ...and save the rest of the string for later
1774
        this._sPastSelections = extraction.previous;
1775
    }
1776
1777
    // Don't search queries that are too short
1778
    if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
1779
        if(this._nDelayID != -1) {
1780
            clearTimeout(this._nDelayID);
1781
        }
1782
        this._toggleContainer(false);
1783
        YAHOO.log("Query \"" + sQuery + "\" is too short", "info", this.toString());
1784
        return;
1785
    }
1786
1787
    sQuery = encodeURIComponent(sQuery);
1788
    this._nDelayID = -1;    // Reset timeout ID because request is being made
1789
    
1790
    // Subset matching
1791
    if(this.dataSource.queryMatchSubset || this.queryMatchSubset) { // backward compat
1792
        var oResponse = this.getSubsetMatches(sQuery);
1793
        if(oResponse) {
1794
            this.handleResponse(sQuery, oResponse, {query: sQuery});
1795
            return;
1796
        }
1797
    }
1798
    
1799
    if(this.dataSource.responseStripAfter) {
1800
        this.dataSource.doBeforeParseData = this.preparseRawResponse;
1801
    }
1802
    if(this.applyLocalFilter) {
1803
        this.dataSource.doBeforeCallback = this.filterResults;
1804
    }
1805
    
1806
    var sRequest = this.generateRequest(sQuery);
1807
    this.dataRequestEvent.fire(this, sQuery, sRequest);
1808
    YAHOO.log("Sending query \"" + sRequest + "\"", "info", this.toString());
1809
1810
    this.dataSource.sendRequest(sRequest, {
1811
            success : this.handleResponse,
1812
            failure : this.handleResponse,
1813
            scope   : this,
1814
            argument: {
1815
                query: sQuery
1816
            }
1817
    });
1818
};
1819
1820
/**
1821
 * Populates the given &lt;li&gt; element with return value from formatResult().
1822
 *
1823
 * @method _populateListItem
1824
 * @param elListItem {HTMLElement} The LI element.
1825
 * @param oResult {Object} The result object.
1826
 * @param sCurQuery {String} The query string.
1827
 * @private
1828
 */
1829
YAHOO.widget.AutoComplete.prototype._populateListItem = function(elListItem, oResult, sQuery) {
1830
    elListItem.innerHTML = this.formatResult(oResult, sQuery, elListItem._sResultMatch);
1831
};
1832
1833
/**
1834
 * Populates the array of &lt;li&gt; elements in the container with query
1835
 * results.
1836
 *
1837
 * @method _populateList
1838
 * @param sQuery {String} Original request.
1839
 * @param oResponse {Object} Response object.
1840
 * @param oPayload {MIXED} (optional) Additional argument(s)
1841
 * @private
1842
 */
1843
YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, oResponse, oPayload) {
1844
    // Clear previous timeout
1845
    if(this._nTypeAheadDelayID != -1) {
1846
        clearTimeout(this._nTypeAheadDelayID);
1847
    }
1848
        
1849
    sQuery = (oPayload && oPayload.query) ? oPayload.query : sQuery;
1850
    
1851
    // Pass data through abstract method for any transformations
1852
    var ok = this.doBeforeLoadData(sQuery, oResponse, oPayload);
1853
1854
    // Data is ok
1855
    if(ok && !oResponse.error) {
1856
        this.dataReturnEvent.fire(this, sQuery, oResponse.results);
1857
        
1858
        // Continue only if instance is still active (i.e., user hasn't already moved on)
1859
        if(this._bFocused) {
1860
            // Store state for this interaction
1861
            var sCurQuery = decodeURIComponent(sQuery);
1862
            this._sCurQuery = sCurQuery;
1863
            this._bItemSelected = false;
1864
        
1865
            var allResults = oResponse.results,
1866
                nItemsToShow = Math.min(allResults.length,this.maxResultsDisplayed),
1867
                sMatchKey = (this.dataSource.responseSchema.fields) ? 
1868
                    (this.dataSource.responseSchema.fields[0].key || this.dataSource.responseSchema.fields[0]) : 0;
1869
            
1870
            if(nItemsToShow > 0) {
1871
                // Make sure container and helpers are ready to go
1872
                if(!this._elList || (this._elList.childNodes.length < nItemsToShow)) {
1873
                    this._initListEl();
1874
                }
1875
                this._initContainerHelperEls();
1876
                
1877
                var allListItemEls = this._elList.childNodes;
1878
                // Fill items with data from the bottom up
1879
                for(var i = nItemsToShow-1; i >= 0; i--) {
1880
                    var elListItem = allListItemEls[i],
1881
                    oResult = allResults[i];
1882
                    
1883
                    // Backward compatibility
1884
                    if(this.resultTypeList) {
1885
                        // Results need to be converted back to an array
1886
                        var aResult = [];
1887
                        // Match key is first
1888
                        aResult[0] = (YAHOO.lang.isString(oResult)) ? oResult : oResult[sMatchKey] || oResult[this.key];
1889
                        // Add additional data to the result array
1890
                        var fields = this.dataSource.responseSchema.fields;
1891
                        if(YAHOO.lang.isArray(fields) && (fields.length > 1)) {
1892
                            for(var k=1, len=fields.length; k<len; k++) {
1893
                                aResult[aResult.length] = oResult[fields[k].key || fields[k]];
1894
                            }
1895
                        }
1896
                        // No specific fields defined, so pass along entire data object
1897
                        else {
1898
                            // Already an array
1899
                            if(YAHOO.lang.isArray(oResult)) {
1900
                                aResult = oResult;
1901
                            }
1902
                            // Simple string 
1903
                            else if(YAHOO.lang.isString(oResult)) {
1904
                                aResult = [oResult];
1905
                            }
1906
                            // Object
1907
                            else {
1908
                                aResult[1] = oResult;
1909
                            }
1910
                        }
1911
                        oResult = aResult;
1912
                    }
1913
1914
                    // The matching value, including backward compatibility for array format and safety net
1915
                    elListItem._sResultMatch = (YAHOO.lang.isString(oResult)) ? oResult : (YAHOO.lang.isArray(oResult)) ? oResult[0] : (oResult[sMatchKey] || "");
1916
                    elListItem._oResultData = oResult; // Additional data
1917
                    this._populateListItem(elListItem, oResult, sCurQuery);
1918
                    elListItem.style.display = "";
1919
                }
1920
        
1921
                // Clear out extraneous items
1922
                if(nItemsToShow < allListItemEls.length) {
1923
                    var extraListItem;
1924
                    for(var j = allListItemEls.length-1; j >= nItemsToShow; j--) {
1925
                        extraListItem = allListItemEls[j];
1926
                        extraListItem.style.display = "none";
1927
                    }
1928
                }
1929
                
1930
                this._nDisplayedItems = nItemsToShow;
1931
                
1932
                this.containerPopulateEvent.fire(this, sQuery, allResults);
1933
                
1934
                // Highlight the first item
1935
                if(this.autoHighlight) {
1936
                    var elFirstListItem = this._elList.firstChild;
1937
                    this._toggleHighlight(elFirstListItem,"to");
1938
                    this.itemArrowToEvent.fire(this, elFirstListItem);
1939
                    YAHOO.log("Arrowed to first item", "info", this.toString());
1940
                    this._typeAhead(elFirstListItem,sQuery);
1941
                }
1942
                // Unhighlight any previous time
1943
                else {
1944
                    this._toggleHighlight(this._elCurListItem,"from");
1945
                }
1946
        
1947
                // Pre-expansion stuff
1948
                ok = this._doBeforeExpandContainer(this._elTextbox, this._elContainer, sQuery, allResults);
1949
                
1950
                // Expand the container
1951
                this._toggleContainer(ok);
1952
            }
1953
            else {
1954
                this._toggleContainer(false);
1955
            }
1956
1957
            YAHOO.log("Container populated with " + nItemsToShow +  " list items", "info", this.toString());
1958
            return;
1959
        }
1960
    }
1961
    // Error
1962
    else {
1963
        this.dataErrorEvent.fire(this, sQuery, oResponse);
1964
    }
1965
        
1966
    YAHOO.log("Could not populate list", "info", this.toString());    
1967
};
1968
1969
/**
1970
 * Called before container expands, by default snaps container to the
1971
 * bottom-left corner of the input element, then calls public overrideable method.
1972
 *
1973
 * @method _doBeforeExpandContainer
1974
 * @param elTextbox {HTMLElement} The text input box.
1975
 * @param elContainer {HTMLElement} The container element.
1976
 * @param sQuery {String} The query string.
1977
 * @param aResults {Object[]}  An array of query results.
1978
 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
1979
 * @private 
1980
 */
1981
YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
1982
    if(this.autoSnapContainer) {
1983
        this.snapContainer();
1984
    }
1985
1986
    return this.doBeforeExpandContainer(elTextbox, elContainer, sQuery, aResults);
1987
};
1988
1989
/**
1990
 * When forceSelection is true and the user attempts
1991
 * leave the text input box without selecting an item from the query results,
1992
 * the user selection is cleared.
1993
 *
1994
 * @method _clearSelection
1995
 * @private
1996
 */
1997
YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
1998
    var extraction = (this.delimChar) ? this._extractQuery(this._elTextbox.value) :
1999
            {previous:"",query:this._elTextbox.value};
2000
    this._elTextbox.value = extraction.previous;
2001
    this.selectionEnforceEvent.fire(this, extraction.query);
2002
    YAHOO.log("Selection enforced", "info", this.toString());
2003
};
2004
2005
/**
2006
 * Whether or not user-typed value in the text input box matches any of the
2007
 * query results.
2008
 *
2009
 * @method _textMatchesOption
2010
 * @return {HTMLElement} Matching list item element if user-input text matches
2011
 * a result, null otherwise.
2012
 * @private
2013
 */
2014
YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
2015
    var elMatch = null;
2016
2017
    for(var i=0; i<this._nDisplayedItems; i++) {
2018
        var elListItem = this._elList.childNodes[i];
2019
        var sMatch = ("" + elListItem._sResultMatch).toLowerCase();
2020
        if(sMatch == this._sCurQuery.toLowerCase()) {
2021
            elMatch = elListItem;
2022
            break;
2023
        }
2024
    }
2025
    return(elMatch);
2026
};
2027
2028
/**
2029
 * Updates in the text input box with the first query result as the user types,
2030
 * selecting the substring that the user has not typed.
2031
 *
2032
 * @method _typeAhead
2033
 * @param elListItem {HTMLElement} The &lt;li&gt; element item whose data populates the input field.
2034
 * @param sQuery {String} Query string.
2035
 * @private
2036
 */
2037
YAHOO.widget.AutoComplete.prototype._typeAhead = function(elListItem, sQuery) {
2038
    // Don't typeAhead if turned off or is backspace
2039
    if(!this.typeAhead || (this._nKeyCode == 8)) {
2040
        return;
2041
    }
2042
2043
    var oSelf = this,
2044
        elTextbox = this._elTextbox;
2045
        
2046
    // Only if text selection is supported
2047
    if(elTextbox.setSelectionRange || elTextbox.createTextRange) {
2048
        // Set and store timeout for this typeahead
2049
        this._nTypeAheadDelayID = setTimeout(function() {
2050
                // Select the portion of text that the user has not typed
2051
                var nStart = elTextbox.value.length; // any saved queries plus what user has typed
2052
                oSelf._updateValue(elListItem);
2053
                var nEnd = elTextbox.value.length;
2054
                oSelf._selectText(elTextbox,nStart,nEnd);
2055
                var sPrefill = elTextbox.value.substr(nStart,nEnd);
2056
                oSelf.typeAheadEvent.fire(oSelf,sQuery,sPrefill);
2057
                YAHOO.log("Typeahead occured with prefill string \"" + sPrefill + "\"", "info", oSelf.toString());
2058
            },(this.typeAheadDelay*1000));            
2059
    }
2060
};
2061
2062
/**
2063
 * Selects text in the input field.
2064
 *
2065
 * @method _selectText
2066
 * @param elTextbox {HTMLElement} Text input box element in which to select text.
2067
 * @param nStart {Number} Starting index of text string to select.
2068
 * @param nEnd {Number} Ending index of text selection.
2069
 * @private
2070
 */
2071
YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
2072
    if(elTextbox.setSelectionRange) { // For Mozilla
2073
        elTextbox.setSelectionRange(nStart,nEnd);
2074
    }
2075
    else if(elTextbox.createTextRange) { // For IE
2076
        var oTextRange = elTextbox.createTextRange();
2077
        oTextRange.moveStart("character", nStart);
2078
        oTextRange.moveEnd("character", nEnd-elTextbox.value.length);
2079
        oTextRange.select();
2080
    }
2081
    else {
2082
        elTextbox.select();
2083
    }
2084
};
2085
2086
/**
2087
 * Extracts rightmost query from delimited string.
2088
 *
2089
 * @method _extractQuery
2090
 * @param sQuery {String} String to parse
2091
 * @return {Object} Object literal containing properties "query" and "previous".  
2092
 * @private
2093
 */
2094
YAHOO.widget.AutoComplete.prototype._extractQuery = function(sQuery) {
2095
    var aDelimChar = this.delimChar,
2096
        nDelimIndex = -1,
2097
        nNewIndex, nQueryStart,
2098
        i = aDelimChar.length-1,
2099
        sPrevious;
2100
        
2101
    // Loop through all possible delimiters and find the rightmost one in the query
2102
    // A " " may be a false positive if they are defined as delimiters AND
2103
    // are used to separate delimited queries
2104
    for(; i >= 0; i--) {
2105
        nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
2106
        if(nNewIndex > nDelimIndex) {
2107
            nDelimIndex = nNewIndex;
2108
        }
2109
    }
2110
    // If we think the last delimiter is a space (" "), make sure it is NOT
2111
    // a false positive by also checking the char directly before it
2112
    if(aDelimChar[i] == " ") {
2113
        for (var j = aDelimChar.length-1; j >= 0; j--) {
2114
            if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
2115
                nDelimIndex--;
2116
                break;
2117
            }
2118
        }
2119
    }
2120
    // A delimiter has been found in the query so extract the latest query from past selections
2121
    if(nDelimIndex > -1) {
2122
        nQueryStart = nDelimIndex + 1;
2123
        // Trim any white space from the beginning...
2124
        while(sQuery.charAt(nQueryStart) == " ") {
2125
            nQueryStart += 1;
2126
        }
2127
        // ...and save the rest of the string for later
2128
        sPrevious = sQuery.substring(0,nQueryStart);
2129
        // Here is the query itself
2130
        sQuery = sQuery.substr(nQueryStart);
2131
    }
2132
    // No delimiter found in the query, so there are no selections from past queries
2133
    else {
2134
        sPrevious = "";
2135
    }
2136
    
2137
    return {
2138
        previous: sPrevious,
2139
        query: sQuery
2140
    };
2141
};
2142
2143
/**
2144
 * Syncs results container with its helpers.
2145
 *
2146
 * @method _toggleContainerHelpers
2147
 * @param bShow {Boolean} True if container is expanded, false if collapsed
2148
 * @private
2149
 */
2150
YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
2151
    var width = this._elContent.offsetWidth + "px";
2152
    var height = this._elContent.offsetHeight + "px";
2153
2154
    if(this.useIFrame && this._elIFrame) {
2155
    var elIFrame = this._elIFrame;
2156
        if(bShow) {
2157
            elIFrame.style.width = width;
2158
            elIFrame.style.height = height;
2159
            elIFrame.style.padding = "";
2160
            YAHOO.log("Iframe expanded", "info", this.toString());
2161
        }
2162
        else {
2163
            elIFrame.style.width = 0;
2164
            elIFrame.style.height = 0;
2165
            elIFrame.style.padding = 0;
2166
            YAHOO.log("Iframe collapsed", "info", this.toString());
2167
        }
2168
    }
2169
    if(this.useShadow && this._elShadow) {
2170
    var elShadow = this._elShadow;
2171
        if(bShow) {
2172
            elShadow.style.width = width;
2173
            elShadow.style.height = height;
2174
            YAHOO.log("Shadow expanded", "info", this.toString());
2175
        }
2176
        else {
2177
            elShadow.style.width = 0;
2178
            elShadow.style.height = 0;
2179
            YAHOO.log("Shadow collapsed", "info", this.toString());
2180
        }
2181
    }
2182
};
2183
2184
/**
2185
 * Animates expansion or collapse of the container.
2186
 *
2187
 * @method _toggleContainer
2188
 * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
2189
 * @private
2190
 */
2191
YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
2192
    YAHOO.log("Toggling container " + ((bShow) ? "open" : "closed"), "info", this.toString());
2193
2194
    var elContainer = this._elContainer;
2195
2196
    // If implementer has container always open and it's already open, don't mess with it
2197
    // Container is initialized with display "none" so it may need to be shown first time through
2198
    if(this.alwaysShowContainer && this._bContainerOpen) {
2199
        return;
2200
    }
2201
    
2202
    // Reset states
2203
    if(!bShow) {
2204
        this._toggleHighlight(this._elCurListItem,"from");
2205
        this._nDisplayedItems = 0;
2206
        this._sCurQuery = null;
2207
        
2208
        // Container is already closed, so don't bother with changing the UI
2209
        if(this._elContent.style.display == "none") {
2210
            return;
2211
        }
2212
    }
2213
2214
    // If animation is enabled...
2215
    var oAnim = this._oAnim;
2216
    if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
2217
        if(oAnim.isAnimated()) {
2218
            oAnim.stop(true);
2219
        }
2220
2221
        // Clone container to grab current size offscreen
2222
        var oClone = this._elContent.cloneNode(true);
2223
        elContainer.appendChild(oClone);
2224
        oClone.style.top = "-9000px";
2225
        oClone.style.width = "";
2226
        oClone.style.height = "";
2227
        oClone.style.display = "";
2228
2229
        // Current size of the container is the EXPANDED size
2230
        var wExp = oClone.offsetWidth;
2231
        var hExp = oClone.offsetHeight;
2232
2233
        // Calculate COLLAPSED sizes based on horiz and vert anim
2234
        var wColl = (this.animHoriz) ? 0 : wExp;
2235
        var hColl = (this.animVert) ? 0 : hExp;
2236
2237
        // Set animation sizes
2238
        oAnim.attributes = (bShow) ?
2239
            {width: { to: wExp }, height: { to: hExp }} :
2240
            {width: { to: wColl}, height: { to: hColl }};
2241
2242
        // If opening anew, set to a collapsed size...
2243
        if(bShow && !this._bContainerOpen) {
2244
            this._elContent.style.width = wColl+"px";
2245
            this._elContent.style.height = hColl+"px";
2246
        }
2247
        // Else, set it to its last known size.
2248
        else {
2249
            this._elContent.style.width = wExp+"px";
2250
            this._elContent.style.height = hExp+"px";
2251
        }
2252
2253
        elContainer.removeChild(oClone);
2254
        oClone = null;
2255
2256
    	var oSelf = this;
2257
    	var onAnimComplete = function() {
2258
            // Finish the collapse
2259
    		oAnim.onComplete.unsubscribeAll();
2260
2261
            if(bShow) {
2262
                oSelf._toggleContainerHelpers(true);
2263
                oSelf._bContainerOpen = bShow;
2264
                oSelf.containerExpandEvent.fire(oSelf);
2265
                YAHOO.log("Container expanded", "info", oSelf.toString());
2266
            }
2267
            else {
2268
                oSelf._elContent.style.display = "none";
2269
                oSelf._bContainerOpen = bShow;
2270
                oSelf.containerCollapseEvent.fire(oSelf);
2271
                YAHOO.log("Container collapsed", "info", oSelf.toString());
2272
            }
2273
     	};
2274
2275
        // Display container and animate it
2276
        this._toggleContainerHelpers(false); // Bug 1424486: Be early to hide, late to show;
2277
        this._elContent.style.display = "";
2278
        oAnim.onComplete.subscribe(onAnimComplete);
2279
        oAnim.animate();
2280
    }
2281
    // Else don't animate, just show or hide
2282
    else {
2283
        if(bShow) {
2284
            this._elContent.style.display = "";
2285
            this._toggleContainerHelpers(true);
2286
            this._bContainerOpen = bShow;
2287
            this.containerExpandEvent.fire(this);
2288
            YAHOO.log("Container expanded", "info", this.toString());
2289
        }
2290
        else {
2291
            this._toggleContainerHelpers(false);
2292
            this._elContent.style.display = "none";
2293
            this._bContainerOpen = bShow;
2294
            this.containerCollapseEvent.fire(this);
2295
            YAHOO.log("Container collapsed", "info", this.toString());
2296
        }
2297
   }
2298
2299
};
2300
2301
/**
2302
 * Toggles the highlight on or off for an item in the container, and also cleans
2303
 * up highlighting of any previous item.
2304
 *
2305
 * @method _toggleHighlight
2306
 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2307
 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2308
 * @private
2309
 */
2310
YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(elNewListItem, sType) {
2311
    if(elNewListItem) {
2312
        var sHighlight = this.highlightClassName;
2313
        if(this._elCurListItem) {
2314
            // Remove highlight from old item
2315
            YAHOO.util.Dom.removeClass(this._elCurListItem, sHighlight);
2316
            this._elCurListItem = null;
2317
        }
2318
    
2319
        if((sType == "to") && sHighlight) {
2320
            // Apply highlight to new item
2321
            YAHOO.util.Dom.addClass(elNewListItem, sHighlight);
2322
            this._elCurListItem = elNewListItem;
2323
        }
2324
    }
2325
};
2326
2327
/**
2328
 * Toggles the pre-highlight on or off for an item in the container, and also cleans
2329
 * up pre-highlighting of any previous item.
2330
 *
2331
 * @method _togglePrehighlight
2332
 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2333
 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2334
 * @private
2335
 */
2336
YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(elNewListItem, sType) {
2337
    var sPrehighlight = this.prehighlightClassName;
2338
2339
    if(this._elCurPrehighlightItem) {
2340
        YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem, sPrehighlight);
2341
    }
2342
    if(elNewListItem == this._elCurListItem) {
2343
        return;
2344
    }
2345
2346
    if((sType == "mouseover") && sPrehighlight) {
2347
        // Apply prehighlight to new item
2348
        YAHOO.util.Dom.addClass(elNewListItem, sPrehighlight);
2349
        this._elCurPrehighlightItem = elNewListItem;
2350
    }
2351
    else {
2352
        // Remove prehighlight from old item
2353
        YAHOO.util.Dom.removeClass(elNewListItem, sPrehighlight);
2354
    }
2355
};
2356
2357
/**
2358
 * Updates the text input box value with selected query result. If a delimiter
2359
 * has been defined, then the value gets appended with the delimiter.
2360
 *
2361
 * @method _updateValue
2362
 * @param elListItem {HTMLElement} The &lt;li&gt; element item with which to update the value.
2363
 * @private
2364
 */
2365
YAHOO.widget.AutoComplete.prototype._updateValue = function(elListItem) {
2366
    if(!this.suppressInputUpdate) {    
2367
        var elTextbox = this._elTextbox;
2368
        var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
2369
        var sResultMatch = elListItem._sResultMatch;
2370
    
2371
        // Calculate the new value
2372
        var sNewValue = "";
2373
        if(sDelimChar) {
2374
            // Preserve selections from past queries
2375
            sNewValue = this._sPastSelections;
2376
            // Add new selection plus delimiter
2377
            sNewValue += sResultMatch + sDelimChar;
2378
            if(sDelimChar != " ") {
2379
                sNewValue += " ";
2380
            }
2381
        }
2382
        else { 
2383
            sNewValue = sResultMatch;
2384
        }
2385
        
2386
        // Update input field
2387
        elTextbox.value = sNewValue;
2388
    
2389
        // Scroll to bottom of textarea if necessary
2390
        if(elTextbox.type == "textarea") {
2391
            elTextbox.scrollTop = elTextbox.scrollHeight;
2392
        }
2393
    
2394
        // Move cursor to end
2395
        var end = elTextbox.value.length;
2396
        this._selectText(elTextbox,end,end);
2397
    
2398
        this._elCurListItem = elListItem;
2399
    }
2400
};
2401
2402
/**
2403
 * Selects a result item from the container
2404
 *
2405
 * @method _selectItem
2406
 * @param elListItem {HTMLElement} The selected &lt;li&gt; element item.
2407
 * @private
2408
 */
2409
YAHOO.widget.AutoComplete.prototype._selectItem = function(elListItem) {
2410
    this._bItemSelected = true;
2411
    this._updateValue(elListItem);
2412
    this._sPastSelections = this._elTextbox.value;
2413
    this._clearInterval();
2414
    this.itemSelectEvent.fire(this, elListItem, elListItem._oResultData);
2415
    YAHOO.log("Item selected: " + YAHOO.lang.dump(elListItem._oResultData), "info", this.toString());
2416
    this._toggleContainer(false);
2417
};
2418
2419
/**
2420
 * If an item is highlighted in the container, the right arrow key jumps to the
2421
 * end of the textbox and selects the highlighted item, otherwise the container
2422
 * is closed.
2423
 *
2424
 * @method _jumpSelection
2425
 * @private
2426
 */
2427
YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
2428
    if(this._elCurListItem) {
2429
        this._selectItem(this._elCurListItem);
2430
    }
2431
    else {
2432
        this._toggleContainer(false);
2433
    }
2434
};
2435
2436
/**
2437
 * Triggered by up and down arrow keys, changes the current highlighted
2438
 * &lt;li&gt; element item. Scrolls container if necessary.
2439
 *
2440
 * @method _moveSelection
2441
 * @param nKeyCode {Number} Code of key pressed.
2442
 * @private
2443
 */
2444
YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
2445
    if(this._bContainerOpen) {
2446
        // Determine current item's id number
2447
        var elCurListItem = this._elCurListItem,
2448
            nCurItemIndex = -1;
2449
2450
        if(elCurListItem) {
2451
            nCurItemIndex = elCurListItem._nItemIndex;
2452
        }
2453
2454
        var nNewItemIndex = (nKeyCode == 40) ?
2455
                (nCurItemIndex + 1) : (nCurItemIndex - 1);
2456
2457
        // Out of bounds
2458
        if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
2459
            return;
2460
        }
2461
2462
        if(elCurListItem) {
2463
            // Unhighlight current item
2464
            this._toggleHighlight(elCurListItem, "from");
2465
            this.itemArrowFromEvent.fire(this, elCurListItem);
2466
            YAHOO.log("Item arrowed from: " + elCurListItem._nItemIndex, "info", this.toString());
2467
        }
2468
        if(nNewItemIndex == -1) {
2469
           // Go back to query (remove type-ahead string)
2470
            if(this.delimChar) {
2471
                this._elTextbox.value = this._sPastSelections + this._sCurQuery;
2472
            }
2473
            else {
2474
                this._elTextbox.value = this._sCurQuery;
2475
            }
2476
            return;
2477
        }
2478
        if(nNewItemIndex == -2) {
2479
            // Close container
2480
            this._toggleContainer(false);
2481
            return;
2482
        }
2483
        
2484
        var elNewListItem = this._elList.childNodes[nNewItemIndex],
2485
2486
        // Scroll the container if necessary
2487
            elContent = this._elContent,
2488
            sOF = YAHOO.util.Dom.getStyle(elContent,"overflow"),
2489
            sOFY = YAHOO.util.Dom.getStyle(elContent,"overflowY"),
2490
            scrollOn = ((sOF == "auto") || (sOF == "scroll") || (sOFY == "auto") || (sOFY == "scroll"));
2491
        if(scrollOn && (nNewItemIndex > -1) &&
2492
        (nNewItemIndex < this._nDisplayedItems)) {
2493
            // User is keying down
2494
            if(nKeyCode == 40) {
2495
                // Bottom of selected item is below scroll area...
2496
                if((elNewListItem.offsetTop+elNewListItem.offsetHeight) > (elContent.scrollTop + elContent.offsetHeight)) {
2497
                    // Set bottom of scroll area to bottom of selected item
2498
                    elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2499
                }
2500
                // Bottom of selected item is above scroll area...
2501
                else if((elNewListItem.offsetTop+elNewListItem.offsetHeight) < elContent.scrollTop) {
2502
                    // Set top of selected item to top of scroll area
2503
                    elContent.scrollTop = elNewListItem.offsetTop;
2504
2505
                }
2506
            }
2507
            // User is keying up
2508
            else {
2509
                // Top of selected item is above scroll area
2510
                if(elNewListItem.offsetTop < elContent.scrollTop) {
2511
                    // Set top of scroll area to top of selected item
2512
                    this._elContent.scrollTop = elNewListItem.offsetTop;
2513
                }
2514
                // Top of selected item is below scroll area
2515
                else if(elNewListItem.offsetTop > (elContent.scrollTop + elContent.offsetHeight)) {
2516
                    // Set bottom of selected item to bottom of scroll area
2517
                    this._elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2518
                }
2519
            }
2520
        }
2521
2522
        this._toggleHighlight(elNewListItem, "to");
2523
        this.itemArrowToEvent.fire(this, elNewListItem);
2524
        YAHOO.log("Item arrowed to " + elNewListItem._nItemIndex, "info", this.toString());
2525
        if(this.typeAhead) {
2526
            this._updateValue(elNewListItem);
2527
        }
2528
    }
2529
};
2530
2531
/////////////////////////////////////////////////////////////////////////////
2532
//
2533
// Private event handlers
2534
//
2535
/////////////////////////////////////////////////////////////////////////////
2536
2537
/**
2538
 * Handles container mouseover events.
2539
 *
2540
 * @method _onContainerMouseover
2541
 * @param v {HTMLEvent} The mouseover event.
2542
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2543
 * @private
2544
 */
2545
YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
2546
    var elTarget = YAHOO.util.Event.getTarget(v);
2547
    var elTag = elTarget.nodeName.toLowerCase();
2548
    while(elTarget && (elTag != "table")) {
2549
        switch(elTag) {
2550
            case "body":
2551
                return;
2552
            case "li":
2553
                if(oSelf.prehighlightClassName) {
2554
                    oSelf._togglePrehighlight(elTarget,"mouseover");
2555
                }
2556
                else {
2557
                    oSelf._toggleHighlight(elTarget,"to");
2558
                }
2559
            
2560
                oSelf.itemMouseOverEvent.fire(oSelf, elTarget);
2561
                YAHOO.log("Item moused over " + elTarget._nItemIndex, "info", oSelf.toString());
2562
                break;
2563
            case "div":
2564
                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2565
                    oSelf._bOverContainer = true;
2566
                    return;
2567
                }
2568
                break;
2569
            default:
2570
                break;
2571
        }
2572
        
2573
        elTarget = elTarget.parentNode;
2574
        if(elTarget) {
2575
            elTag = elTarget.nodeName.toLowerCase();
2576
        }
2577
    }
2578
};
2579
2580
/**
2581
 * Handles container mouseout events.
2582
 *
2583
 * @method _onContainerMouseout
2584
 * @param v {HTMLEvent} The mouseout event.
2585
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2586
 * @private
2587
 */
2588
YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
2589
    var elTarget = YAHOO.util.Event.getTarget(v);
2590
    var elTag = elTarget.nodeName.toLowerCase();
2591
    while(elTarget && (elTag != "table")) {
2592
        switch(elTag) {
2593
            case "body":
2594
                return;
2595
            case "li":
2596
                if(oSelf.prehighlightClassName) {
2597
                    oSelf._togglePrehighlight(elTarget,"mouseout");
2598
                }
2599
                else {
2600
                    oSelf._toggleHighlight(elTarget,"from");
2601
                }
2602
            
2603
                oSelf.itemMouseOutEvent.fire(oSelf, elTarget);
2604
                YAHOO.log("Item moused out " + elTarget._nItemIndex, "info", oSelf.toString());
2605
                break;
2606
            case "ul":
2607
                oSelf._toggleHighlight(oSelf._elCurListItem,"to");
2608
                break;
2609
            case "div":
2610
                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2611
                    oSelf._bOverContainer = false;
2612
                    return;
2613
                }
2614
                break;
2615
            default:
2616
                break;
2617
        }
2618
2619
        elTarget = elTarget.parentNode;
2620
        if(elTarget) {
2621
            elTag = elTarget.nodeName.toLowerCase();
2622
        }
2623
    }
2624
};
2625
2626
/**
2627
 * Handles container click events.
2628
 *
2629
 * @method _onContainerClick
2630
 * @param v {HTMLEvent} The click event.
2631
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2632
 * @private
2633
 */
2634
YAHOO.widget.AutoComplete.prototype._onContainerClick = function(v,oSelf) {
2635
    var elTarget = YAHOO.util.Event.getTarget(v);
2636
    var elTag = elTarget.nodeName.toLowerCase();
2637
    while(elTarget && (elTag != "table")) {
2638
        switch(elTag) {
2639
            case "body":
2640
                return;
2641
            case "li":
2642
                // In case item has not been moused over
2643
                oSelf._toggleHighlight(elTarget,"to");
2644
                oSelf._selectItem(elTarget);
2645
                return;
2646
            default:
2647
                break;
2648
        }
2649
2650
        elTarget = elTarget.parentNode;
2651
        if(elTarget) {
2652
            elTag = elTarget.nodeName.toLowerCase();
2653
        }
2654
    }    
2655
};
2656
2657
2658
/**
2659
 * Handles container scroll events.
2660
 *
2661
 * @method _onContainerScroll
2662
 * @param v {HTMLEvent} The scroll event.
2663
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2664
 * @private
2665
 */
2666
YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
2667
    oSelf._focus();
2668
};
2669
2670
/**
2671
 * Handles container resize events.
2672
 *
2673
 * @method _onContainerResize
2674
 * @param v {HTMLEvent} The resize event.
2675
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2676
 * @private
2677
 */
2678
YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
2679
    oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
2680
};
2681
2682
2683
/**
2684
 * Handles textbox keydown events of functional keys, mainly for UI behavior.
2685
 *
2686
 * @method _onTextboxKeyDown
2687
 * @param v {HTMLEvent} The keydown event.
2688
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2689
 * @private
2690
 */
2691
YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
2692
    var nKeyCode = v.keyCode;
2693
2694
    // Clear timeout
2695
    if(oSelf._nTypeAheadDelayID != -1) {
2696
        clearTimeout(oSelf._nTypeAheadDelayID);
2697
    }
2698
    
2699
    switch (nKeyCode) {
2700
        case 9: // tab
2701
            if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2702
                // select an item or clear out
2703
                if(oSelf._elCurListItem) {
2704
                    if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
2705
                        if(oSelf._bContainerOpen) {
2706
                            YAHOO.util.Event.stopEvent(v);
2707
                        }
2708
                    }
2709
                    oSelf._selectItem(oSelf._elCurListItem);
2710
                }
2711
                else {
2712
                    oSelf._toggleContainer(false);
2713
                }
2714
            }
2715
            break;
2716
        case 13: // enter
2717
            if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2718
                if(oSelf._elCurListItem) {
2719
                    if(oSelf._nKeyCode != nKeyCode) {
2720
                        if(oSelf._bContainerOpen) {
2721
                            YAHOO.util.Event.stopEvent(v);
2722
                        }
2723
                    }
2724
                    oSelf._selectItem(oSelf._elCurListItem);
2725
                }
2726
                else {
2727
                    oSelf._toggleContainer(false);
2728
                }
2729
            }
2730
            break;
2731
        case 27: // esc
2732
            oSelf._toggleContainer(false);
2733
            return;
2734
        case 39: // right
2735
            oSelf._jumpSelection();
2736
            break;
2737
        case 38: // up
2738
            if(oSelf._bContainerOpen) {
2739
                YAHOO.util.Event.stopEvent(v);
2740
                oSelf._moveSelection(nKeyCode);
2741
            }
2742
            break;
2743
        case 40: // down
2744
            if(oSelf._bContainerOpen) {
2745
                YAHOO.util.Event.stopEvent(v);
2746
                oSelf._moveSelection(nKeyCode);
2747
            }
2748
            break;
2749
        default: 
2750
            oSelf._bItemSelected = false;
2751
            oSelf._toggleHighlight(oSelf._elCurListItem, "from");
2752
2753
            oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
2754
            YAHOO.log("Textbox keyed", "info", oSelf.toString());
2755
            break;
2756
    }
2757
2758
    if(nKeyCode === 18){
2759
        oSelf._enableIntervalDetection();
2760
    }    
2761
    oSelf._nKeyCode = nKeyCode;
2762
};
2763
2764
/**
2765
 * Handles textbox keypress events.
2766
 * @method _onTextboxKeyPress
2767
 * @param v {HTMLEvent} The keypress event.
2768
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2769
 * @private
2770
 */
2771
YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
2772
    var nKeyCode = v.keyCode;
2773
2774
        // Expose only to non SF3 (bug 1978549) Mac browsers (bug 790337) and  Opera browsers (bug 583531),
2775
        // where stopEvent is ineffective on keydown events 
2776
        if(YAHOO.env.ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") != -1) && (YAHOO.env.ua.webkit < 420)) {
2777
            switch (nKeyCode) {
2778
            case 9: // tab
2779
                // select an item or clear out
2780
                if(oSelf._bContainerOpen) {
2781
                    if(oSelf.delimChar) {
2782
                        YAHOO.util.Event.stopEvent(v);
2783
                    }
2784
                    if(oSelf._elCurListItem) {
2785
                        oSelf._selectItem(oSelf._elCurListItem);
2786
                    }
2787
                    else {
2788
                        oSelf._toggleContainer(false);
2789
                    }
2790
                }
2791
                break;
2792
            case 13: // enter
2793
                if(oSelf._bContainerOpen) {
2794
                    YAHOO.util.Event.stopEvent(v);
2795
                    if(oSelf._elCurListItem) {
2796
                        oSelf._selectItem(oSelf._elCurListItem);
2797
                    }
2798
                    else {
2799
                        oSelf._toggleContainer(false);
2800
                    }
2801
                }
2802
                break;
2803
            default:
2804
                break;
2805
            }
2806
        }
2807
2808
        //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
2809
        // Korean IME detected
2810
        else if(nKeyCode == 229) {
2811
            oSelf._enableIntervalDetection();
2812
        }
2813
};
2814
2815
/**
2816
 * Handles textbox keyup events to trigger queries.
2817
 *
2818
 * @method _onTextboxKeyUp
2819
 * @param v {HTMLEvent} The keyup event.
2820
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2821
 * @private
2822
 */
2823
YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
2824
    var sText = this.value; //string in textbox
2825
    
2826
    // Check to see if any of the public properties have been updated
2827
    oSelf._initProps();
2828
2829
    // Filter out chars that don't trigger queries
2830
    var nKeyCode = v.keyCode;
2831
    if(oSelf._isIgnoreKey(nKeyCode)) {
2832
        return;
2833
    }
2834
2835
    // Clear previous timeout
2836
    if(oSelf._nDelayID != -1) {
2837
        clearTimeout(oSelf._nDelayID);
2838
    }
2839
2840
    // Set new timeout
2841
    oSelf._nDelayID = setTimeout(function(){
2842
            oSelf._sendQuery(sText);
2843
        },(oSelf.queryDelay * 1000));
2844
};
2845
2846
/**
2847
 * Handles text input box receiving focus.
2848
 *
2849
 * @method _onTextboxFocus
2850
 * @param v {HTMLEvent} The focus event.
2851
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2852
 * @private
2853
 */
2854
YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
2855
    // Start of a new interaction
2856
    if(!oSelf._bFocused) {
2857
        oSelf._elTextbox.setAttribute("autocomplete","off");
2858
        oSelf._bFocused = true;
2859
        oSelf._sInitInputValue = oSelf._elTextbox.value;
2860
        oSelf.textboxFocusEvent.fire(oSelf);
2861
        YAHOO.log("Textbox focused", "info", oSelf.toString());
2862
    }
2863
};
2864
2865
/**
2866
 * Handles text input box losing focus.
2867
 *
2868
 * @method _onTextboxBlur
2869
 * @param v {HTMLEvent} The focus event.
2870
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2871
 * @private
2872
 */
2873
YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
2874
    // Is a true blur
2875
    if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
2876
        // Current query needs to be validated as a selection
2877
        if(!oSelf._bItemSelected) {
2878
            var elMatchListItem = oSelf._textMatchesOption();
2879
            // Container is closed or current query doesn't match any result
2880
            if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (elMatchListItem === null))) {
2881
                // Force selection is enabled so clear the current query
2882
                if(oSelf.forceSelection) {
2883
                    oSelf._clearSelection();
2884
                }
2885
                // Treat current query as a valid selection
2886
                else {
2887
                    oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery);
2888
                    YAHOO.log("Unmatched item selected: " + oSelf._sCurQuery, "info", oSelf.toString());
2889
                }
2890
            }
2891
            // Container is open and current query matches a result
2892
            else {
2893
                // Force a selection when textbox is blurred with a match
2894
                if(oSelf.forceSelection) {
2895
                    oSelf._selectItem(elMatchListItem);
2896
                }
2897
            }
2898
        }
2899
2900
        oSelf._clearInterval();
2901
        oSelf._bFocused = false;
2902
        if(oSelf._sInitInputValue !== oSelf._elTextbox.value) {
2903
            oSelf.textboxChangeEvent.fire(oSelf);
2904
        }
2905
        oSelf.textboxBlurEvent.fire(oSelf);
2906
        YAHOO.log("Textbox blurred", "info", oSelf.toString());
2907
2908
        oSelf._toggleContainer(false);
2909
    }
2910
    // Not a true blur if it was a selection via mouse click
2911
    else {
2912
        oSelf._focus();
2913
    }
2914
};
2915
2916
/**
2917
 * Handles window unload event.
2918
 *
2919
 * @method _onWindowUnload
2920
 * @param v {HTMLEvent} The unload event.
2921
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2922
 * @private
2923
 */
2924
YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
2925
    if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
2926
        oSelf._elTextbox.setAttribute("autocomplete","on");
2927
    }
2928
};
2929
2930
/////////////////////////////////////////////////////////////////////////////
2931
//
2932
// Deprecated for Backwards Compatibility
2933
//
2934
/////////////////////////////////////////////////////////////////////////////
2935
/**
2936
 * @method doBeforeSendQuery
2937
 * @deprecated Use generateRequest.
2938
 */
2939
YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
2940
    return this.generateRequest(sQuery);
2941
};
2942
2943
/**
2944
 * @method getListItems
2945
 * @deprecated Use getListEl().childNodes.
2946
 */
2947
YAHOO.widget.AutoComplete.prototype.getListItems = function() {
2948
    var allListItemEls = [],
2949
        els = this._elList.childNodes;
2950
    for(var i=els.length-1; i>=0; i--) {
2951
        allListItemEls[i] = els[i];
2952
    }
2953
    return allListItemEls;
2954
};
2955
2956
/////////////////////////////////////////////////////////////////////////
2957
//
2958
// Private static methods
2959
//
2960
/////////////////////////////////////////////////////////////////////////
2961
2962
/**
2963
 * Clones object literal or array of object literals.
2964
 *
2965
 * @method AutoComplete._cloneObject
2966
 * @param o {Object} Object.
2967
 * @private
2968
 * @static     
2969
 */
2970
YAHOO.widget.AutoComplete._cloneObject = function(o) {
2971
    if(!YAHOO.lang.isValue(o)) {
2972
        return o;
2973
    }
2974
    
2975
    var copy = {};
2976
    
2977
    if(YAHOO.lang.isFunction(o)) {
2978
        copy = o;
2979
    }
2980
    else if(YAHOO.lang.isArray(o)) {
2981
        var array = [];
2982
        for(var i=0,len=o.length;i<len;i++) {
2983
            array[i] = YAHOO.widget.AutoComplete._cloneObject(o[i]);
2984
        }
2985
        copy = array;
2986
    }
2987
    else if(YAHOO.lang.isObject(o)) { 
2988
        for (var x in o){
2989
            if(YAHOO.lang.hasOwnProperty(o, x)) {
2990
                if(YAHOO.lang.isValue(o[x]) && YAHOO.lang.isObject(o[x]) || YAHOO.lang.isArray(o[x])) {
2991
                    copy[x] = YAHOO.widget.AutoComplete._cloneObject(o[x]);
2992
                }
2993
                else {
2994
                    copy[x] = o[x];
2995
                }
2996
            }
2997
        }
2998
    }
2999
    else {
3000
        copy = o;
3001
    }
3002
3003
    return copy;
3004
};
3005
3006
3007
3008
3009
YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.8.0r4", build: "2449"});
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/autocomplete/autocomplete-min.js (-12 lines)
Lines 1-12 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
YAHOO.widget.DS_JSArray=YAHOO.util.LocalDataSource;YAHOO.widget.DS_JSFunction=YAHOO.util.FunctionDataSource;YAHOO.widget.DS_XHR=function(B,A,D){var C=new YAHOO.util.XHRDataSource(B,D);C._aDeprecatedSchema=A;return C;};YAHOO.widget.DS_ScriptNode=function(B,A,D){var C=new YAHOO.util.ScriptNodeDataSource(B,D);C._aDeprecatedSchema=A;return C;};YAHOO.widget.DS_XHR.TYPE_JSON=YAHOO.util.DataSourceBase.TYPE_JSON;YAHOO.widget.DS_XHR.TYPE_XML=YAHOO.util.DataSourceBase.TYPE_XML;YAHOO.widget.DS_XHR.TYPE_FLAT=YAHOO.util.DataSourceBase.TYPE_TEXT;YAHOO.widget.AutoComplete=function(G,B,J,C){if(G&&B&&J){if(J&&YAHOO.lang.isFunction(J.sendRequest)){this.dataSource=J;}else{return;}this.key=0;var D=J.responseSchema;if(J._aDeprecatedSchema){var K=J._aDeprecatedSchema;if(YAHOO.lang.isArray(K)){if((J.responseType===YAHOO.util.DataSourceBase.TYPE_JSON)||(J.responseType===YAHOO.util.DataSourceBase.TYPE_UNKNOWN)){D.resultsList=K[0];this.key=K[1];D.fields=(K.length<3)?null:K.slice(1);}else{if(J.responseType===YAHOO.util.DataSourceBase.TYPE_XML){D.resultNode=K[0];this.key=K[1];D.fields=K.slice(1);}else{if(J.responseType===YAHOO.util.DataSourceBase.TYPE_TEXT){D.recordDelim=K[0];D.fieldDelim=K[1];}}}J.responseSchema=D;}}if(YAHOO.util.Dom.inDocument(G)){if(YAHOO.lang.isString(G)){this._sName="instance"+YAHOO.widget.AutoComplete._nIndex+" "+G;this._elTextbox=document.getElementById(G);}else{this._sName=(G.id)?"instance"+YAHOO.widget.AutoComplete._nIndex+" "+G.id:"instance"+YAHOO.widget.AutoComplete._nIndex;this._elTextbox=G;}YAHOO.util.Dom.addClass(this._elTextbox,"yui-ac-input");}else{return;}if(YAHOO.util.Dom.inDocument(B)){if(YAHOO.lang.isString(B)){this._elContainer=document.getElementById(B);}else{this._elContainer=B;}if(this._elContainer.style.display=="none"){}var E=this._elContainer.parentNode;var A=E.tagName.toLowerCase();if(A=="div"){YAHOO.util.Dom.addClass(E,"yui-ac");}else{}}else{return;}if(this.dataSource.dataType===YAHOO.util.DataSourceBase.TYPE_LOCAL){this.applyLocalFilter=true;}if(C&&(C.constructor==Object)){for(var I in C){if(I){this[I]=C[I];}}}this._initContainerEl();this._initProps();this._initListEl();this._initContainerHelperEls();var H=this;var F=this._elTextbox;YAHOO.util.Event.addListener(F,"keyup",H._onTextboxKeyUp,H);YAHOO.util.Event.addListener(F,"keydown",H._onTextboxKeyDown,H);YAHOO.util.Event.addListener(F,"focus",H._onTextboxFocus,H);YAHOO.util.Event.addListener(F,"blur",H._onTextboxBlur,H);YAHOO.util.Event.addListener(B,"mouseover",H._onContainerMouseover,H);YAHOO.util.Event.addListener(B,"mouseout",H._onContainerMouseout,H);YAHOO.util.Event.addListener(B,"click",H._onContainerClick,H);YAHOO.util.Event.addListener(B,"scroll",H._onContainerScroll,H);YAHOO.util.Event.addListener(B,"resize",H._onContainerResize,H);YAHOO.util.Event.addListener(F,"keypress",H._onTextboxKeyPress,H);YAHOO.util.Event.addListener(window,"unload",H._onWindowUnload,H);this.textboxFocusEvent=new YAHOO.util.CustomEvent("textboxFocus",this);this.textboxKeyEvent=new YAHOO.util.CustomEvent("textboxKey",this);this.dataRequestEvent=new YAHOO.util.CustomEvent("dataRequest",this);this.dataReturnEvent=new YAHOO.util.CustomEvent("dataReturn",this);this.dataErrorEvent=new YAHOO.util.CustomEvent("dataError",this);this.containerPopulateEvent=new YAHOO.util.CustomEvent("containerPopulate",this);this.containerExpandEvent=new YAHOO.util.CustomEvent("containerExpand",this);this.typeAheadEvent=new YAHOO.util.CustomEvent("typeAhead",this);this.itemMouseOverEvent=new YAHOO.util.CustomEvent("itemMouseOver",this);this.itemMouseOutEvent=new YAHOO.util.CustomEvent("itemMouseOut",this);this.itemArrowToEvent=new YAHOO.util.CustomEvent("itemArrowTo",this);this.itemArrowFromEvent=new YAHOO.util.CustomEvent("itemArrowFrom",this);this.itemSelectEvent=new YAHOO.util.CustomEvent("itemSelect",this);this.unmatchedItemSelectEvent=new YAHOO.util.CustomEvent("unmatchedItemSelect",this);this.selectionEnforceEvent=new YAHOO.util.CustomEvent("selectionEnforce",this);this.containerCollapseEvent=new YAHOO.util.CustomEvent("containerCollapse",this);this.textboxBlurEvent=new YAHOO.util.CustomEvent("textboxBlur",this);this.textboxChangeEvent=new YAHOO.util.CustomEvent("textboxChange",this);F.setAttribute("autocomplete","off");YAHOO.widget.AutoComplete._nIndex++;}else{}};YAHOO.widget.AutoComplete.prototype.dataSource=null;YAHOO.widget.AutoComplete.prototype.applyLocalFilter=null;YAHOO.widget.AutoComplete.prototype.queryMatchCase=false;YAHOO.widget.AutoComplete.prototype.queryMatchContains=false;YAHOO.widget.AutoComplete.prototype.queryMatchSubset=false;YAHOO.widget.AutoComplete.prototype.minQueryLength=1;YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed=10;YAHOO.widget.AutoComplete.prototype.queryDelay=0.2;YAHOO.widget.AutoComplete.prototype.typeAheadDelay=0.5;YAHOO.widget.AutoComplete.prototype.queryInterval=500;YAHOO.widget.AutoComplete.prototype.highlightClassName="yui-ac-highlight";YAHOO.widget.AutoComplete.prototype.prehighlightClassName=null;YAHOO.widget.AutoComplete.prototype.delimChar=null;YAHOO.widget.AutoComplete.prototype.autoHighlight=true;YAHOO.widget.AutoComplete.prototype.typeAhead=false;YAHOO.widget.AutoComplete.prototype.animHoriz=false;YAHOO.widget.AutoComplete.prototype.animVert=true;YAHOO.widget.AutoComplete.prototype.animSpeed=0.3;YAHOO.widget.AutoComplete.prototype.forceSelection=false;YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete=true;YAHOO.widget.AutoComplete.prototype.alwaysShowContainer=false;YAHOO.widget.AutoComplete.prototype.useIFrame=false;YAHOO.widget.AutoComplete.prototype.useShadow=false;YAHOO.widget.AutoComplete.prototype.suppressInputUpdate=false;YAHOO.widget.AutoComplete.prototype.resultTypeList=true;YAHOO.widget.AutoComplete.prototype.queryQuestionMark=true;YAHOO.widget.AutoComplete.prototype.autoSnapContainer=true;YAHOO.widget.AutoComplete.prototype.toString=function(){return"AutoComplete "+this._sName;};YAHOO.widget.AutoComplete.prototype.getInputEl=function(){return this._elTextbox;};YAHOO.widget.AutoComplete.prototype.getContainerEl=function(){return this._elContainer;
8
};YAHOO.widget.AutoComplete.prototype.isFocused=function(){return this._bFocused;};YAHOO.widget.AutoComplete.prototype.isContainerOpen=function(){return this._bContainerOpen;};YAHOO.widget.AutoComplete.prototype.getListEl=function(){return this._elList;};YAHOO.widget.AutoComplete.prototype.getListItemMatch=function(A){if(A._sResultMatch){return A._sResultMatch;}else{return null;}};YAHOO.widget.AutoComplete.prototype.getListItemData=function(A){if(A._oResultData){return A._oResultData;}else{return null;}};YAHOO.widget.AutoComplete.prototype.getListItemIndex=function(A){if(YAHOO.lang.isNumber(A._nItemIndex)){return A._nItemIndex;}else{return null;}};YAHOO.widget.AutoComplete.prototype.setHeader=function(B){if(this._elHeader){var A=this._elHeader;if(B){A.innerHTML=B;A.style.display="";}else{A.innerHTML="";A.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setFooter=function(B){if(this._elFooter){var A=this._elFooter;if(B){A.innerHTML=B;A.style.display="";}else{A.innerHTML="";A.style.display="none";}}};YAHOO.widget.AutoComplete.prototype.setBody=function(A){if(this._elBody){var B=this._elBody;YAHOO.util.Event.purgeElement(B,true);if(A){B.innerHTML=A;B.style.display="";}else{B.innerHTML="";B.style.display="none";}this._elList=null;}};YAHOO.widget.AutoComplete.prototype.generateRequest=function(B){var A=this.dataSource.dataType;if(A===YAHOO.util.DataSourceBase.TYPE_XHR){if(!this.dataSource.connMethodPost){B=(this.queryQuestionMark?"?":"")+(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}else{B=(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}}else{if(A===YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE){B="&"+(this.dataSource.scriptQueryParam||"query")+"="+B+(this.dataSource.scriptQueryAppend?("&"+this.dataSource.scriptQueryAppend):"");}}return B;};YAHOO.widget.AutoComplete.prototype.sendQuery=function(B){this._bFocused=true;var A=(this.delimChar)?this._elTextbox.value+B:B;this._sendQuery(A);};YAHOO.widget.AutoComplete.prototype.snapContainer=function(){var A=this._elTextbox,B=YAHOO.util.Dom.getXY(A);B[1]+=YAHOO.util.Dom.get(A).offsetHeight+2;YAHOO.util.Dom.setXY(this._elContainer,B);};YAHOO.widget.AutoComplete.prototype.expandContainer=function(){this._toggleContainer(true);};YAHOO.widget.AutoComplete.prototype.collapseContainer=function(){this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype.clearList=function(){var B=this._elList.childNodes,A=B.length-1;for(;A>-1;A--){B[A].style.display="none";}};YAHOO.widget.AutoComplete.prototype.getSubsetMatches=function(E){var D,C,A;for(var B=E.length;B>=this.minQueryLength;B--){A=this.generateRequest(E.substr(0,B));this.dataRequestEvent.fire(this,D,A);C=this.dataSource.getCachedResponse(A);if(C){return this.filterResults.apply(this.dataSource,[E,C,C,{scope:this}]);}}return null;};YAHOO.widget.AutoComplete.prototype.preparseRawResponse=function(C,B,A){var D=((this.responseStripAfter!=="")&&(B.indexOf))?B.indexOf(this.responseStripAfter):-1;if(D!=-1){B=B.substring(0,D);}return B;};YAHOO.widget.AutoComplete.prototype.filterResults=function(K,M,Q,L){if(L&&L.argument&&L.argument.query){K=L.argument.query;}if(K&&K!==""){Q=YAHOO.widget.AutoComplete._cloneObject(Q);var I=L.scope,P=this,C=Q.results,N=[],B=I.maxResultsDisplayed,J=(P.queryMatchCase||I.queryMatchCase),A=(P.queryMatchContains||I.queryMatchContains);for(var D=0,H=C.length;D<H;D++){var F=C[D];var E=null;if(YAHOO.lang.isString(F)){E=F;}else{if(YAHOO.lang.isArray(F)){E=F[0];}else{if(this.responseSchema.fields){var O=this.responseSchema.fields[0].key||this.responseSchema.fields[0];E=F[O];}else{if(this.key){E=F[this.key];}}}}if(YAHOO.lang.isString(E)){var G=(J)?E.indexOf(decodeURIComponent(K)):E.toLowerCase().indexOf(decodeURIComponent(K).toLowerCase());if((!A&&(G===0))||(A&&(G>-1))){N.push(F);}}if(H>B&&N.length===B){break;}}Q.results=N;}else{}return Q;};YAHOO.widget.AutoComplete.prototype.handleResponse=function(C,A,B){if((this instanceof YAHOO.widget.AutoComplete)&&this._sName){this._populateList(C,A,B);}};YAHOO.widget.AutoComplete.prototype.doBeforeLoadData=function(C,A,B){return true;};YAHOO.widget.AutoComplete.prototype.formatResult=function(B,D,A){var C=(A)?A:"";return C;};YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer=function(D,A,C,B){return true;};YAHOO.widget.AutoComplete.prototype.destroy=function(){var B=this.toString();var A=this._elTextbox;var D=this._elContainer;this.textboxFocusEvent.unsubscribeAll();this.textboxKeyEvent.unsubscribeAll();this.dataRequestEvent.unsubscribeAll();this.dataReturnEvent.unsubscribeAll();this.dataErrorEvent.unsubscribeAll();this.containerPopulateEvent.unsubscribeAll();this.containerExpandEvent.unsubscribeAll();this.typeAheadEvent.unsubscribeAll();this.itemMouseOverEvent.unsubscribeAll();this.itemMouseOutEvent.unsubscribeAll();this.itemArrowToEvent.unsubscribeAll();this.itemArrowFromEvent.unsubscribeAll();this.itemSelectEvent.unsubscribeAll();this.unmatchedItemSelectEvent.unsubscribeAll();this.selectionEnforceEvent.unsubscribeAll();this.containerCollapseEvent.unsubscribeAll();this.textboxBlurEvent.unsubscribeAll();this.textboxChangeEvent.unsubscribeAll();YAHOO.util.Event.purgeElement(A,true);YAHOO.util.Event.purgeElement(D,true);D.innerHTML="";for(var C in this){if(YAHOO.lang.hasOwnProperty(this,C)){this[C]=null;}}};YAHOO.widget.AutoComplete.prototype.textboxFocusEvent=null;YAHOO.widget.AutoComplete.prototype.textboxKeyEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestEvent=null;YAHOO.widget.AutoComplete.prototype.dataReturnEvent=null;YAHOO.widget.AutoComplete.prototype.dataErrorEvent=null;YAHOO.widget.AutoComplete.prototype.containerPopulateEvent=null;YAHOO.widget.AutoComplete.prototype.containerExpandEvent=null;YAHOO.widget.AutoComplete.prototype.typeAheadEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent=null;
9
YAHOO.widget.AutoComplete.prototype.itemArrowToEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent=null;YAHOO.widget.AutoComplete.prototype.itemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent=null;YAHOO.widget.AutoComplete.prototype.containerCollapseEvent=null;YAHOO.widget.AutoComplete.prototype.textboxBlurEvent=null;YAHOO.widget.AutoComplete.prototype.textboxChangeEvent=null;YAHOO.widget.AutoComplete._nIndex=0;YAHOO.widget.AutoComplete.prototype._sName=null;YAHOO.widget.AutoComplete.prototype._elTextbox=null;YAHOO.widget.AutoComplete.prototype._elContainer=null;YAHOO.widget.AutoComplete.prototype._elContent=null;YAHOO.widget.AutoComplete.prototype._elHeader=null;YAHOO.widget.AutoComplete.prototype._elBody=null;YAHOO.widget.AutoComplete.prototype._elFooter=null;YAHOO.widget.AutoComplete.prototype._elShadow=null;YAHOO.widget.AutoComplete.prototype._elIFrame=null;YAHOO.widget.AutoComplete.prototype._bFocused=false;YAHOO.widget.AutoComplete.prototype._oAnim=null;YAHOO.widget.AutoComplete.prototype._bContainerOpen=false;YAHOO.widget.AutoComplete.prototype._bOverContainer=false;YAHOO.widget.AutoComplete.prototype._elList=null;YAHOO.widget.AutoComplete.prototype._nDisplayedItems=0;YAHOO.widget.AutoComplete.prototype._sCurQuery=null;YAHOO.widget.AutoComplete.prototype._sPastSelections="";YAHOO.widget.AutoComplete.prototype._sInitInputValue=null;YAHOO.widget.AutoComplete.prototype._elCurListItem=null;YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem=null;YAHOO.widget.AutoComplete.prototype._bItemSelected=false;YAHOO.widget.AutoComplete.prototype._nKeyCode=null;YAHOO.widget.AutoComplete.prototype._nDelayID=-1;YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID=-1;YAHOO.widget.AutoComplete.prototype._iFrameSrc="javascript:false;";YAHOO.widget.AutoComplete.prototype._queryInterval=null;YAHOO.widget.AutoComplete.prototype._sLastTextboxValue=null;YAHOO.widget.AutoComplete.prototype._initProps=function(){var B=this.minQueryLength;if(!YAHOO.lang.isNumber(B)){this.minQueryLength=1;}var E=this.maxResultsDisplayed;if(!YAHOO.lang.isNumber(E)||(E<1)){this.maxResultsDisplayed=10;}var F=this.queryDelay;if(!YAHOO.lang.isNumber(F)||(F<0)){this.queryDelay=0.2;}var C=this.typeAheadDelay;if(!YAHOO.lang.isNumber(C)||(C<0)){this.typeAheadDelay=0.2;}var A=this.delimChar;if(YAHOO.lang.isString(A)&&(A.length>0)){this.delimChar=[A];}else{if(!YAHOO.lang.isArray(A)){this.delimChar=null;}}var D=this.animSpeed;if((this.animHoriz||this.animVert)&&YAHOO.util.Anim){if(!YAHOO.lang.isNumber(D)||(D<0)){this.animSpeed=0.3;}if(!this._oAnim){this._oAnim=new YAHOO.util.Anim(this._elContent,{},this.animSpeed);}else{this._oAnim.duration=this.animSpeed;}}if(this.forceSelection&&A){}};YAHOO.widget.AutoComplete.prototype._initContainerHelperEls=function(){if(this.useShadow&&!this._elShadow){var A=document.createElement("div");A.className="yui-ac-shadow";A.style.width=0;A.style.height=0;this._elShadow=this._elContainer.appendChild(A);}if(this.useIFrame&&!this._elIFrame){var B=document.createElement("iframe");B.src=this._iFrameSrc;B.frameBorder=0;B.scrolling="no";B.style.position="absolute";B.style.width=0;B.style.height=0;B.style.padding=0;B.tabIndex=-1;B.role="presentation";B.title="Presentational iframe shim";this._elIFrame=this._elContainer.appendChild(B);}};YAHOO.widget.AutoComplete.prototype._initContainerEl=function(){YAHOO.util.Dom.addClass(this._elContainer,"yui-ac-container");if(!this._elContent){var C=document.createElement("div");C.className="yui-ac-content";C.style.display="none";this._elContent=this._elContainer.appendChild(C);var B=document.createElement("div");B.className="yui-ac-hd";B.style.display="none";this._elHeader=this._elContent.appendChild(B);var D=document.createElement("div");D.className="yui-ac-bd";this._elBody=this._elContent.appendChild(D);var A=document.createElement("div");A.className="yui-ac-ft";A.style.display="none";this._elFooter=this._elContent.appendChild(A);}else{}};YAHOO.widget.AutoComplete.prototype._initListEl=function(){var C=this.maxResultsDisplayed,A=this._elList||document.createElement("ul"),B;while(A.childNodes.length<C){B=document.createElement("li");B.style.display="none";B._nItemIndex=A.childNodes.length;A.appendChild(B);}if(!this._elList){var D=this._elBody;YAHOO.util.Event.purgeElement(D,true);D.innerHTML="";this._elList=D.appendChild(A);}this._elBody.style.display="";};YAHOO.widget.AutoComplete.prototype._focus=function(){var A=this;setTimeout(function(){try{A._elTextbox.focus();}catch(B){}},0);};YAHOO.widget.AutoComplete.prototype._enableIntervalDetection=function(){var A=this;if(!A._queryInterval&&A.queryInterval){A._queryInterval=setInterval(function(){A._onInterval();},A.queryInterval);}};YAHOO.widget.AutoComplete.prototype.enableIntervalDetection=YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;YAHOO.widget.AutoComplete.prototype._onInterval=function(){var A=this._elTextbox.value;var B=this._sLastTextboxValue;if(A!=B){this._sLastTextboxValue=A;this._sendQuery(A);}};YAHOO.widget.AutoComplete.prototype._clearInterval=function(){if(this._queryInterval){clearInterval(this._queryInterval);this._queryInterval=null;}};YAHOO.widget.AutoComplete.prototype._isIgnoreKey=function(A){if((A==9)||(A==13)||(A==16)||(A==17)||(A>=18&&A<=20)||(A==27)||(A>=33&&A<=35)||(A>=36&&A<=40)||(A>=44&&A<=45)||(A==229)){return true;}return false;};YAHOO.widget.AutoComplete.prototype._sendQuery=function(D){if(this.minQueryLength<0){this._toggleContainer(false);return;}if(this.delimChar){var A=this._extractQuery(D);D=A.query;this._sPastSelections=A.previous;}if((D&&(D.length<this.minQueryLength))||(!D&&this.minQueryLength>0)){if(this._nDelayID!=-1){clearTimeout(this._nDelayID);}this._toggleContainer(false);return;}D=encodeURIComponent(D);this._nDelayID=-1;if(this.dataSource.queryMatchSubset||this.queryMatchSubset){var C=this.getSubsetMatches(D);if(C){this.handleResponse(D,C,{query:D});return;
10
}}if(this.dataSource.responseStripAfter){this.dataSource.doBeforeParseData=this.preparseRawResponse;}if(this.applyLocalFilter){this.dataSource.doBeforeCallback=this.filterResults;}var B=this.generateRequest(D);this.dataRequestEvent.fire(this,D,B);this.dataSource.sendRequest(B,{success:this.handleResponse,failure:this.handleResponse,scope:this,argument:{query:D}});};YAHOO.widget.AutoComplete.prototype._populateListItem=function(B,A,C){B.innerHTML=this.formatResult(A,C,B._sResultMatch);};YAHOO.widget.AutoComplete.prototype._populateList=function(K,F,C){if(this._nTypeAheadDelayID!=-1){clearTimeout(this._nTypeAheadDelayID);}K=(C&&C.query)?C.query:K;var H=this.doBeforeLoadData(K,F,C);if(H&&!F.error){this.dataReturnEvent.fire(this,K,F.results);if(this._bFocused){var M=decodeURIComponent(K);this._sCurQuery=M;this._bItemSelected=false;var R=F.results,A=Math.min(R.length,this.maxResultsDisplayed),J=(this.dataSource.responseSchema.fields)?(this.dataSource.responseSchema.fields[0].key||this.dataSource.responseSchema.fields[0]):0;if(A>0){if(!this._elList||(this._elList.childNodes.length<A)){this._initListEl();}this._initContainerHelperEls();var I=this._elList.childNodes;for(var Q=A-1;Q>=0;Q--){var P=I[Q],E=R[Q];if(this.resultTypeList){var B=[];B[0]=(YAHOO.lang.isString(E))?E:E[J]||E[this.key];var L=this.dataSource.responseSchema.fields;if(YAHOO.lang.isArray(L)&&(L.length>1)){for(var N=1,S=L.length;N<S;N++){B[B.length]=E[L[N].key||L[N]];}}else{if(YAHOO.lang.isArray(E)){B=E;}else{if(YAHOO.lang.isString(E)){B=[E];}else{B[1]=E;}}}E=B;}P._sResultMatch=(YAHOO.lang.isString(E))?E:(YAHOO.lang.isArray(E))?E[0]:(E[J]||"");P._oResultData=E;this._populateListItem(P,E,M);P.style.display="";}if(A<I.length){var G;for(var O=I.length-1;O>=A;O--){G=I[O];G.style.display="none";}}this._nDisplayedItems=A;this.containerPopulateEvent.fire(this,K,R);if(this.autoHighlight){var D=this._elList.firstChild;this._toggleHighlight(D,"to");this.itemArrowToEvent.fire(this,D);this._typeAhead(D,K);}else{this._toggleHighlight(this._elCurListItem,"from");}H=this._doBeforeExpandContainer(this._elTextbox,this._elContainer,K,R);this._toggleContainer(H);}else{this._toggleContainer(false);}return;}}else{this.dataErrorEvent.fire(this,K,F);}};YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer=function(D,A,C,B){if(this.autoSnapContainer){this.snapContainer();}return this.doBeforeExpandContainer(D,A,C,B);};YAHOO.widget.AutoComplete.prototype._clearSelection=function(){var A=(this.delimChar)?this._extractQuery(this._elTextbox.value):{previous:"",query:this._elTextbox.value};this._elTextbox.value=A.previous;this.selectionEnforceEvent.fire(this,A.query);};YAHOO.widget.AutoComplete.prototype._textMatchesOption=function(){var A=null;for(var B=0;B<this._nDisplayedItems;B++){var C=this._elList.childNodes[B];var D=(""+C._sResultMatch).toLowerCase();if(D==this._sCurQuery.toLowerCase()){A=C;break;}}return(A);};YAHOO.widget.AutoComplete.prototype._typeAhead=function(B,D){if(!this.typeAhead||(this._nKeyCode==8)){return;}var A=this,C=this._elTextbox;if(C.setSelectionRange||C.createTextRange){this._nTypeAheadDelayID=setTimeout(function(){var F=C.value.length;A._updateValue(B);var G=C.value.length;A._selectText(C,F,G);var E=C.value.substr(F,G);A.typeAheadEvent.fire(A,D,E);},(this.typeAheadDelay*1000));}};YAHOO.widget.AutoComplete.prototype._selectText=function(D,A,B){if(D.setSelectionRange){D.setSelectionRange(A,B);}else{if(D.createTextRange){var C=D.createTextRange();C.moveStart("character",A);C.moveEnd("character",B-D.value.length);C.select();}else{D.select();}}};YAHOO.widget.AutoComplete.prototype._extractQuery=function(H){var C=this.delimChar,F=-1,G,E,B=C.length-1,D;for(;B>=0;B--){G=H.lastIndexOf(C[B]);if(G>F){F=G;}}if(C[B]==" "){for(var A=C.length-1;A>=0;A--){if(H[F-1]==C[A]){F--;break;}}}if(F>-1){E=F+1;while(H.charAt(E)==" "){E+=1;}D=H.substring(0,E);H=H.substr(E);}else{D="";}return{previous:D,query:H};};YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers=function(D){var E=this._elContent.offsetWidth+"px";var B=this._elContent.offsetHeight+"px";if(this.useIFrame&&this._elIFrame){var C=this._elIFrame;if(D){C.style.width=E;C.style.height=B;C.style.padding="";}else{C.style.width=0;C.style.height=0;C.style.padding=0;}}if(this.useShadow&&this._elShadow){var A=this._elShadow;if(D){A.style.width=E;A.style.height=B;}else{A.style.width=0;A.style.height=0;}}};YAHOO.widget.AutoComplete.prototype._toggleContainer=function(I){var D=this._elContainer;if(this.alwaysShowContainer&&this._bContainerOpen){return;}if(!I){this._toggleHighlight(this._elCurListItem,"from");this._nDisplayedItems=0;this._sCurQuery=null;if(this._elContent.style.display=="none"){return;}}var A=this._oAnim;if(A&&A.getEl()&&(this.animHoriz||this.animVert)){if(A.isAnimated()){A.stop(true);}var G=this._elContent.cloneNode(true);D.appendChild(G);G.style.top="-9000px";G.style.width="";G.style.height="";G.style.display="";var F=G.offsetWidth;var C=G.offsetHeight;var B=(this.animHoriz)?0:F;var E=(this.animVert)?0:C;A.attributes=(I)?{width:{to:F},height:{to:C}}:{width:{to:B},height:{to:E}};if(I&&!this._bContainerOpen){this._elContent.style.width=B+"px";this._elContent.style.height=E+"px";}else{this._elContent.style.width=F+"px";this._elContent.style.height=C+"px";}D.removeChild(G);G=null;var H=this;var J=function(){A.onComplete.unsubscribeAll();if(I){H._toggleContainerHelpers(true);H._bContainerOpen=I;H.containerExpandEvent.fire(H);}else{H._elContent.style.display="none";H._bContainerOpen=I;H.containerCollapseEvent.fire(H);}};this._toggleContainerHelpers(false);this._elContent.style.display="";A.onComplete.subscribe(J);A.animate();}else{if(I){this._elContent.style.display="";this._toggleContainerHelpers(true);this._bContainerOpen=I;this.containerExpandEvent.fire(this);}else{this._toggleContainerHelpers(false);this._elContent.style.display="none";this._bContainerOpen=I;this.containerCollapseEvent.fire(this);}}};YAHOO.widget.AutoComplete.prototype._toggleHighlight=function(A,C){if(A){var B=this.highlightClassName;
11
if(this._elCurListItem){YAHOO.util.Dom.removeClass(this._elCurListItem,B);this._elCurListItem=null;}if((C=="to")&&B){YAHOO.util.Dom.addClass(A,B);this._elCurListItem=A;}}};YAHOO.widget.AutoComplete.prototype._togglePrehighlight=function(B,C){var A=this.prehighlightClassName;if(this._elCurPrehighlightItem){YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem,A);}if(B==this._elCurListItem){return;}if((C=="mouseover")&&A){YAHOO.util.Dom.addClass(B,A);this._elCurPrehighlightItem=B;}else{YAHOO.util.Dom.removeClass(B,A);}};YAHOO.widget.AutoComplete.prototype._updateValue=function(C){if(!this.suppressInputUpdate){var F=this._elTextbox;var E=(this.delimChar)?(this.delimChar[0]||this.delimChar):null;var B=C._sResultMatch;var D="";if(E){D=this._sPastSelections;D+=B+E;if(E!=" "){D+=" ";}}else{D=B;}F.value=D;if(F.type=="textarea"){F.scrollTop=F.scrollHeight;}var A=F.value.length;this._selectText(F,A,A);this._elCurListItem=C;}};YAHOO.widget.AutoComplete.prototype._selectItem=function(A){this._bItemSelected=true;this._updateValue(A);this._sPastSelections=this._elTextbox.value;this._clearInterval();this.itemSelectEvent.fire(this,A,A._oResultData);this._toggleContainer(false);};YAHOO.widget.AutoComplete.prototype._jumpSelection=function(){if(this._elCurListItem){this._selectItem(this._elCurListItem);}else{this._toggleContainer(false);}};YAHOO.widget.AutoComplete.prototype._moveSelection=function(G){if(this._bContainerOpen){var H=this._elCurListItem,D=-1;if(H){D=H._nItemIndex;}var E=(G==40)?(D+1):(D-1);if(E<-2||E>=this._nDisplayedItems){return;}if(H){this._toggleHighlight(H,"from");this.itemArrowFromEvent.fire(this,H);}if(E==-1){if(this.delimChar){this._elTextbox.value=this._sPastSelections+this._sCurQuery;}else{this._elTextbox.value=this._sCurQuery;}return;}if(E==-2){this._toggleContainer(false);return;}var F=this._elList.childNodes[E],B=this._elContent,C=YAHOO.util.Dom.getStyle(B,"overflow"),I=YAHOO.util.Dom.getStyle(B,"overflowY"),A=((C=="auto")||(C=="scroll")||(I=="auto")||(I=="scroll"));if(A&&(E>-1)&&(E<this._nDisplayedItems)){if(G==40){if((F.offsetTop+F.offsetHeight)>(B.scrollTop+B.offsetHeight)){B.scrollTop=(F.offsetTop+F.offsetHeight)-B.offsetHeight;}else{if((F.offsetTop+F.offsetHeight)<B.scrollTop){B.scrollTop=F.offsetTop;}}}else{if(F.offsetTop<B.scrollTop){this._elContent.scrollTop=F.offsetTop;}else{if(F.offsetTop>(B.scrollTop+B.offsetHeight)){this._elContent.scrollTop=(F.offsetTop+F.offsetHeight)-B.offsetHeight;}}}}this._toggleHighlight(F,"to");this.itemArrowToEvent.fire(this,F);if(this.typeAhead){this._updateValue(F);}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseover=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":if(C.prehighlightClassName){C._togglePrehighlight(D,"mouseover");}else{C._toggleHighlight(D,"to");}C.itemMouseOverEvent.fire(C,D);break;case"div":if(YAHOO.util.Dom.hasClass(D,"yui-ac-container")){C._bOverContainer=true;return;}break;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerMouseout=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":if(C.prehighlightClassName){C._togglePrehighlight(D,"mouseout");}else{C._toggleHighlight(D,"from");}C.itemMouseOutEvent.fire(C,D);break;case"ul":C._toggleHighlight(C._elCurListItem,"to");break;case"div":if(YAHOO.util.Dom.hasClass(D,"yui-ac-container")){C._bOverContainer=false;return;}break;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerClick=function(A,C){var D=YAHOO.util.Event.getTarget(A);var B=D.nodeName.toLowerCase();while(D&&(B!="table")){switch(B){case"body":return;case"li":C._toggleHighlight(D,"to");C._selectItem(D);return;default:break;}D=D.parentNode;if(D){B=D.nodeName.toLowerCase();}}};YAHOO.widget.AutoComplete.prototype._onContainerScroll=function(A,B){B._focus();};YAHOO.widget.AutoComplete.prototype._onContainerResize=function(A,B){B._toggleContainerHelpers(B._bContainerOpen);};YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown=function(A,B){var C=A.keyCode;if(B._nTypeAheadDelayID!=-1){clearTimeout(B._nTypeAheadDelayID);}switch(C){case 9:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(B._elCurListItem){if(B.delimChar&&(B._nKeyCode!=C)){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 13:if(!YAHOO.env.ua.opera&&(navigator.userAgent.toLowerCase().indexOf("mac")==-1)||(YAHOO.env.ua.webkit>420)){if(B._elCurListItem){if(B._nKeyCode!=C){if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);}}B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 27:B._toggleContainer(false);return;case 39:B._jumpSelection();break;case 38:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);B._moveSelection(C);}break;case 40:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);B._moveSelection(C);}break;default:B._bItemSelected=false;B._toggleHighlight(B._elCurListItem,"from");B.textboxKeyEvent.fire(B,C);break;}if(C===18){B._enableIntervalDetection();}B._nKeyCode=C;};YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress=function(A,B){var C=A.keyCode;if(YAHOO.env.ua.opera||(navigator.userAgent.toLowerCase().indexOf("mac")!=-1)&&(YAHOO.env.ua.webkit<420)){switch(C){case 9:if(B._bContainerOpen){if(B.delimChar){YAHOO.util.Event.stopEvent(A);}if(B._elCurListItem){B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;case 13:if(B._bContainerOpen){YAHOO.util.Event.stopEvent(A);if(B._elCurListItem){B._selectItem(B._elCurListItem);}else{B._toggleContainer(false);}}break;default:break;}}else{if(C==229){B._enableIntervalDetection();}}};YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp=function(A,C){var B=this.value;C._initProps();var D=A.keyCode;if(C._isIgnoreKey(D)){return;
12
}if(C._nDelayID!=-1){clearTimeout(C._nDelayID);}C._nDelayID=setTimeout(function(){C._sendQuery(B);},(C.queryDelay*1000));};YAHOO.widget.AutoComplete.prototype._onTextboxFocus=function(A,B){if(!B._bFocused){B._elTextbox.setAttribute("autocomplete","off");B._bFocused=true;B._sInitInputValue=B._elTextbox.value;B.textboxFocusEvent.fire(B);}};YAHOO.widget.AutoComplete.prototype._onTextboxBlur=function(A,C){if(!C._bOverContainer||(C._nKeyCode==9)){if(!C._bItemSelected){var B=C._textMatchesOption();if(!C._bContainerOpen||(C._bContainerOpen&&(B===null))){if(C.forceSelection){C._clearSelection();}else{C.unmatchedItemSelectEvent.fire(C,C._sCurQuery);}}else{if(C.forceSelection){C._selectItem(B);}}}C._clearInterval();C._bFocused=false;if(C._sInitInputValue!==C._elTextbox.value){C.textboxChangeEvent.fire(C);}C.textboxBlurEvent.fire(C);C._toggleContainer(false);}else{C._focus();}};YAHOO.widget.AutoComplete.prototype._onWindowUnload=function(A,B){if(B&&B._elTextbox&&B.allowBrowserAutocomplete){B._elTextbox.setAttribute("autocomplete","on");}};YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery=function(A){return this.generateRequest(A);};YAHOO.widget.AutoComplete.prototype.getListItems=function(){var C=[],B=this._elList.childNodes;for(var A=B.length-1;A>=0;A--){C[A]=B[A];}return C;};YAHOO.widget.AutoComplete._cloneObject=function(D){if(!YAHOO.lang.isValue(D)){return D;}var F={};if(YAHOO.lang.isFunction(D)){F=D;}else{if(YAHOO.lang.isArray(D)){var E=[];for(var C=0,B=D.length;C<B;C++){E[C]=YAHOO.widget.AutoComplete._cloneObject(D[C]);}F=E;}else{if(YAHOO.lang.isObject(D)){for(var A in D){if(YAHOO.lang.hasOwnProperty(D,A)){if(YAHOO.lang.isValue(D[A])&&YAHOO.lang.isObject(D[A])||YAHOO.lang.isArray(D[A])){F[A]=YAHOO.widget.AutoComplete._cloneObject(D[A]);}else{F[A]=D[A];}}}}else{F=D;}}}return F;};YAHOO.register("autocomplete",YAHOO.widget.AutoComplete,{version:"2.8.0r4",build:"2449"});
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/autocomplete/autocomplete.js (-2966 lines)
Lines 1-2966 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
/////////////////////////////////////////////////////////////////////////////
8
//
9
// YAHOO.widget.DataSource Backwards Compatibility
10
//
11
/////////////////////////////////////////////////////////////////////////////
12
13
YAHOO.widget.DS_JSArray = YAHOO.util.LocalDataSource;
14
15
YAHOO.widget.DS_JSFunction = YAHOO.util.FunctionDataSource;
16
17
YAHOO.widget.DS_XHR = function(sScriptURI, aSchema, oConfigs) {
18
    var DS = new YAHOO.util.XHRDataSource(sScriptURI, oConfigs);
19
    DS._aDeprecatedSchema = aSchema;
20
    return DS;
21
};
22
23
YAHOO.widget.DS_ScriptNode = function(sScriptURI, aSchema, oConfigs) {
24
    var DS = new YAHOO.util.ScriptNodeDataSource(sScriptURI, oConfigs);
25
    DS._aDeprecatedSchema = aSchema;
26
    return DS;
27
};
28
29
YAHOO.widget.DS_XHR.TYPE_JSON = YAHOO.util.DataSourceBase.TYPE_JSON;
30
YAHOO.widget.DS_XHR.TYPE_XML = YAHOO.util.DataSourceBase.TYPE_XML;
31
YAHOO.widget.DS_XHR.TYPE_FLAT = YAHOO.util.DataSourceBase.TYPE_TEXT;
32
33
// TODO: widget.DS_ScriptNode.scriptCallbackParam
34
35
36
37
 /**
38
 * The AutoComplete control provides the front-end logic for text-entry suggestion and
39
 * completion functionality.
40
 *
41
 * @module autocomplete
42
 * @requires yahoo, dom, event, datasource
43
 * @optional animation
44
 * @namespace YAHOO.widget
45
 * @title AutoComplete Widget
46
 */
47
48
/****************************************************************************/
49
/****************************************************************************/
50
/****************************************************************************/
51
52
/**
53
 * The AutoComplete class provides the customizable functionality of a plug-and-play DHTML
54
 * auto completion widget.  Some key features:
55
 * <ul>
56
 * <li>Navigate with up/down arrow keys and/or mouse to pick a selection</li>
57
 * <li>The drop down container can "roll down" or "fly out" via configurable
58
 * animation</li>
59
 * <li>UI look-and-feel customizable through CSS, including container
60
 * attributes, borders, position, fonts, etc</li>
61
 * </ul>
62
 *
63
 * @class AutoComplete
64
 * @constructor
65
 * @param elInput {HTMLElement} DOM element reference of an input field.
66
 * @param elInput {String} String ID of an input field.
67
 * @param elContainer {HTMLElement} DOM element reference of an existing DIV.
68
 * @param elContainer {String} String ID of an existing DIV.
69
 * @param oDataSource {YAHOO.widget.DataSource} DataSource instance.
70
 * @param oConfigs {Object} (optional) Object literal of configuration params.
71
 */
72
YAHOO.widget.AutoComplete = function(elInput,elContainer,oDataSource,oConfigs) {
73
    if(elInput && elContainer && oDataSource) {
74
        // Validate DataSource
75
        if(oDataSource && YAHOO.lang.isFunction(oDataSource.sendRequest)) {
76
            this.dataSource = oDataSource;
77
        }
78
        else {
79
            return;
80
        }
81
82
        // YAHOO.widget.DataSource schema backwards compatibility
83
        // Converted deprecated schema into supported schema
84
        // First assume key data is held in position 0 of results array
85
        this.key = 0;
86
        var schema = oDataSource.responseSchema;
87
        // An old school schema has been defined in the deprecated DataSource constructor
88
        if(oDataSource._aDeprecatedSchema) {
89
            var aDeprecatedSchema = oDataSource._aDeprecatedSchema;
90
            if(YAHOO.lang.isArray(aDeprecatedSchema)) {
91
                
92
                if((oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_JSON) || 
93
                (oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_UNKNOWN)) { // Used to default to unknown
94
                    // Store the resultsList
95
                    schema.resultsList = aDeprecatedSchema[0];
96
                    // Store the key
97
                    this.key = aDeprecatedSchema[1];
98
                    // Only resultsList and key are defined, so grab all the data
99
                    schema.fields = (aDeprecatedSchema.length < 3) ? null : aDeprecatedSchema.slice(1);
100
                }
101
                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_XML) {
102
                    schema.resultNode = aDeprecatedSchema[0];
103
                    this.key = aDeprecatedSchema[1];
104
                    schema.fields = aDeprecatedSchema.slice(1);
105
                }                
106
                else if(oDataSource.responseType === YAHOO.util.DataSourceBase.TYPE_TEXT) {
107
                    schema.recordDelim = aDeprecatedSchema[0];
108
                    schema.fieldDelim = aDeprecatedSchema[1];
109
                }                
110
                oDataSource.responseSchema = schema;
111
            }
112
        }
113
        
114
        // Validate input element
115
        if(YAHOO.util.Dom.inDocument(elInput)) {
116
            if(YAHOO.lang.isString(elInput)) {
117
                    this._sName = "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput;
118
                    this._elTextbox = document.getElementById(elInput);
119
            }
120
            else {
121
                this._sName = (elInput.id) ?
122
                    "instance" + YAHOO.widget.AutoComplete._nIndex + " " + elInput.id:
123
                    "instance" + YAHOO.widget.AutoComplete._nIndex;
124
                this._elTextbox = elInput;
125
            }
126
            YAHOO.util.Dom.addClass(this._elTextbox, "yui-ac-input");
127
        }
128
        else {
129
            return;
130
        }
131
132
        // Validate container element
133
        if(YAHOO.util.Dom.inDocument(elContainer)) {
134
            if(YAHOO.lang.isString(elContainer)) {
135
                    this._elContainer = document.getElementById(elContainer);
136
            }
137
            else {
138
                this._elContainer = elContainer;
139
            }
140
            if(this._elContainer.style.display == "none") {
141
            }
142
            
143
            // For skinning
144
            var elParent = this._elContainer.parentNode;
145
            var elTag = elParent.tagName.toLowerCase();
146
            if(elTag == "div") {
147
                YAHOO.util.Dom.addClass(elParent, "yui-ac");
148
            }
149
            else {
150
            }
151
        }
152
        else {
153
            return;
154
        }
155
156
        // Default applyLocalFilter setting is to enable for local sources
157
        if(this.dataSource.dataType === YAHOO.util.DataSourceBase.TYPE_LOCAL) {
158
            this.applyLocalFilter = true;
159
        }
160
        
161
        // Set any config params passed in to override defaults
162
        if(oConfigs && (oConfigs.constructor == Object)) {
163
            for(var sConfig in oConfigs) {
164
                if(sConfig) {
165
                    this[sConfig] = oConfigs[sConfig];
166
                }
167
            }
168
        }
169
170
        // Initialization sequence
171
        this._initContainerEl();
172
        this._initProps();
173
        this._initListEl();
174
        this._initContainerHelperEls();
175
176
        // Set up events
177
        var oSelf = this;
178
        var elTextbox = this._elTextbox;
179
180
        // Dom events
181
        YAHOO.util.Event.addListener(elTextbox,"keyup",oSelf._onTextboxKeyUp,oSelf);
182
        YAHOO.util.Event.addListener(elTextbox,"keydown",oSelf._onTextboxKeyDown,oSelf);
183
        YAHOO.util.Event.addListener(elTextbox,"focus",oSelf._onTextboxFocus,oSelf);
184
        YAHOO.util.Event.addListener(elTextbox,"blur",oSelf._onTextboxBlur,oSelf);
185
        YAHOO.util.Event.addListener(elContainer,"mouseover",oSelf._onContainerMouseover,oSelf);
186
        YAHOO.util.Event.addListener(elContainer,"mouseout",oSelf._onContainerMouseout,oSelf);
187
        YAHOO.util.Event.addListener(elContainer,"click",oSelf._onContainerClick,oSelf);
188
        YAHOO.util.Event.addListener(elContainer,"scroll",oSelf._onContainerScroll,oSelf);
189
        YAHOO.util.Event.addListener(elContainer,"resize",oSelf._onContainerResize,oSelf);
190
        YAHOO.util.Event.addListener(elTextbox,"keypress",oSelf._onTextboxKeyPress,oSelf);
191
        YAHOO.util.Event.addListener(window,"unload",oSelf._onWindowUnload,oSelf);
192
193
        // Custom events
194
        this.textboxFocusEvent = new YAHOO.util.CustomEvent("textboxFocus", this);
195
        this.textboxKeyEvent = new YAHOO.util.CustomEvent("textboxKey", this);
196
        this.dataRequestEvent = new YAHOO.util.CustomEvent("dataRequest", this);
197
        this.dataReturnEvent = new YAHOO.util.CustomEvent("dataReturn", this);
198
        this.dataErrorEvent = new YAHOO.util.CustomEvent("dataError", this);
199
        this.containerPopulateEvent = new YAHOO.util.CustomEvent("containerPopulate", this);
200
        this.containerExpandEvent = new YAHOO.util.CustomEvent("containerExpand", this);
201
        this.typeAheadEvent = new YAHOO.util.CustomEvent("typeAhead", this);
202
        this.itemMouseOverEvent = new YAHOO.util.CustomEvent("itemMouseOver", this);
203
        this.itemMouseOutEvent = new YAHOO.util.CustomEvent("itemMouseOut", this);
204
        this.itemArrowToEvent = new YAHOO.util.CustomEvent("itemArrowTo", this);
205
        this.itemArrowFromEvent = new YAHOO.util.CustomEvent("itemArrowFrom", this);
206
        this.itemSelectEvent = new YAHOO.util.CustomEvent("itemSelect", this);
207
        this.unmatchedItemSelectEvent = new YAHOO.util.CustomEvent("unmatchedItemSelect", this);
208
        this.selectionEnforceEvent = new YAHOO.util.CustomEvent("selectionEnforce", this);
209
        this.containerCollapseEvent = new YAHOO.util.CustomEvent("containerCollapse", this);
210
        this.textboxBlurEvent = new YAHOO.util.CustomEvent("textboxBlur", this);
211
        this.textboxChangeEvent = new YAHOO.util.CustomEvent("textboxChange", this);
212
        
213
        // Finish up
214
        elTextbox.setAttribute("autocomplete","off");
215
        YAHOO.widget.AutoComplete._nIndex++;
216
    }
217
    // Required arguments were not found
218
    else {
219
    }
220
};
221
222
/////////////////////////////////////////////////////////////////////////////
223
//
224
// Public member variables
225
//
226
/////////////////////////////////////////////////////////////////////////////
227
228
/**
229
 * The DataSource object that encapsulates the data used for auto completion.
230
 * This object should be an inherited object from YAHOO.widget.DataSource.
231
 *
232
 * @property dataSource
233
 * @type YAHOO.widget.DataSource
234
 */
235
YAHOO.widget.AutoComplete.prototype.dataSource = null;
236
237
/**
238
 * By default, results from local DataSources will pass through the filterResults
239
 * method to apply a client-side matching algorithm. 
240
 * 
241
 * @property applyLocalFilter
242
 * @type Boolean
243
 * @default true for local arrays and json, otherwise false
244
 */
245
YAHOO.widget.AutoComplete.prototype.applyLocalFilter = null;
246
247
/**
248
 * When applyLocalFilter is true, the local filtering algorthim can have case sensitivity
249
 * enabled. 
250
 * 
251
 * @property queryMatchCase
252
 * @type Boolean
253
 * @default false
254
 */
255
YAHOO.widget.AutoComplete.prototype.queryMatchCase = false;
256
257
/**
258
 * When applyLocalFilter is true, results can  be locally filtered to return
259
 * matching strings that "contain" the query string rather than simply "start with"
260
 * the query string.
261
 * 
262
 * @property queryMatchContains
263
 * @type Boolean
264
 * @default false
265
 */
266
YAHOO.widget.AutoComplete.prototype.queryMatchContains = false;
267
268
/**
269
 * Enables query subset matching. When the DataSource's cache is enabled and queryMatchSubset is
270
 * true, substrings of queries will return matching cached results. For
271
 * instance, if the first query is for "abc" susequent queries that start with
272
 * "abc", like "abcd", will be queried against the cache, and not the live data
273
 * source. Recommended only for DataSources that return comprehensive results
274
 * for queries with very few characters.
275
 *
276
 * @property queryMatchSubset
277
 * @type Boolean
278
 * @default false
279
 *
280
 */
281
YAHOO.widget.AutoComplete.prototype.queryMatchSubset = false;
282
283
/**
284
 * Number of characters that must be entered before querying for results. A negative value
285
 * effectively turns off the widget. A value of 0 allows queries of null or empty string
286
 * values.
287
 *
288
 * @property minQueryLength
289
 * @type Number
290
 * @default 1
291
 */
292
YAHOO.widget.AutoComplete.prototype.minQueryLength = 1;
293
294
/**
295
 * Maximum number of results to display in results container.
296
 *
297
 * @property maxResultsDisplayed
298
 * @type Number
299
 * @default 10
300
 */
301
YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed = 10;
302
303
/**
304
 * Number of seconds to delay before submitting a query request.  If a query
305
 * request is received before a previous one has completed its delay, the
306
 * previous request is cancelled and the new request is set to the delay. If 
307
 * typeAhead is also enabled, this value must always be less than the typeAheadDelay
308
 * in order to avoid certain race conditions. 
309
 *
310
 * @property queryDelay
311
 * @type Number
312
 * @default 0.2
313
 */
314
YAHOO.widget.AutoComplete.prototype.queryDelay = 0.2;
315
316
/**
317
 * If typeAhead is true, number of seconds to delay before updating input with
318
 * typeAhead value. In order to prevent certain race conditions, this value must
319
 * always be greater than the queryDelay.
320
 *
321
 * @property typeAheadDelay
322
 * @type Number
323
 * @default 0.5
324
 */
325
YAHOO.widget.AutoComplete.prototype.typeAheadDelay = 0.5;
326
327
/**
328
 * When IME usage is detected or interval detection is explicitly enabled,
329
 * AutoComplete will detect the input value at the given interval and send a
330
 * query if the value has changed.
331
 *
332
 * @property queryInterval
333
 * @type Number
334
 * @default 500
335
 */
336
YAHOO.widget.AutoComplete.prototype.queryInterval = 500;
337
338
/**
339
 * Class name of a highlighted item within results container.
340
 *
341
 * @property highlightClassName
342
 * @type String
343
 * @default "yui-ac-highlight"
344
 */
345
YAHOO.widget.AutoComplete.prototype.highlightClassName = "yui-ac-highlight";
346
347
/**
348
 * Class name of a pre-highlighted item within results container.
349
 *
350
 * @property prehighlightClassName
351
 * @type String
352
 */
353
YAHOO.widget.AutoComplete.prototype.prehighlightClassName = null;
354
355
/**
356
 * Query delimiter. A single character separator for multiple delimited
357
 * selections. Multiple delimiter characteres may be defined as an array of
358
 * strings. A null value or empty string indicates that query results cannot
359
 * be delimited. This feature is not recommended if you need forceSelection to
360
 * be true.
361
 *
362
 * @property delimChar
363
 * @type String | String[]
364
 */
365
YAHOO.widget.AutoComplete.prototype.delimChar = null;
366
367
/**
368
 * Whether or not the first item in results container should be automatically highlighted
369
 * on expand.
370
 *
371
 * @property autoHighlight
372
 * @type Boolean
373
 * @default true
374
 */
375
YAHOO.widget.AutoComplete.prototype.autoHighlight = true;
376
377
/**
378
 * If autohighlight is enabled, whether or not the input field should be automatically updated
379
 * with the first query result as the user types, auto-selecting the substring portion
380
 * of the first result that the user has not yet typed.
381
 *
382
 * @property typeAhead
383
 * @type Boolean
384
 * @default false
385
 */
386
YAHOO.widget.AutoComplete.prototype.typeAhead = false;
387
388
/**
389
 * Whether or not to animate the expansion/collapse of the results container in the
390
 * horizontal direction.
391
 *
392
 * @property animHoriz
393
 * @type Boolean
394
 * @default false
395
 */
396
YAHOO.widget.AutoComplete.prototype.animHoriz = false;
397
398
/**
399
 * Whether or not to animate the expansion/collapse of the results container in the
400
 * vertical direction.
401
 *
402
 * @property animVert
403
 * @type Boolean
404
 * @default true
405
 */
406
YAHOO.widget.AutoComplete.prototype.animVert = true;
407
408
/**
409
 * Speed of container expand/collapse animation, in seconds..
410
 *
411
 * @property animSpeed
412
 * @type Number
413
 * @default 0.3
414
 */
415
YAHOO.widget.AutoComplete.prototype.animSpeed = 0.3;
416
417
/**
418
 * Whether or not to force the user's selection to match one of the query
419
 * results. Enabling this feature essentially transforms the input field into a
420
 * &lt;select&gt; field. This feature is not recommended with delimiter character(s)
421
 * defined.
422
 *
423
 * @property forceSelection
424
 * @type Boolean
425
 * @default false
426
 */
427
YAHOO.widget.AutoComplete.prototype.forceSelection = false;
428
429
/**
430
 * Whether or not to allow browsers to cache user-typed input in the input
431
 * field. Disabling this feature will prevent the widget from setting the
432
 * autocomplete="off" on the input field. When autocomplete="off"
433
 * and users click the back button after form submission, user-typed input can
434
 * be prefilled by the browser from its cache. This caching of user input may
435
 * not be desired for sensitive data, such as credit card numbers, in which
436
 * case, implementers should consider setting allowBrowserAutocomplete to false.
437
 *
438
 * @property allowBrowserAutocomplete
439
 * @type Boolean
440
 * @default true
441
 */
442
YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete = true;
443
444
/**
445
 * Enabling this feature prevents the toggling of the container to a collapsed state.
446
 * Setting to true does not automatically trigger the opening of the container.
447
 * Implementers are advised to pre-load the container with an explicit "sendQuery()" call.   
448
 *
449
 * @property alwaysShowContainer
450
 * @type Boolean
451
 * @default false
452
 */
453
YAHOO.widget.AutoComplete.prototype.alwaysShowContainer = false;
454
455
/**
456
 * Whether or not to use an iFrame to layer over Windows form elements in
457
 * IE. Set to true only when the results container will be on top of a
458
 * &lt;select&gt; field in IE and thus exposed to the IE z-index bug (i.e.,
459
 * 5.5 < IE < 7).
460
 *
461
 * @property useIFrame
462
 * @type Boolean
463
 * @default false
464
 */
465
YAHOO.widget.AutoComplete.prototype.useIFrame = false;
466
467
/**
468
 * Whether or not the results container should have a shadow.
469
 *
470
 * @property useShadow
471
 * @type Boolean
472
 * @default false
473
 */
474
YAHOO.widget.AutoComplete.prototype.useShadow = false;
475
476
/**
477
 * Whether or not the input field should be updated with selections.
478
 *
479
 * @property suppressInputUpdate
480
 * @type Boolean
481
 * @default false
482
 */
483
YAHOO.widget.AutoComplete.prototype.suppressInputUpdate = false;
484
485
/**
486
 * For backward compatibility to pre-2.6.0 formatResults() signatures, setting
487
 * resultsTypeList to true will take each object literal result returned by
488
 * DataSource and flatten into an array.  
489
 *
490
 * @property resultTypeList
491
 * @type Boolean
492
 * @default true
493
 */
494
YAHOO.widget.AutoComplete.prototype.resultTypeList = true;
495
496
/**
497
 * For XHR DataSources, AutoComplete will automatically insert a "?" between the server URI and 
498
 * the "query" param/value pair. To prevent this behavior, implementers should
499
 * set this value to false. To more fully customize the query syntax, implementers
500
 * should override the generateRequest() method. 
501
 *
502
 * @property queryQuestionMark
503
 * @type Boolean
504
 * @default true
505
 */
506
YAHOO.widget.AutoComplete.prototype.queryQuestionMark = true;
507
508
/**
509
 * If true, before each time the container expands, the container element will be
510
 * positioned to snap to the bottom-left corner of the input element. If
511
 * autoSnapContainer is set to false, this positioning will not be done.  
512
 *
513
 * @property autoSnapContainer
514
 * @type Boolean
515
 * @default true
516
 */
517
YAHOO.widget.AutoComplete.prototype.autoSnapContainer = true;
518
519
/////////////////////////////////////////////////////////////////////////////
520
//
521
// Public methods
522
//
523
/////////////////////////////////////////////////////////////////////////////
524
525
 /**
526
 * Public accessor to the unique name of the AutoComplete instance.
527
 *
528
 * @method toString
529
 * @return {String} Unique name of the AutoComplete instance.
530
 */
531
YAHOO.widget.AutoComplete.prototype.toString = function() {
532
    return "AutoComplete " + this._sName;
533
};
534
535
 /**
536
 * Returns DOM reference to input element.
537
 *
538
 * @method getInputEl
539
 * @return {HTMLELement} DOM reference to input element.
540
 */
541
YAHOO.widget.AutoComplete.prototype.getInputEl = function() {
542
    return this._elTextbox;
543
};
544
545
 /**
546
 * Returns DOM reference to container element.
547
 *
548
 * @method getContainerEl
549
 * @return {HTMLELement} DOM reference to container element.
550
 */
551
YAHOO.widget.AutoComplete.prototype.getContainerEl = function() {
552
    return this._elContainer;
553
};
554
555
 /**
556
 * Returns true if widget instance is currently active.
557
 *
558
 * @method isFocused
559
 * @return {Boolean} Returns true if widget instance is currently active.
560
 */
561
YAHOO.widget.AutoComplete.prototype.isFocused = function() {
562
    return this._bFocused;
563
};
564
565
 /**
566
 * Returns true if container is in an expanded state, false otherwise.
567
 *
568
 * @method isContainerOpen
569
 * @return {Boolean} Returns true if container is in an expanded state, false otherwise.
570
 */
571
YAHOO.widget.AutoComplete.prototype.isContainerOpen = function() {
572
    return this._bContainerOpen;
573
};
574
575
/**
576
 * Public accessor to the &lt;ul&gt; element that displays query results within the results container.
577
 *
578
 * @method getListEl
579
 * @return {HTMLElement[]} Reference to &lt;ul&gt; element within the results container.
580
 */
581
YAHOO.widget.AutoComplete.prototype.getListEl = function() {
582
    return this._elList;
583
};
584
585
/**
586
 * Public accessor to the matching string associated with a given &lt;li&gt; result.
587
 *
588
 * @method getListItemMatch
589
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
590
 * @return {String} Matching string.
591
 */
592
YAHOO.widget.AutoComplete.prototype.getListItemMatch = function(elListItem) {
593
    if(elListItem._sResultMatch) {
594
        return elListItem._sResultMatch;
595
    }
596
    else {
597
        return null;
598
    }
599
};
600
601
/**
602
 * Public accessor to the result data associated with a given &lt;li&gt; result.
603
 *
604
 * @method getListItemData
605
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
606
 * @return {Object} Result data.
607
 */
608
YAHOO.widget.AutoComplete.prototype.getListItemData = function(elListItem) {
609
    if(elListItem._oResultData) {
610
        return elListItem._oResultData;
611
    }
612
    else {
613
        return null;
614
    }
615
};
616
617
/**
618
 * Public accessor to the index of the associated with a given &lt;li&gt; result.
619
 *
620
 * @method getListItemIndex
621
 * @param elListItem {HTMLElement} Reference to &lt;LI&gt; element.
622
 * @return {Number} Index.
623
 */
624
YAHOO.widget.AutoComplete.prototype.getListItemIndex = function(elListItem) {
625
    if(YAHOO.lang.isNumber(elListItem._nItemIndex)) {
626
        return elListItem._nItemIndex;
627
    }
628
    else {
629
        return null;
630
    }
631
};
632
633
/**
634
 * Sets HTML markup for the results container header. This markup will be
635
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-hd".
636
 *
637
 * @method setHeader
638
 * @param sHeader {String} HTML markup for results container header.
639
 */
640
YAHOO.widget.AutoComplete.prototype.setHeader = function(sHeader) {
641
    if(this._elHeader) {
642
        var elHeader = this._elHeader;
643
        if(sHeader) {
644
            elHeader.innerHTML = sHeader;
645
            elHeader.style.display = "";
646
        }
647
        else {
648
            elHeader.innerHTML = "";
649
            elHeader.style.display = "none";
650
        }
651
    }
652
};
653
654
/**
655
 * Sets HTML markup for the results container footer. This markup will be
656
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-ft".
657
 *
658
 * @method setFooter
659
 * @param sFooter {String} HTML markup for results container footer.
660
 */
661
YAHOO.widget.AutoComplete.prototype.setFooter = function(sFooter) {
662
    if(this._elFooter) {
663
        var elFooter = this._elFooter;
664
        if(sFooter) {
665
                elFooter.innerHTML = sFooter;
666
                elFooter.style.display = "";
667
        }
668
        else {
669
            elFooter.innerHTML = "";
670
            elFooter.style.display = "none";
671
        }
672
    }
673
};
674
675
/**
676
 * Sets HTML markup for the results container body. This markup will be
677
 * inserted within a &lt;div&gt; tag with a class of "yui-ac-bd".
678
 *
679
 * @method setBody
680
 * @param sBody {String} HTML markup for results container body.
681
 */
682
YAHOO.widget.AutoComplete.prototype.setBody = function(sBody) {
683
    if(this._elBody) {
684
        var elBody = this._elBody;
685
        YAHOO.util.Event.purgeElement(elBody, true);
686
        if(sBody) {
687
            elBody.innerHTML = sBody;
688
            elBody.style.display = "";
689
        }
690
        else {
691
            elBody.innerHTML = "";
692
            elBody.style.display = "none";
693
        }
694
        this._elList = null;
695
    }
696
};
697
698
/**
699
* A function that converts an AutoComplete query into a request value which is then
700
* passed to the DataSource's sendRequest method in order to retrieve data for 
701
* the query. By default, returns a String with the syntax: "query={query}"
702
* Implementers can customize this method for custom request syntaxes.
703
* 
704
* @method generateRequest
705
* @param sQuery {String} Query string
706
* @return {MIXED} Request
707
*/
708
YAHOO.widget.AutoComplete.prototype.generateRequest = function(sQuery) {
709
    var dataType = this.dataSource.dataType;
710
    
711
    // Transform query string in to a request for remote data
712
    // By default, local data doesn't need a transformation, just passes along the query as is.
713
    if(dataType === YAHOO.util.DataSourceBase.TYPE_XHR) {
714
        // By default, XHR GET requests look like "{scriptURI}?{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
715
        if(!this.dataSource.connMethodPost) {
716
            sQuery = (this.queryQuestionMark ? "?" : "") + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
717
                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");        
718
        }
719
        // By default, XHR POST bodies are sent to the {scriptURI} like "{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
720
        else {
721
            sQuery = (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
722
                (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");
723
        }
724
    }
725
    // By default, remote script node requests look like "{scriptURI}&{scriptCallbackParam}={callbackString}&{scriptQueryParam}={sQuery}&{scriptQueryAppend}"
726
    else if(dataType === YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE) {
727
        sQuery = "&" + (this.dataSource.scriptQueryParam || "query") + "=" + sQuery + 
728
            (this.dataSource.scriptQueryAppend ? ("&" + this.dataSource.scriptQueryAppend) : "");    
729
    }
730
    
731
    return sQuery;
732
};
733
734
/**
735
 * Makes query request to the DataSource.
736
 *
737
 * @method sendQuery
738
 * @param sQuery {String} Query string.
739
 */
740
YAHOO.widget.AutoComplete.prototype.sendQuery = function(sQuery) {
741
    // Activate focus for a new interaction
742
    this._bFocused = true;
743
    
744
    // Adjust programatically sent queries to look like they were input by user
745
    // when delimiters are enabled
746
    var newQuery = (this.delimChar) ? this._elTextbox.value + sQuery : sQuery;
747
    this._sendQuery(newQuery);
748
};
749
750
/**
751
 * Snaps container to bottom-left corner of input element
752
 *
753
 * @method snapContainer
754
 */
755
YAHOO.widget.AutoComplete.prototype.snapContainer = function() {
756
    var oTextbox = this._elTextbox,
757
        pos = YAHOO.util.Dom.getXY(oTextbox);
758
    pos[1] += YAHOO.util.Dom.get(oTextbox).offsetHeight + 2;
759
    YAHOO.util.Dom.setXY(this._elContainer,pos);
760
};
761
762
/**
763
 * Expands container.
764
 *
765
 * @method expandContainer
766
 */
767
YAHOO.widget.AutoComplete.prototype.expandContainer = function() {
768
    this._toggleContainer(true);
769
};
770
771
/**
772
 * Collapses container.
773
 *
774
 * @method collapseContainer
775
 */
776
YAHOO.widget.AutoComplete.prototype.collapseContainer = function() {
777
    this._toggleContainer(false);
778
};
779
780
/**
781
 * Clears entire list of suggestions.
782
 *
783
 * @method clearList
784
 */
785
YAHOO.widget.AutoComplete.prototype.clearList = function() {
786
    var allItems = this._elList.childNodes,
787
        i=allItems.length-1;
788
    for(; i>-1; i--) {
789
          allItems[i].style.display = "none";
790
    }
791
};
792
793
/**
794
 * Handles subset matching for when queryMatchSubset is enabled.
795
 *
796
 * @method getSubsetMatches
797
 * @param sQuery {String} Query string.
798
 * @return {Object} oParsedResponse or null. 
799
 */
800
YAHOO.widget.AutoComplete.prototype.getSubsetMatches = function(sQuery) {
801
    var subQuery, oCachedResponse, subRequest;
802
    // Loop through substrings of each cached element's query property...
803
    for(var i = sQuery.length; i >= this.minQueryLength ; i--) {
804
        subRequest = this.generateRequest(sQuery.substr(0,i));
805
        this.dataRequestEvent.fire(this, subQuery, subRequest);
806
        
807
        // If a substring of the query is found in the cache
808
        oCachedResponse = this.dataSource.getCachedResponse(subRequest);
809
        if(oCachedResponse) {
810
            return this.filterResults.apply(this.dataSource, [sQuery, oCachedResponse, oCachedResponse, {scope:this}]);
811
        }
812
    }
813
    return null;
814
};
815
816
/**
817
 * Executed by DataSource (within DataSource scope via doBeforeParseData()) to
818
 * handle responseStripAfter cleanup.
819
 *
820
 * @method preparseRawResponse
821
 * @param sQuery {String} Query string.
822
 * @return {Object} oParsedResponse or null. 
823
 */
824
YAHOO.widget.AutoComplete.prototype.preparseRawResponse = function(oRequest, oFullResponse, oCallback) {
825
    var nEnd = ((this.responseStripAfter !== "") && (oFullResponse.indexOf)) ?
826
        oFullResponse.indexOf(this.responseStripAfter) : -1;
827
    if(nEnd != -1) {
828
        oFullResponse = oFullResponse.substring(0,nEnd);
829
    }
830
    return oFullResponse;
831
};
832
833
/**
834
 * Executed by DataSource (within DataSource scope via doBeforeCallback()) to
835
 * filter results through a simple client-side matching algorithm. 
836
 *
837
 * @method filterResults
838
 * @param sQuery {String} Original request.
839
 * @param oFullResponse {Object} Full response object.
840
 * @param oParsedResponse {Object} Parsed response object.
841
 * @param oCallback {Object} Callback object. 
842
 * @return {Object} Filtered response object.
843
 */
844
845
YAHOO.widget.AutoComplete.prototype.filterResults = function(sQuery, oFullResponse, oParsedResponse, oCallback) {
846
    // If AC has passed a query string value back to itself, grab it
847
    if(oCallback && oCallback.argument && oCallback.argument.query) {
848
        sQuery = oCallback.argument.query;
849
    }
850
851
    // Only if a query string is available to match against
852
    if(sQuery && sQuery !== "") {
853
        // First make a copy of the oParseResponse
854
        oParsedResponse = YAHOO.widget.AutoComplete._cloneObject(oParsedResponse);
855
        
856
        var oAC = oCallback.scope,
857
            oDS = this,
858
            allResults = oParsedResponse.results, // the array of results
859
            filteredResults = [], // container for filtered results,
860
            nMax = oAC.maxResultsDisplayed, // max to find
861
            bMatchCase = (oDS.queryMatchCase || oAC.queryMatchCase), // backward compat
862
            bMatchContains = (oDS.queryMatchContains || oAC.queryMatchContains); // backward compat
863
            
864
        // Loop through each result object...
865
        for(var i=0, len=allResults.length; i<len; i++) {
866
            var oResult = allResults[i];
867
868
            // Grab the data to match against from the result object...
869
            var sResult = null;
870
            
871
            // Result object is a simple string already
872
            if(YAHOO.lang.isString(oResult)) {
873
                sResult = oResult;
874
            }
875
            // Result object is an array of strings
876
            else if(YAHOO.lang.isArray(oResult)) {
877
                sResult = oResult[0];
878
            
879
            }
880
            // Result object is an object literal of strings
881
            else if(this.responseSchema.fields) {
882
                var key = this.responseSchema.fields[0].key || this.responseSchema.fields[0];
883
                sResult = oResult[key];
884
            }
885
            // Backwards compatibility
886
            else if(this.key) {
887
                sResult = oResult[this.key];
888
            }
889
            
890
            if(YAHOO.lang.isString(sResult)) {
891
                
892
                var sKeyIndex = (bMatchCase) ?
893
                sResult.indexOf(decodeURIComponent(sQuery)) :
894
                sResult.toLowerCase().indexOf(decodeURIComponent(sQuery).toLowerCase());
895
896
                // A STARTSWITH match is when the query is found at the beginning of the key string...
897
                if((!bMatchContains && (sKeyIndex === 0)) ||
898
                // A CONTAINS match is when the query is found anywhere within the key string...
899
                (bMatchContains && (sKeyIndex > -1))) {
900
                    // Stash the match
901
                    filteredResults.push(oResult);
902
                }
903
            }
904
            
905
            // Filter no more if maxResultsDisplayed is reached
906
            if(len>nMax && filteredResults.length===nMax) {
907
                break;
908
            }
909
        }
910
        oParsedResponse.results = filteredResults;
911
    }
912
    else {
913
    }
914
    
915
    return oParsedResponse;
916
};
917
918
/**
919
 * Handles response for display. This is the callback function method passed to
920
 * YAHOO.util.DataSourceBase#sendRequest so results from the DataSource are
921
 * returned to the AutoComplete instance.
922
 *
923
 * @method handleResponse
924
 * @param sQuery {String} Original request.
925
 * @param oResponse {Object} Response object.
926
 * @param oPayload {MIXED} (optional) Additional argument(s)
927
 */
928
YAHOO.widget.AutoComplete.prototype.handleResponse = function(sQuery, oResponse, oPayload) {
929
    if((this instanceof YAHOO.widget.AutoComplete) && this._sName) {
930
        this._populateList(sQuery, oResponse, oPayload);
931
    }
932
};
933
934
/**
935
 * Overridable method called before container is loaded with result data.
936
 *
937
 * @method doBeforeLoadData
938
 * @param sQuery {String} Original request.
939
 * @param oResponse {Object} Response object.
940
 * @param oPayload {MIXED} (optional) Additional argument(s)
941
 * @return {Boolean} Return true to continue loading data, false to cancel.
942
 */
943
YAHOO.widget.AutoComplete.prototype.doBeforeLoadData = function(sQuery, oResponse, oPayload) {
944
    return true;
945
};
946
947
/**
948
 * Overridable method that returns HTML markup for one result to be populated
949
 * as innerHTML of an &lt;LI&gt; element. 
950
 *
951
 * @method formatResult
952
 * @param oResultData {Object} Result data object.
953
 * @param sQuery {String} The corresponding query string.
954
 * @param sResultMatch {HTMLElement} The current query string. 
955
 * @return {String} HTML markup of formatted result data.
956
 */
957
YAHOO.widget.AutoComplete.prototype.formatResult = function(oResultData, sQuery, sResultMatch) {
958
    var sMarkup = (sResultMatch) ? sResultMatch : "";
959
    return sMarkup;
960
};
961
962
/**
963
 * Overridable method called before container expands allows implementers to access data
964
 * and DOM elements.
965
 *
966
 * @method doBeforeExpandContainer
967
 * @param elTextbox {HTMLElement} The text input box.
968
 * @param elContainer {HTMLElement} The container element.
969
 * @param sQuery {String} The query string.
970
 * @param aResults {Object[]}  An array of query results.
971
 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
972
 */
973
YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
974
    return true;
975
};
976
977
978
/**
979
 * Nulls out the entire AutoComplete instance and related objects, removes attached
980
 * event listeners, and clears out DOM elements inside the container. After
981
 * calling this method, the instance reference should be expliclitly nulled by
982
 * implementer, as in myAutoComplete = null. Use with caution!
983
 *
984
 * @method destroy
985
 */
986
YAHOO.widget.AutoComplete.prototype.destroy = function() {
987
    var instanceName = this.toString();
988
    var elInput = this._elTextbox;
989
    var elContainer = this._elContainer;
990
991
    // Unhook custom events
992
    this.textboxFocusEvent.unsubscribeAll();
993
    this.textboxKeyEvent.unsubscribeAll();
994
    this.dataRequestEvent.unsubscribeAll();
995
    this.dataReturnEvent.unsubscribeAll();
996
    this.dataErrorEvent.unsubscribeAll();
997
    this.containerPopulateEvent.unsubscribeAll();
998
    this.containerExpandEvent.unsubscribeAll();
999
    this.typeAheadEvent.unsubscribeAll();
1000
    this.itemMouseOverEvent.unsubscribeAll();
1001
    this.itemMouseOutEvent.unsubscribeAll();
1002
    this.itemArrowToEvent.unsubscribeAll();
1003
    this.itemArrowFromEvent.unsubscribeAll();
1004
    this.itemSelectEvent.unsubscribeAll();
1005
    this.unmatchedItemSelectEvent.unsubscribeAll();
1006
    this.selectionEnforceEvent.unsubscribeAll();
1007
    this.containerCollapseEvent.unsubscribeAll();
1008
    this.textboxBlurEvent.unsubscribeAll();
1009
    this.textboxChangeEvent.unsubscribeAll();
1010
1011
    // Unhook DOM events
1012
    YAHOO.util.Event.purgeElement(elInput, true);
1013
    YAHOO.util.Event.purgeElement(elContainer, true);
1014
1015
    // Remove DOM elements
1016
    elContainer.innerHTML = "";
1017
1018
    // Null out objects
1019
    for(var key in this) {
1020
        if(YAHOO.lang.hasOwnProperty(this, key)) {
1021
            this[key] = null;
1022
        }
1023
    }
1024
1025
};
1026
1027
/////////////////////////////////////////////////////////////////////////////
1028
//
1029
// Public events
1030
//
1031
/////////////////////////////////////////////////////////////////////////////
1032
1033
/**
1034
 * Fired when the input field receives focus.
1035
 *
1036
 * @event textboxFocusEvent
1037
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1038
 */
1039
YAHOO.widget.AutoComplete.prototype.textboxFocusEvent = null;
1040
1041
/**
1042
 * Fired when the input field receives key input.
1043
 *
1044
 * @event textboxKeyEvent
1045
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1046
 * @param nKeycode {Number} The keycode number.
1047
 */
1048
YAHOO.widget.AutoComplete.prototype.textboxKeyEvent = null;
1049
1050
/**
1051
 * Fired when the AutoComplete instance makes a request to the DataSource.
1052
 * 
1053
 * @event dataRequestEvent
1054
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1055
 * @param sQuery {String} The query string. 
1056
 * @param oRequest {Object} The request.
1057
 */
1058
YAHOO.widget.AutoComplete.prototype.dataRequestEvent = null;
1059
1060
/**
1061
 * Fired when the AutoComplete instance receives query results from the data
1062
 * source.
1063
 *
1064
 * @event dataReturnEvent
1065
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1066
 * @param sQuery {String} The query string.
1067
 * @param aResults {Object[]} Results array.
1068
 */
1069
YAHOO.widget.AutoComplete.prototype.dataReturnEvent = null;
1070
1071
/**
1072
 * Fired when the AutoComplete instance does not receive query results from the
1073
 * DataSource due to an error.
1074
 *
1075
 * @event dataErrorEvent
1076
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1077
 * @param sQuery {String} The query string.
1078
 * @param oResponse {Object} The response object, if available.
1079
 */
1080
YAHOO.widget.AutoComplete.prototype.dataErrorEvent = null;
1081
1082
/**
1083
 * Fired when the results container is populated.
1084
 *
1085
 * @event containerPopulateEvent
1086
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1087
 */
1088
YAHOO.widget.AutoComplete.prototype.containerPopulateEvent = null;
1089
1090
/**
1091
 * Fired when the results container is expanded.
1092
 *
1093
 * @event containerExpandEvent
1094
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1095
 */
1096
YAHOO.widget.AutoComplete.prototype.containerExpandEvent = null;
1097
1098
/**
1099
 * Fired when the input field has been prefilled by the type-ahead
1100
 * feature. 
1101
 *
1102
 * @event typeAheadEvent
1103
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1104
 * @param sQuery {String} The query string.
1105
 * @param sPrefill {String} The prefill string.
1106
 */
1107
YAHOO.widget.AutoComplete.prototype.typeAheadEvent = null;
1108
1109
/**
1110
 * Fired when result item has been moused over.
1111
 *
1112
 * @event itemMouseOverEvent
1113
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1114
 * @param elItem {HTMLElement} The &lt;li&gt element item moused to.
1115
 */
1116
YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent = null;
1117
1118
/**
1119
 * Fired when result item has been moused out.
1120
 *
1121
 * @event itemMouseOutEvent
1122
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1123
 * @param elItem {HTMLElement} The &lt;li&gt; element item moused from.
1124
 */
1125
YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent = null;
1126
1127
/**
1128
 * Fired when result item has been arrowed to. 
1129
 *
1130
 * @event itemArrowToEvent
1131
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1132
 * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed to.
1133
 */
1134
YAHOO.widget.AutoComplete.prototype.itemArrowToEvent = null;
1135
1136
/**
1137
 * Fired when result item has been arrowed away from.
1138
 *
1139
 * @event itemArrowFromEvent
1140
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1141
 * @param elItem {HTMLElement} The &lt;li&gt; element item arrowed from.
1142
 */
1143
YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent = null;
1144
1145
/**
1146
 * Fired when an item is selected via mouse click, ENTER key, or TAB key.
1147
 *
1148
 * @event itemSelectEvent
1149
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1150
 * @param elItem {HTMLElement} The selected &lt;li&gt; element item.
1151
 * @param oData {Object} The data returned for the item, either as an object,
1152
 * or mapped from the schema into an array.
1153
 */
1154
YAHOO.widget.AutoComplete.prototype.itemSelectEvent = null;
1155
1156
/**
1157
 * Fired when a user selection does not match any of the displayed result items.
1158
 *
1159
 * @event unmatchedItemSelectEvent
1160
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1161
 * @param sSelection {String} The selected string.  
1162
 */
1163
YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent = null;
1164
1165
/**
1166
 * Fired if forceSelection is enabled and the user's input has been cleared
1167
 * because it did not match one of the returned query results.
1168
 *
1169
 * @event selectionEnforceEvent
1170
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1171
 * @param sClearedValue {String} The cleared value (including delimiters if applicable). 
1172
 */
1173
YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent = null;
1174
1175
/**
1176
 * Fired when the results container is collapsed.
1177
 *
1178
 * @event containerCollapseEvent
1179
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1180
 */
1181
YAHOO.widget.AutoComplete.prototype.containerCollapseEvent = null;
1182
1183
/**
1184
 * Fired when the input field loses focus.
1185
 *
1186
 * @event textboxBlurEvent
1187
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1188
 */
1189
YAHOO.widget.AutoComplete.prototype.textboxBlurEvent = null;
1190
1191
/**
1192
 * Fired when the input field value has changed when it loses focus.
1193
 *
1194
 * @event textboxChangeEvent
1195
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1196
 */
1197
YAHOO.widget.AutoComplete.prototype.textboxChangeEvent = null;
1198
1199
/////////////////////////////////////////////////////////////////////////////
1200
//
1201
// Private member variables
1202
//
1203
/////////////////////////////////////////////////////////////////////////////
1204
1205
/**
1206
 * Internal class variable to index multiple AutoComplete instances.
1207
 *
1208
 * @property _nIndex
1209
 * @type Number
1210
 * @default 0
1211
 * @private
1212
 */
1213
YAHOO.widget.AutoComplete._nIndex = 0;
1214
1215
/**
1216
 * Name of AutoComplete instance.
1217
 *
1218
 * @property _sName
1219
 * @type String
1220
 * @private
1221
 */
1222
YAHOO.widget.AutoComplete.prototype._sName = null;
1223
1224
/**
1225
 * Text input field DOM element.
1226
 *
1227
 * @property _elTextbox
1228
 * @type HTMLElement
1229
 * @private
1230
 */
1231
YAHOO.widget.AutoComplete.prototype._elTextbox = null;
1232
1233
/**
1234
 * Container DOM element.
1235
 *
1236
 * @property _elContainer
1237
 * @type HTMLElement
1238
 * @private
1239
 */
1240
YAHOO.widget.AutoComplete.prototype._elContainer = null;
1241
1242
/**
1243
 * Reference to content element within container element.
1244
 *
1245
 * @property _elContent
1246
 * @type HTMLElement
1247
 * @private
1248
 */
1249
YAHOO.widget.AutoComplete.prototype._elContent = null;
1250
1251
/**
1252
 * Reference to header element within content element.
1253
 *
1254
 * @property _elHeader
1255
 * @type HTMLElement
1256
 * @private
1257
 */
1258
YAHOO.widget.AutoComplete.prototype._elHeader = null;
1259
1260
/**
1261
 * Reference to body element within content element.
1262
 *
1263
 * @property _elBody
1264
 * @type HTMLElement
1265
 * @private
1266
 */
1267
YAHOO.widget.AutoComplete.prototype._elBody = null;
1268
1269
/**
1270
 * Reference to footer element within content element.
1271
 *
1272
 * @property _elFooter
1273
 * @type HTMLElement
1274
 * @private
1275
 */
1276
YAHOO.widget.AutoComplete.prototype._elFooter = null;
1277
1278
/**
1279
 * Reference to shadow element within container element.
1280
 *
1281
 * @property _elShadow
1282
 * @type HTMLElement
1283
 * @private
1284
 */
1285
YAHOO.widget.AutoComplete.prototype._elShadow = null;
1286
1287
/**
1288
 * Reference to iframe element within container element.
1289
 *
1290
 * @property _elIFrame
1291
 * @type HTMLElement
1292
 * @private
1293
 */
1294
YAHOO.widget.AutoComplete.prototype._elIFrame = null;
1295
1296
/**
1297
 * Whether or not the widget instance is currently active. If query results come back
1298
 * but the user has already moved on, do not proceed with auto complete behavior.
1299
 *
1300
 * @property _bFocused
1301
 * @type Boolean
1302
 * @private
1303
 */
1304
YAHOO.widget.AutoComplete.prototype._bFocused = false;
1305
1306
/**
1307
 * Animation instance for container expand/collapse.
1308
 *
1309
 * @property _oAnim
1310
 * @type Boolean
1311
 * @private
1312
 */
1313
YAHOO.widget.AutoComplete.prototype._oAnim = null;
1314
1315
/**
1316
 * Whether or not the results container is currently open.
1317
 *
1318
 * @property _bContainerOpen
1319
 * @type Boolean
1320
 * @private
1321
 */
1322
YAHOO.widget.AutoComplete.prototype._bContainerOpen = false;
1323
1324
/**
1325
 * Whether or not the mouse is currently over the results
1326
 * container. This is necessary in order to prevent clicks on container items
1327
 * from being text input field blur events.
1328
 *
1329
 * @property _bOverContainer
1330
 * @type Boolean
1331
 * @private
1332
 */
1333
YAHOO.widget.AutoComplete.prototype._bOverContainer = false;
1334
1335
/**
1336
 * Internal reference to &lt;ul&gt; elements that contains query results within the
1337
 * results container.
1338
 *
1339
 * @property _elList
1340
 * @type HTMLElement
1341
 * @private
1342
 */
1343
YAHOO.widget.AutoComplete.prototype._elList = null;
1344
1345
/*
1346
 * Array of &lt;li&gt; elements references that contain query results within the
1347
 * results container.
1348
 *
1349
 * @property _aListItemEls
1350
 * @type HTMLElement[]
1351
 * @private
1352
 */
1353
//YAHOO.widget.AutoComplete.prototype._aListItemEls = null;
1354
1355
/**
1356
 * Number of &lt;li&gt; elements currently displayed in results container.
1357
 *
1358
 * @property _nDisplayedItems
1359
 * @type Number
1360
 * @private
1361
 */
1362
YAHOO.widget.AutoComplete.prototype._nDisplayedItems = 0;
1363
1364
/*
1365
 * Internal count of &lt;li&gt; elements displayed and hidden in results container.
1366
 *
1367
 * @property _maxResultsDisplayed
1368
 * @type Number
1369
 * @private
1370
 */
1371
//YAHOO.widget.AutoComplete.prototype._maxResultsDisplayed = 0;
1372
1373
/**
1374
 * Current query string
1375
 *
1376
 * @property _sCurQuery
1377
 * @type String
1378
 * @private
1379
 */
1380
YAHOO.widget.AutoComplete.prototype._sCurQuery = null;
1381
1382
/**
1383
 * Selections from previous queries (for saving delimited queries).
1384
 *
1385
 * @property _sPastSelections
1386
 * @type String
1387
 * @default "" 
1388
 * @private
1389
 */
1390
YAHOO.widget.AutoComplete.prototype._sPastSelections = "";
1391
1392
/**
1393
 * Stores initial input value used to determine if textboxChangeEvent should be fired.
1394
 *
1395
 * @property _sInitInputValue
1396
 * @type String
1397
 * @private
1398
 */
1399
YAHOO.widget.AutoComplete.prototype._sInitInputValue = null;
1400
1401
/**
1402
 * Pointer to the currently highlighted &lt;li&gt; element in the container.
1403
 *
1404
 * @property _elCurListItem
1405
 * @type HTMLElement
1406
 * @private
1407
 */
1408
YAHOO.widget.AutoComplete.prototype._elCurListItem = null;
1409
1410
/**
1411
 * Pointer to the currently pre-highlighted &lt;li&gt; element in the container.
1412
 *
1413
 * @property _elCurPrehighlightItem
1414
 * @type HTMLElement
1415
 * @private
1416
 */
1417
YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem = null;
1418
1419
/**
1420
 * Whether or not an item has been selected since the container was populated
1421
 * with results. Reset to false by _populateList, and set to true when item is
1422
 * selected.
1423
 *
1424
 * @property _bItemSelected
1425
 * @type Boolean
1426
 * @private
1427
 */
1428
YAHOO.widget.AutoComplete.prototype._bItemSelected = false;
1429
1430
/**
1431
 * Key code of the last key pressed in textbox.
1432
 *
1433
 * @property _nKeyCode
1434
 * @type Number
1435
 * @private
1436
 */
1437
YAHOO.widget.AutoComplete.prototype._nKeyCode = null;
1438
1439
/**
1440
 * Delay timeout ID.
1441
 *
1442
 * @property _nDelayID
1443
 * @type Number
1444
 * @private
1445
 */
1446
YAHOO.widget.AutoComplete.prototype._nDelayID = -1;
1447
1448
/**
1449
 * TypeAhead delay timeout ID.
1450
 *
1451
 * @property _nTypeAheadDelayID
1452
 * @type Number
1453
 * @private
1454
 */
1455
YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID = -1;
1456
1457
/**
1458
 * Src to iFrame used when useIFrame = true. Supports implementations over SSL
1459
 * as well.
1460
 *
1461
 * @property _iFrameSrc
1462
 * @type String
1463
 * @private
1464
 */
1465
YAHOO.widget.AutoComplete.prototype._iFrameSrc = "javascript:false;";
1466
1467
/**
1468
 * For users typing via certain IMEs, queries must be triggered by intervals,
1469
 * since key events yet supported across all browsers for all IMEs.
1470
 *
1471
 * @property _queryInterval
1472
 * @type Object
1473
 * @private
1474
 */
1475
YAHOO.widget.AutoComplete.prototype._queryInterval = null;
1476
1477
/**
1478
 * Internal tracker to last known textbox value, used to determine whether or not
1479
 * to trigger a query via interval for certain IME users.
1480
 *
1481
 * @event _sLastTextboxValue
1482
 * @type String
1483
 * @private
1484
 */
1485
YAHOO.widget.AutoComplete.prototype._sLastTextboxValue = null;
1486
1487
/////////////////////////////////////////////////////////////////////////////
1488
//
1489
// Private methods
1490
//
1491
/////////////////////////////////////////////////////////////////////////////
1492
1493
/**
1494
 * Updates and validates latest public config properties.
1495
 *
1496
 * @method __initProps
1497
 * @private
1498
 */
1499
YAHOO.widget.AutoComplete.prototype._initProps = function() {
1500
    // Correct any invalid values
1501
    var minQueryLength = this.minQueryLength;
1502
    if(!YAHOO.lang.isNumber(minQueryLength)) {
1503
        this.minQueryLength = 1;
1504
    }
1505
    var maxResultsDisplayed = this.maxResultsDisplayed;
1506
    if(!YAHOO.lang.isNumber(maxResultsDisplayed) || (maxResultsDisplayed < 1)) {
1507
        this.maxResultsDisplayed = 10;
1508
    }
1509
    var queryDelay = this.queryDelay;
1510
    if(!YAHOO.lang.isNumber(queryDelay) || (queryDelay < 0)) {
1511
        this.queryDelay = 0.2;
1512
    }
1513
    var typeAheadDelay = this.typeAheadDelay;
1514
    if(!YAHOO.lang.isNumber(typeAheadDelay) || (typeAheadDelay < 0)) {
1515
        this.typeAheadDelay = 0.2;
1516
    }
1517
    var delimChar = this.delimChar;
1518
    if(YAHOO.lang.isString(delimChar) && (delimChar.length > 0)) {
1519
        this.delimChar = [delimChar];
1520
    }
1521
    else if(!YAHOO.lang.isArray(delimChar)) {
1522
        this.delimChar = null;
1523
    }
1524
    var animSpeed = this.animSpeed;
1525
    if((this.animHoriz || this.animVert) && YAHOO.util.Anim) {
1526
        if(!YAHOO.lang.isNumber(animSpeed) || (animSpeed < 0)) {
1527
            this.animSpeed = 0.3;
1528
        }
1529
        if(!this._oAnim ) {
1530
            this._oAnim = new YAHOO.util.Anim(this._elContent, {}, this.animSpeed);
1531
        }
1532
        else {
1533
            this._oAnim.duration = this.animSpeed;
1534
        }
1535
    }
1536
    if(this.forceSelection && delimChar) {
1537
    }
1538
};
1539
1540
/**
1541
 * Initializes the results container helpers if they are enabled and do
1542
 * not exist
1543
 *
1544
 * @method _initContainerHelperEls
1545
 * @private
1546
 */
1547
YAHOO.widget.AutoComplete.prototype._initContainerHelperEls = function() {
1548
    if(this.useShadow && !this._elShadow) {
1549
        var elShadow = document.createElement("div");
1550
        elShadow.className = "yui-ac-shadow";
1551
        elShadow.style.width = 0;
1552
        elShadow.style.height = 0;
1553
        this._elShadow = this._elContainer.appendChild(elShadow);
1554
    }
1555
    if(this.useIFrame && !this._elIFrame) {
1556
        var elIFrame = document.createElement("iframe");
1557
        elIFrame.src = this._iFrameSrc;
1558
        elIFrame.frameBorder = 0;
1559
        elIFrame.scrolling = "no";
1560
        elIFrame.style.position = "absolute";
1561
        elIFrame.style.width = 0;
1562
        elIFrame.style.height = 0;
1563
        elIFrame.style.padding = 0;
1564
        elIFrame.tabIndex = -1;
1565
        elIFrame.role = "presentation";
1566
        elIFrame.title = "Presentational iframe shim";
1567
        this._elIFrame = this._elContainer.appendChild(elIFrame);
1568
    }
1569
};
1570
1571
/**
1572
 * Initializes the results container once at object creation
1573
 *
1574
 * @method _initContainerEl
1575
 * @private
1576
 */
1577
YAHOO.widget.AutoComplete.prototype._initContainerEl = function() {
1578
    YAHOO.util.Dom.addClass(this._elContainer, "yui-ac-container");
1579
    
1580
    if(!this._elContent) {
1581
        // The elContent div is assigned DOM listeners and 
1582
        // helps size the iframe and shadow properly
1583
        var elContent = document.createElement("div");
1584
        elContent.className = "yui-ac-content";
1585
        elContent.style.display = "none";
1586
1587
        this._elContent = this._elContainer.appendChild(elContent);
1588
1589
        var elHeader = document.createElement("div");
1590
        elHeader.className = "yui-ac-hd";
1591
        elHeader.style.display = "none";
1592
        this._elHeader = this._elContent.appendChild(elHeader);
1593
1594
        var elBody = document.createElement("div");
1595
        elBody.className = "yui-ac-bd";
1596
        this._elBody = this._elContent.appendChild(elBody);
1597
1598
        var elFooter = document.createElement("div");
1599
        elFooter.className = "yui-ac-ft";
1600
        elFooter.style.display = "none";
1601
        this._elFooter = this._elContent.appendChild(elFooter);
1602
    }
1603
    else {
1604
    }
1605
};
1606
1607
/**
1608
 * Clears out contents of container body and creates up to
1609
 * YAHOO.widget.AutoComplete#maxResultsDisplayed &lt;li&gt; elements in an
1610
 * &lt;ul&gt; element.
1611
 *
1612
 * @method _initListEl
1613
 * @private
1614
 */
1615
YAHOO.widget.AutoComplete.prototype._initListEl = function() {
1616
    var nListLength = this.maxResultsDisplayed,
1617
        elList = this._elList || document.createElement("ul"),
1618
        elListItem;
1619
    
1620
    while(elList.childNodes.length < nListLength) {
1621
        elListItem = document.createElement("li");
1622
        elListItem.style.display = "none";
1623
        elListItem._nItemIndex = elList.childNodes.length;
1624
        elList.appendChild(elListItem);
1625
    }
1626
    if(!this._elList) {
1627
        var elBody = this._elBody;
1628
        YAHOO.util.Event.purgeElement(elBody, true);
1629
        elBody.innerHTML = "";
1630
        this._elList = elBody.appendChild(elList);
1631
    }
1632
    
1633
    this._elBody.style.display = "";
1634
};
1635
1636
/**
1637
 * Focuses input field.
1638
 *
1639
 * @method _focus
1640
 * @private
1641
 */
1642
YAHOO.widget.AutoComplete.prototype._focus = function() {
1643
    // http://developer.mozilla.org/en/docs/index.php?title=Key-navigable_custom_DHTML_widgets
1644
    var oSelf = this;
1645
    setTimeout(function() {
1646
        try {
1647
            oSelf._elTextbox.focus();
1648
        }
1649
        catch(e) {
1650
        }
1651
    },0);
1652
};
1653
1654
/**
1655
 * Enables interval detection for IME support.
1656
 *
1657
 * @method _enableIntervalDetection
1658
 * @private
1659
 */
1660
YAHOO.widget.AutoComplete.prototype._enableIntervalDetection = function() {
1661
    var oSelf = this;
1662
    if(!oSelf._queryInterval && oSelf.queryInterval) {
1663
        oSelf._queryInterval = setInterval(function() { oSelf._onInterval(); }, oSelf.queryInterval);
1664
    }
1665
};
1666
1667
/**
1668
 * Enables interval detection for a less performant but brute force mechanism to
1669
 * detect input values at an interval set by queryInterval and send queries if
1670
 * input value has changed. Needed to support right-click+paste or shift+insert
1671
 * edge cases. Please note that intervals are cleared at the end of each interaction,
1672
 * so enableIntervalDetection must be called for each new interaction. The
1673
 * recommended approach is to call it in response to textboxFocusEvent.
1674
 *
1675
 * @method enableIntervalDetection
1676
 */
1677
YAHOO.widget.AutoComplete.prototype.enableIntervalDetection =
1678
    YAHOO.widget.AutoComplete.prototype._enableIntervalDetection;
1679
1680
/**
1681
 * Enables query triggers based on text input detection by intervals (rather
1682
 * than by key events).
1683
 *
1684
 * @method _onInterval
1685
 * @private
1686
 */
1687
YAHOO.widget.AutoComplete.prototype._onInterval = function() {
1688
    var currValue = this._elTextbox.value;
1689
    var lastValue = this._sLastTextboxValue;
1690
    if(currValue != lastValue) {
1691
        this._sLastTextboxValue = currValue;
1692
        this._sendQuery(currValue);
1693
    }
1694
};
1695
1696
/**
1697
 * Cancels text input detection by intervals.
1698
 *
1699
 * @method _clearInterval
1700
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
1701
 * @private
1702
 */
1703
YAHOO.widget.AutoComplete.prototype._clearInterval = function() {
1704
    if(this._queryInterval) {
1705
        clearInterval(this._queryInterval);
1706
        this._queryInterval = null;
1707
    }
1708
};
1709
1710
/**
1711
 * Whether or not key is functional or should be ignored. Note that the right
1712
 * arrow key is NOT an ignored key since it triggers queries for certain intl
1713
 * charsets.
1714
 *
1715
 * @method _isIgnoreKey
1716
 * @param nKeycode {Number} Code of key pressed.
1717
 * @return {Boolean} True if key should be ignored, false otherwise.
1718
 * @private
1719
 */
1720
YAHOO.widget.AutoComplete.prototype._isIgnoreKey = function(nKeyCode) {
1721
    if((nKeyCode == 9) || (nKeyCode == 13)  || // tab, enter
1722
            (nKeyCode == 16) || (nKeyCode == 17) || // shift, ctl
1723
            (nKeyCode >= 18 && nKeyCode <= 20) || // alt, pause/break,caps lock
1724
            (nKeyCode == 27) || // esc
1725
            (nKeyCode >= 33 && nKeyCode <= 35) || // page up,page down,end
1726
            /*(nKeyCode >= 36 && nKeyCode <= 38) || // home,left,up
1727
            (nKeyCode == 40) || // down*/
1728
            (nKeyCode >= 36 && nKeyCode <= 40) || // home,left,up, right, down
1729
            (nKeyCode >= 44 && nKeyCode <= 45) || // print screen,insert
1730
            (nKeyCode == 229) // Bug 2041973: Korean XP fires 2 keyup events, the key and 229
1731
        ) { 
1732
        return true;
1733
    }
1734
    return false;
1735
};
1736
1737
/**
1738
 * Makes query request to the DataSource.
1739
 *
1740
 * @method _sendQuery
1741
 * @param sQuery {String} Query string.
1742
 * @private
1743
 */
1744
YAHOO.widget.AutoComplete.prototype._sendQuery = function(sQuery) {
1745
    // Widget has been effectively turned off
1746
    if(this.minQueryLength < 0) {
1747
        this._toggleContainer(false);
1748
        return;
1749
    }
1750
    // Delimiter has been enabled
1751
    if(this.delimChar) {
1752
        var extraction = this._extractQuery(sQuery);
1753
        // Here is the query itself
1754
        sQuery = extraction.query;
1755
        // ...and save the rest of the string for later
1756
        this._sPastSelections = extraction.previous;
1757
    }
1758
1759
    // Don't search queries that are too short
1760
    if((sQuery && (sQuery.length < this.minQueryLength)) || (!sQuery && this.minQueryLength > 0)) {
1761
        if(this._nDelayID != -1) {
1762
            clearTimeout(this._nDelayID);
1763
        }
1764
        this._toggleContainer(false);
1765
        return;
1766
    }
1767
1768
    sQuery = encodeURIComponent(sQuery);
1769
    this._nDelayID = -1;    // Reset timeout ID because request is being made
1770
    
1771
    // Subset matching
1772
    if(this.dataSource.queryMatchSubset || this.queryMatchSubset) { // backward compat
1773
        var oResponse = this.getSubsetMatches(sQuery);
1774
        if(oResponse) {
1775
            this.handleResponse(sQuery, oResponse, {query: sQuery});
1776
            return;
1777
        }
1778
    }
1779
    
1780
    if(this.dataSource.responseStripAfter) {
1781
        this.dataSource.doBeforeParseData = this.preparseRawResponse;
1782
    }
1783
    if(this.applyLocalFilter) {
1784
        this.dataSource.doBeforeCallback = this.filterResults;
1785
    }
1786
    
1787
    var sRequest = this.generateRequest(sQuery);
1788
    this.dataRequestEvent.fire(this, sQuery, sRequest);
1789
1790
    this.dataSource.sendRequest(sRequest, {
1791
            success : this.handleResponse,
1792
            failure : this.handleResponse,
1793
            scope   : this,
1794
            argument: {
1795
                query: sQuery
1796
            }
1797
    });
1798
};
1799
1800
/**
1801
 * Populates the given &lt;li&gt; element with return value from formatResult().
1802
 *
1803
 * @method _populateListItem
1804
 * @param elListItem {HTMLElement} The LI element.
1805
 * @param oResult {Object} The result object.
1806
 * @param sCurQuery {String} The query string.
1807
 * @private
1808
 */
1809
YAHOO.widget.AutoComplete.prototype._populateListItem = function(elListItem, oResult, sQuery) {
1810
    elListItem.innerHTML = this.formatResult(oResult, sQuery, elListItem._sResultMatch);
1811
};
1812
1813
/**
1814
 * Populates the array of &lt;li&gt; elements in the container with query
1815
 * results.
1816
 *
1817
 * @method _populateList
1818
 * @param sQuery {String} Original request.
1819
 * @param oResponse {Object} Response object.
1820
 * @param oPayload {MIXED} (optional) Additional argument(s)
1821
 * @private
1822
 */
1823
YAHOO.widget.AutoComplete.prototype._populateList = function(sQuery, oResponse, oPayload) {
1824
    // Clear previous timeout
1825
    if(this._nTypeAheadDelayID != -1) {
1826
        clearTimeout(this._nTypeAheadDelayID);
1827
    }
1828
        
1829
    sQuery = (oPayload && oPayload.query) ? oPayload.query : sQuery;
1830
    
1831
    // Pass data through abstract method for any transformations
1832
    var ok = this.doBeforeLoadData(sQuery, oResponse, oPayload);
1833
1834
    // Data is ok
1835
    if(ok && !oResponse.error) {
1836
        this.dataReturnEvent.fire(this, sQuery, oResponse.results);
1837
        
1838
        // Continue only if instance is still active (i.e., user hasn't already moved on)
1839
        if(this._bFocused) {
1840
            // Store state for this interaction
1841
            var sCurQuery = decodeURIComponent(sQuery);
1842
            this._sCurQuery = sCurQuery;
1843
            this._bItemSelected = false;
1844
        
1845
            var allResults = oResponse.results,
1846
                nItemsToShow = Math.min(allResults.length,this.maxResultsDisplayed),
1847
                sMatchKey = (this.dataSource.responseSchema.fields) ? 
1848
                    (this.dataSource.responseSchema.fields[0].key || this.dataSource.responseSchema.fields[0]) : 0;
1849
            
1850
            if(nItemsToShow > 0) {
1851
                // Make sure container and helpers are ready to go
1852
                if(!this._elList || (this._elList.childNodes.length < nItemsToShow)) {
1853
                    this._initListEl();
1854
                }
1855
                this._initContainerHelperEls();
1856
                
1857
                var allListItemEls = this._elList.childNodes;
1858
                // Fill items with data from the bottom up
1859
                for(var i = nItemsToShow-1; i >= 0; i--) {
1860
                    var elListItem = allListItemEls[i],
1861
                    oResult = allResults[i];
1862
                    
1863
                    // Backward compatibility
1864
                    if(this.resultTypeList) {
1865
                        // Results need to be converted back to an array
1866
                        var aResult = [];
1867
                        // Match key is first
1868
                        aResult[0] = (YAHOO.lang.isString(oResult)) ? oResult : oResult[sMatchKey] || oResult[this.key];
1869
                        // Add additional data to the result array
1870
                        var fields = this.dataSource.responseSchema.fields;
1871
                        if(YAHOO.lang.isArray(fields) && (fields.length > 1)) {
1872
                            for(var k=1, len=fields.length; k<len; k++) {
1873
                                aResult[aResult.length] = oResult[fields[k].key || fields[k]];
1874
                            }
1875
                        }
1876
                        // No specific fields defined, so pass along entire data object
1877
                        else {
1878
                            // Already an array
1879
                            if(YAHOO.lang.isArray(oResult)) {
1880
                                aResult = oResult;
1881
                            }
1882
                            // Simple string 
1883
                            else if(YAHOO.lang.isString(oResult)) {
1884
                                aResult = [oResult];
1885
                            }
1886
                            // Object
1887
                            else {
1888
                                aResult[1] = oResult;
1889
                            }
1890
                        }
1891
                        oResult = aResult;
1892
                    }
1893
1894
                    // The matching value, including backward compatibility for array format and safety net
1895
                    elListItem._sResultMatch = (YAHOO.lang.isString(oResult)) ? oResult : (YAHOO.lang.isArray(oResult)) ? oResult[0] : (oResult[sMatchKey] || "");
1896
                    elListItem._oResultData = oResult; // Additional data
1897
                    this._populateListItem(elListItem, oResult, sCurQuery);
1898
                    elListItem.style.display = "";
1899
                }
1900
        
1901
                // Clear out extraneous items
1902
                if(nItemsToShow < allListItemEls.length) {
1903
                    var extraListItem;
1904
                    for(var j = allListItemEls.length-1; j >= nItemsToShow; j--) {
1905
                        extraListItem = allListItemEls[j];
1906
                        extraListItem.style.display = "none";
1907
                    }
1908
                }
1909
                
1910
                this._nDisplayedItems = nItemsToShow;
1911
                
1912
                this.containerPopulateEvent.fire(this, sQuery, allResults);
1913
                
1914
                // Highlight the first item
1915
                if(this.autoHighlight) {
1916
                    var elFirstListItem = this._elList.firstChild;
1917
                    this._toggleHighlight(elFirstListItem,"to");
1918
                    this.itemArrowToEvent.fire(this, elFirstListItem);
1919
                    this._typeAhead(elFirstListItem,sQuery);
1920
                }
1921
                // Unhighlight any previous time
1922
                else {
1923
                    this._toggleHighlight(this._elCurListItem,"from");
1924
                }
1925
        
1926
                // Pre-expansion stuff
1927
                ok = this._doBeforeExpandContainer(this._elTextbox, this._elContainer, sQuery, allResults);
1928
                
1929
                // Expand the container
1930
                this._toggleContainer(ok);
1931
            }
1932
            else {
1933
                this._toggleContainer(false);
1934
            }
1935
1936
            return;
1937
        }
1938
    }
1939
    // Error
1940
    else {
1941
        this.dataErrorEvent.fire(this, sQuery, oResponse);
1942
    }
1943
        
1944
};
1945
1946
/**
1947
 * Called before container expands, by default snaps container to the
1948
 * bottom-left corner of the input element, then calls public overrideable method.
1949
 *
1950
 * @method _doBeforeExpandContainer
1951
 * @param elTextbox {HTMLElement} The text input box.
1952
 * @param elContainer {HTMLElement} The container element.
1953
 * @param sQuery {String} The query string.
1954
 * @param aResults {Object[]}  An array of query results.
1955
 * @return {Boolean} Return true to continue expanding container, false to cancel the expand.
1956
 * @private 
1957
 */
1958
YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer = function(elTextbox, elContainer, sQuery, aResults) {
1959
    if(this.autoSnapContainer) {
1960
        this.snapContainer();
1961
    }
1962
1963
    return this.doBeforeExpandContainer(elTextbox, elContainer, sQuery, aResults);
1964
};
1965
1966
/**
1967
 * When forceSelection is true and the user attempts
1968
 * leave the text input box without selecting an item from the query results,
1969
 * the user selection is cleared.
1970
 *
1971
 * @method _clearSelection
1972
 * @private
1973
 */
1974
YAHOO.widget.AutoComplete.prototype._clearSelection = function() {
1975
    var extraction = (this.delimChar) ? this._extractQuery(this._elTextbox.value) :
1976
            {previous:"",query:this._elTextbox.value};
1977
    this._elTextbox.value = extraction.previous;
1978
    this.selectionEnforceEvent.fire(this, extraction.query);
1979
};
1980
1981
/**
1982
 * Whether or not user-typed value in the text input box matches any of the
1983
 * query results.
1984
 *
1985
 * @method _textMatchesOption
1986
 * @return {HTMLElement} Matching list item element if user-input text matches
1987
 * a result, null otherwise.
1988
 * @private
1989
 */
1990
YAHOO.widget.AutoComplete.prototype._textMatchesOption = function() {
1991
    var elMatch = null;
1992
1993
    for(var i=0; i<this._nDisplayedItems; i++) {
1994
        var elListItem = this._elList.childNodes[i];
1995
        var sMatch = ("" + elListItem._sResultMatch).toLowerCase();
1996
        if(sMatch == this._sCurQuery.toLowerCase()) {
1997
            elMatch = elListItem;
1998
            break;
1999
        }
2000
    }
2001
    return(elMatch);
2002
};
2003
2004
/**
2005
 * Updates in the text input box with the first query result as the user types,
2006
 * selecting the substring that the user has not typed.
2007
 *
2008
 * @method _typeAhead
2009
 * @param elListItem {HTMLElement} The &lt;li&gt; element item whose data populates the input field.
2010
 * @param sQuery {String} Query string.
2011
 * @private
2012
 */
2013
YAHOO.widget.AutoComplete.prototype._typeAhead = function(elListItem, sQuery) {
2014
    // Don't typeAhead if turned off or is backspace
2015
    if(!this.typeAhead || (this._nKeyCode == 8)) {
2016
        return;
2017
    }
2018
2019
    var oSelf = this,
2020
        elTextbox = this._elTextbox;
2021
        
2022
    // Only if text selection is supported
2023
    if(elTextbox.setSelectionRange || elTextbox.createTextRange) {
2024
        // Set and store timeout for this typeahead
2025
        this._nTypeAheadDelayID = setTimeout(function() {
2026
                // Select the portion of text that the user has not typed
2027
                var nStart = elTextbox.value.length; // any saved queries plus what user has typed
2028
                oSelf._updateValue(elListItem);
2029
                var nEnd = elTextbox.value.length;
2030
                oSelf._selectText(elTextbox,nStart,nEnd);
2031
                var sPrefill = elTextbox.value.substr(nStart,nEnd);
2032
                oSelf.typeAheadEvent.fire(oSelf,sQuery,sPrefill);
2033
            },(this.typeAheadDelay*1000));            
2034
    }
2035
};
2036
2037
/**
2038
 * Selects text in the input field.
2039
 *
2040
 * @method _selectText
2041
 * @param elTextbox {HTMLElement} Text input box element in which to select text.
2042
 * @param nStart {Number} Starting index of text string to select.
2043
 * @param nEnd {Number} Ending index of text selection.
2044
 * @private
2045
 */
2046
YAHOO.widget.AutoComplete.prototype._selectText = function(elTextbox, nStart, nEnd) {
2047
    if(elTextbox.setSelectionRange) { // For Mozilla
2048
        elTextbox.setSelectionRange(nStart,nEnd);
2049
    }
2050
    else if(elTextbox.createTextRange) { // For IE
2051
        var oTextRange = elTextbox.createTextRange();
2052
        oTextRange.moveStart("character", nStart);
2053
        oTextRange.moveEnd("character", nEnd-elTextbox.value.length);
2054
        oTextRange.select();
2055
    }
2056
    else {
2057
        elTextbox.select();
2058
    }
2059
};
2060
2061
/**
2062
 * Extracts rightmost query from delimited string.
2063
 *
2064
 * @method _extractQuery
2065
 * @param sQuery {String} String to parse
2066
 * @return {Object} Object literal containing properties "query" and "previous".  
2067
 * @private
2068
 */
2069
YAHOO.widget.AutoComplete.prototype._extractQuery = function(sQuery) {
2070
    var aDelimChar = this.delimChar,
2071
        nDelimIndex = -1,
2072
        nNewIndex, nQueryStart,
2073
        i = aDelimChar.length-1,
2074
        sPrevious;
2075
        
2076
    // Loop through all possible delimiters and find the rightmost one in the query
2077
    // A " " may be a false positive if they are defined as delimiters AND
2078
    // are used to separate delimited queries
2079
    for(; i >= 0; i--) {
2080
        nNewIndex = sQuery.lastIndexOf(aDelimChar[i]);
2081
        if(nNewIndex > nDelimIndex) {
2082
            nDelimIndex = nNewIndex;
2083
        }
2084
    }
2085
    // If we think the last delimiter is a space (" "), make sure it is NOT
2086
    // a false positive by also checking the char directly before it
2087
    if(aDelimChar[i] == " ") {
2088
        for (var j = aDelimChar.length-1; j >= 0; j--) {
2089
            if(sQuery[nDelimIndex - 1] == aDelimChar[j]) {
2090
                nDelimIndex--;
2091
                break;
2092
            }
2093
        }
2094
    }
2095
    // A delimiter has been found in the query so extract the latest query from past selections
2096
    if(nDelimIndex > -1) {
2097
        nQueryStart = nDelimIndex + 1;
2098
        // Trim any white space from the beginning...
2099
        while(sQuery.charAt(nQueryStart) == " ") {
2100
            nQueryStart += 1;
2101
        }
2102
        // ...and save the rest of the string for later
2103
        sPrevious = sQuery.substring(0,nQueryStart);
2104
        // Here is the query itself
2105
        sQuery = sQuery.substr(nQueryStart);
2106
    }
2107
    // No delimiter found in the query, so there are no selections from past queries
2108
    else {
2109
        sPrevious = "";
2110
    }
2111
    
2112
    return {
2113
        previous: sPrevious,
2114
        query: sQuery
2115
    };
2116
};
2117
2118
/**
2119
 * Syncs results container with its helpers.
2120
 *
2121
 * @method _toggleContainerHelpers
2122
 * @param bShow {Boolean} True if container is expanded, false if collapsed
2123
 * @private
2124
 */
2125
YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers = function(bShow) {
2126
    var width = this._elContent.offsetWidth + "px";
2127
    var height = this._elContent.offsetHeight + "px";
2128
2129
    if(this.useIFrame && this._elIFrame) {
2130
    var elIFrame = this._elIFrame;
2131
        if(bShow) {
2132
            elIFrame.style.width = width;
2133
            elIFrame.style.height = height;
2134
            elIFrame.style.padding = "";
2135
        }
2136
        else {
2137
            elIFrame.style.width = 0;
2138
            elIFrame.style.height = 0;
2139
            elIFrame.style.padding = 0;
2140
        }
2141
    }
2142
    if(this.useShadow && this._elShadow) {
2143
    var elShadow = this._elShadow;
2144
        if(bShow) {
2145
            elShadow.style.width = width;
2146
            elShadow.style.height = height;
2147
        }
2148
        else {
2149
            elShadow.style.width = 0;
2150
            elShadow.style.height = 0;
2151
        }
2152
    }
2153
};
2154
2155
/**
2156
 * Animates expansion or collapse of the container.
2157
 *
2158
 * @method _toggleContainer
2159
 * @param bShow {Boolean} True if container should be expanded, false if container should be collapsed
2160
 * @private
2161
 */
2162
YAHOO.widget.AutoComplete.prototype._toggleContainer = function(bShow) {
2163
2164
    var elContainer = this._elContainer;
2165
2166
    // If implementer has container always open and it's already open, don't mess with it
2167
    // Container is initialized with display "none" so it may need to be shown first time through
2168
    if(this.alwaysShowContainer && this._bContainerOpen) {
2169
        return;
2170
    }
2171
    
2172
    // Reset states
2173
    if(!bShow) {
2174
        this._toggleHighlight(this._elCurListItem,"from");
2175
        this._nDisplayedItems = 0;
2176
        this._sCurQuery = null;
2177
        
2178
        // Container is already closed, so don't bother with changing the UI
2179
        if(this._elContent.style.display == "none") {
2180
            return;
2181
        }
2182
    }
2183
2184
    // If animation is enabled...
2185
    var oAnim = this._oAnim;
2186
    if(oAnim && oAnim.getEl() && (this.animHoriz || this.animVert)) {
2187
        if(oAnim.isAnimated()) {
2188
            oAnim.stop(true);
2189
        }
2190
2191
        // Clone container to grab current size offscreen
2192
        var oClone = this._elContent.cloneNode(true);
2193
        elContainer.appendChild(oClone);
2194
        oClone.style.top = "-9000px";
2195
        oClone.style.width = "";
2196
        oClone.style.height = "";
2197
        oClone.style.display = "";
2198
2199
        // Current size of the container is the EXPANDED size
2200
        var wExp = oClone.offsetWidth;
2201
        var hExp = oClone.offsetHeight;
2202
2203
        // Calculate COLLAPSED sizes based on horiz and vert anim
2204
        var wColl = (this.animHoriz) ? 0 : wExp;
2205
        var hColl = (this.animVert) ? 0 : hExp;
2206
2207
        // Set animation sizes
2208
        oAnim.attributes = (bShow) ?
2209
            {width: { to: wExp }, height: { to: hExp }} :
2210
            {width: { to: wColl}, height: { to: hColl }};
2211
2212
        // If opening anew, set to a collapsed size...
2213
        if(bShow && !this._bContainerOpen) {
2214
            this._elContent.style.width = wColl+"px";
2215
            this._elContent.style.height = hColl+"px";
2216
        }
2217
        // Else, set it to its last known size.
2218
        else {
2219
            this._elContent.style.width = wExp+"px";
2220
            this._elContent.style.height = hExp+"px";
2221
        }
2222
2223
        elContainer.removeChild(oClone);
2224
        oClone = null;
2225
2226
    	var oSelf = this;
2227
    	var onAnimComplete = function() {
2228
            // Finish the collapse
2229
    		oAnim.onComplete.unsubscribeAll();
2230
2231
            if(bShow) {
2232
                oSelf._toggleContainerHelpers(true);
2233
                oSelf._bContainerOpen = bShow;
2234
                oSelf.containerExpandEvent.fire(oSelf);
2235
            }
2236
            else {
2237
                oSelf._elContent.style.display = "none";
2238
                oSelf._bContainerOpen = bShow;
2239
                oSelf.containerCollapseEvent.fire(oSelf);
2240
            }
2241
     	};
2242
2243
        // Display container and animate it
2244
        this._toggleContainerHelpers(false); // Bug 1424486: Be early to hide, late to show;
2245
        this._elContent.style.display = "";
2246
        oAnim.onComplete.subscribe(onAnimComplete);
2247
        oAnim.animate();
2248
    }
2249
    // Else don't animate, just show or hide
2250
    else {
2251
        if(bShow) {
2252
            this._elContent.style.display = "";
2253
            this._toggleContainerHelpers(true);
2254
            this._bContainerOpen = bShow;
2255
            this.containerExpandEvent.fire(this);
2256
        }
2257
        else {
2258
            this._toggleContainerHelpers(false);
2259
            this._elContent.style.display = "none";
2260
            this._bContainerOpen = bShow;
2261
            this.containerCollapseEvent.fire(this);
2262
        }
2263
   }
2264
2265
};
2266
2267
/**
2268
 * Toggles the highlight on or off for an item in the container, and also cleans
2269
 * up highlighting of any previous item.
2270
 *
2271
 * @method _toggleHighlight
2272
 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2273
 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2274
 * @private
2275
 */
2276
YAHOO.widget.AutoComplete.prototype._toggleHighlight = function(elNewListItem, sType) {
2277
    if(elNewListItem) {
2278
        var sHighlight = this.highlightClassName;
2279
        if(this._elCurListItem) {
2280
            // Remove highlight from old item
2281
            YAHOO.util.Dom.removeClass(this._elCurListItem, sHighlight);
2282
            this._elCurListItem = null;
2283
        }
2284
    
2285
        if((sType == "to") && sHighlight) {
2286
            // Apply highlight to new item
2287
            YAHOO.util.Dom.addClass(elNewListItem, sHighlight);
2288
            this._elCurListItem = elNewListItem;
2289
        }
2290
    }
2291
};
2292
2293
/**
2294
 * Toggles the pre-highlight on or off for an item in the container, and also cleans
2295
 * up pre-highlighting of any previous item.
2296
 *
2297
 * @method _togglePrehighlight
2298
 * @param elNewListItem {HTMLElement} The &lt;li&gt; element item to receive highlight behavior.
2299
 * @param sType {String} Type "mouseover" will toggle highlight on, and "mouseout" will toggle highlight off.
2300
 * @private
2301
 */
2302
YAHOO.widget.AutoComplete.prototype._togglePrehighlight = function(elNewListItem, sType) {
2303
    var sPrehighlight = this.prehighlightClassName;
2304
2305
    if(this._elCurPrehighlightItem) {
2306
        YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem, sPrehighlight);
2307
    }
2308
    if(elNewListItem == this._elCurListItem) {
2309
        return;
2310
    }
2311
2312
    if((sType == "mouseover") && sPrehighlight) {
2313
        // Apply prehighlight to new item
2314
        YAHOO.util.Dom.addClass(elNewListItem, sPrehighlight);
2315
        this._elCurPrehighlightItem = elNewListItem;
2316
    }
2317
    else {
2318
        // Remove prehighlight from old item
2319
        YAHOO.util.Dom.removeClass(elNewListItem, sPrehighlight);
2320
    }
2321
};
2322
2323
/**
2324
 * Updates the text input box value with selected query result. If a delimiter
2325
 * has been defined, then the value gets appended with the delimiter.
2326
 *
2327
 * @method _updateValue
2328
 * @param elListItem {HTMLElement} The &lt;li&gt; element item with which to update the value.
2329
 * @private
2330
 */
2331
YAHOO.widget.AutoComplete.prototype._updateValue = function(elListItem) {
2332
    if(!this.suppressInputUpdate) {    
2333
        var elTextbox = this._elTextbox;
2334
        var sDelimChar = (this.delimChar) ? (this.delimChar[0] || this.delimChar) : null;
2335
        var sResultMatch = elListItem._sResultMatch;
2336
    
2337
        // Calculate the new value
2338
        var sNewValue = "";
2339
        if(sDelimChar) {
2340
            // Preserve selections from past queries
2341
            sNewValue = this._sPastSelections;
2342
            // Add new selection plus delimiter
2343
            sNewValue += sResultMatch + sDelimChar;
2344
            if(sDelimChar != " ") {
2345
                sNewValue += " ";
2346
            }
2347
        }
2348
        else { 
2349
            sNewValue = sResultMatch;
2350
        }
2351
        
2352
        // Update input field
2353
        elTextbox.value = sNewValue;
2354
    
2355
        // Scroll to bottom of textarea if necessary
2356
        if(elTextbox.type == "textarea") {
2357
            elTextbox.scrollTop = elTextbox.scrollHeight;
2358
        }
2359
    
2360
        // Move cursor to end
2361
        var end = elTextbox.value.length;
2362
        this._selectText(elTextbox,end,end);
2363
    
2364
        this._elCurListItem = elListItem;
2365
    }
2366
};
2367
2368
/**
2369
 * Selects a result item from the container
2370
 *
2371
 * @method _selectItem
2372
 * @param elListItem {HTMLElement} The selected &lt;li&gt; element item.
2373
 * @private
2374
 */
2375
YAHOO.widget.AutoComplete.prototype._selectItem = function(elListItem) {
2376
    this._bItemSelected = true;
2377
    this._updateValue(elListItem);
2378
    this._sPastSelections = this._elTextbox.value;
2379
    this._clearInterval();
2380
    this.itemSelectEvent.fire(this, elListItem, elListItem._oResultData);
2381
    this._toggleContainer(false);
2382
};
2383
2384
/**
2385
 * If an item is highlighted in the container, the right arrow key jumps to the
2386
 * end of the textbox and selects the highlighted item, otherwise the container
2387
 * is closed.
2388
 *
2389
 * @method _jumpSelection
2390
 * @private
2391
 */
2392
YAHOO.widget.AutoComplete.prototype._jumpSelection = function() {
2393
    if(this._elCurListItem) {
2394
        this._selectItem(this._elCurListItem);
2395
    }
2396
    else {
2397
        this._toggleContainer(false);
2398
    }
2399
};
2400
2401
/**
2402
 * Triggered by up and down arrow keys, changes the current highlighted
2403
 * &lt;li&gt; element item. Scrolls container if necessary.
2404
 *
2405
 * @method _moveSelection
2406
 * @param nKeyCode {Number} Code of key pressed.
2407
 * @private
2408
 */
2409
YAHOO.widget.AutoComplete.prototype._moveSelection = function(nKeyCode) {
2410
    if(this._bContainerOpen) {
2411
        // Determine current item's id number
2412
        var elCurListItem = this._elCurListItem,
2413
            nCurItemIndex = -1;
2414
2415
        if(elCurListItem) {
2416
            nCurItemIndex = elCurListItem._nItemIndex;
2417
        }
2418
2419
        var nNewItemIndex = (nKeyCode == 40) ?
2420
                (nCurItemIndex + 1) : (nCurItemIndex - 1);
2421
2422
        // Out of bounds
2423
        if(nNewItemIndex < -2 || nNewItemIndex >= this._nDisplayedItems) {
2424
            return;
2425
        }
2426
2427
        if(elCurListItem) {
2428
            // Unhighlight current item
2429
            this._toggleHighlight(elCurListItem, "from");
2430
            this.itemArrowFromEvent.fire(this, elCurListItem);
2431
        }
2432
        if(nNewItemIndex == -1) {
2433
           // Go back to query (remove type-ahead string)
2434
            if(this.delimChar) {
2435
                this._elTextbox.value = this._sPastSelections + this._sCurQuery;
2436
            }
2437
            else {
2438
                this._elTextbox.value = this._sCurQuery;
2439
            }
2440
            return;
2441
        }
2442
        if(nNewItemIndex == -2) {
2443
            // Close container
2444
            this._toggleContainer(false);
2445
            return;
2446
        }
2447
        
2448
        var elNewListItem = this._elList.childNodes[nNewItemIndex],
2449
2450
        // Scroll the container if necessary
2451
            elContent = this._elContent,
2452
            sOF = YAHOO.util.Dom.getStyle(elContent,"overflow"),
2453
            sOFY = YAHOO.util.Dom.getStyle(elContent,"overflowY"),
2454
            scrollOn = ((sOF == "auto") || (sOF == "scroll") || (sOFY == "auto") || (sOFY == "scroll"));
2455
        if(scrollOn && (nNewItemIndex > -1) &&
2456
        (nNewItemIndex < this._nDisplayedItems)) {
2457
            // User is keying down
2458
            if(nKeyCode == 40) {
2459
                // Bottom of selected item is below scroll area...
2460
                if((elNewListItem.offsetTop+elNewListItem.offsetHeight) > (elContent.scrollTop + elContent.offsetHeight)) {
2461
                    // Set bottom of scroll area to bottom of selected item
2462
                    elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2463
                }
2464
                // Bottom of selected item is above scroll area...
2465
                else if((elNewListItem.offsetTop+elNewListItem.offsetHeight) < elContent.scrollTop) {
2466
                    // Set top of selected item to top of scroll area
2467
                    elContent.scrollTop = elNewListItem.offsetTop;
2468
2469
                }
2470
            }
2471
            // User is keying up
2472
            else {
2473
                // Top of selected item is above scroll area
2474
                if(elNewListItem.offsetTop < elContent.scrollTop) {
2475
                    // Set top of scroll area to top of selected item
2476
                    this._elContent.scrollTop = elNewListItem.offsetTop;
2477
                }
2478
                // Top of selected item is below scroll area
2479
                else if(elNewListItem.offsetTop > (elContent.scrollTop + elContent.offsetHeight)) {
2480
                    // Set bottom of selected item to bottom of scroll area
2481
                    this._elContent.scrollTop = (elNewListItem.offsetTop+elNewListItem.offsetHeight) - elContent.offsetHeight;
2482
                }
2483
            }
2484
        }
2485
2486
        this._toggleHighlight(elNewListItem, "to");
2487
        this.itemArrowToEvent.fire(this, elNewListItem);
2488
        if(this.typeAhead) {
2489
            this._updateValue(elNewListItem);
2490
        }
2491
    }
2492
};
2493
2494
/////////////////////////////////////////////////////////////////////////////
2495
//
2496
// Private event handlers
2497
//
2498
/////////////////////////////////////////////////////////////////////////////
2499
2500
/**
2501
 * Handles container mouseover events.
2502
 *
2503
 * @method _onContainerMouseover
2504
 * @param v {HTMLEvent} The mouseover event.
2505
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2506
 * @private
2507
 */
2508
YAHOO.widget.AutoComplete.prototype._onContainerMouseover = function(v,oSelf) {
2509
    var elTarget = YAHOO.util.Event.getTarget(v);
2510
    var elTag = elTarget.nodeName.toLowerCase();
2511
    while(elTarget && (elTag != "table")) {
2512
        switch(elTag) {
2513
            case "body":
2514
                return;
2515
            case "li":
2516
                if(oSelf.prehighlightClassName) {
2517
                    oSelf._togglePrehighlight(elTarget,"mouseover");
2518
                }
2519
                else {
2520
                    oSelf._toggleHighlight(elTarget,"to");
2521
                }
2522
            
2523
                oSelf.itemMouseOverEvent.fire(oSelf, elTarget);
2524
                break;
2525
            case "div":
2526
                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2527
                    oSelf._bOverContainer = true;
2528
                    return;
2529
                }
2530
                break;
2531
            default:
2532
                break;
2533
        }
2534
        
2535
        elTarget = elTarget.parentNode;
2536
        if(elTarget) {
2537
            elTag = elTarget.nodeName.toLowerCase();
2538
        }
2539
    }
2540
};
2541
2542
/**
2543
 * Handles container mouseout events.
2544
 *
2545
 * @method _onContainerMouseout
2546
 * @param v {HTMLEvent} The mouseout event.
2547
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2548
 * @private
2549
 */
2550
YAHOO.widget.AutoComplete.prototype._onContainerMouseout = function(v,oSelf) {
2551
    var elTarget = YAHOO.util.Event.getTarget(v);
2552
    var elTag = elTarget.nodeName.toLowerCase();
2553
    while(elTarget && (elTag != "table")) {
2554
        switch(elTag) {
2555
            case "body":
2556
                return;
2557
            case "li":
2558
                if(oSelf.prehighlightClassName) {
2559
                    oSelf._togglePrehighlight(elTarget,"mouseout");
2560
                }
2561
                else {
2562
                    oSelf._toggleHighlight(elTarget,"from");
2563
                }
2564
            
2565
                oSelf.itemMouseOutEvent.fire(oSelf, elTarget);
2566
                break;
2567
            case "ul":
2568
                oSelf._toggleHighlight(oSelf._elCurListItem,"to");
2569
                break;
2570
            case "div":
2571
                if(YAHOO.util.Dom.hasClass(elTarget,"yui-ac-container")) {
2572
                    oSelf._bOverContainer = false;
2573
                    return;
2574
                }
2575
                break;
2576
            default:
2577
                break;
2578
        }
2579
2580
        elTarget = elTarget.parentNode;
2581
        if(elTarget) {
2582
            elTag = elTarget.nodeName.toLowerCase();
2583
        }
2584
    }
2585
};
2586
2587
/**
2588
 * Handles container click events.
2589
 *
2590
 * @method _onContainerClick
2591
 * @param v {HTMLEvent} The click event.
2592
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2593
 * @private
2594
 */
2595
YAHOO.widget.AutoComplete.prototype._onContainerClick = function(v,oSelf) {
2596
    var elTarget = YAHOO.util.Event.getTarget(v);
2597
    var elTag = elTarget.nodeName.toLowerCase();
2598
    while(elTarget && (elTag != "table")) {
2599
        switch(elTag) {
2600
            case "body":
2601
                return;
2602
            case "li":
2603
                // In case item has not been moused over
2604
                oSelf._toggleHighlight(elTarget,"to");
2605
                oSelf._selectItem(elTarget);
2606
                return;
2607
            default:
2608
                break;
2609
        }
2610
2611
        elTarget = elTarget.parentNode;
2612
        if(elTarget) {
2613
            elTag = elTarget.nodeName.toLowerCase();
2614
        }
2615
    }    
2616
};
2617
2618
2619
/**
2620
 * Handles container scroll events.
2621
 *
2622
 * @method _onContainerScroll
2623
 * @param v {HTMLEvent} The scroll event.
2624
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2625
 * @private
2626
 */
2627
YAHOO.widget.AutoComplete.prototype._onContainerScroll = function(v,oSelf) {
2628
    oSelf._focus();
2629
};
2630
2631
/**
2632
 * Handles container resize events.
2633
 *
2634
 * @method _onContainerResize
2635
 * @param v {HTMLEvent} The resize event.
2636
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2637
 * @private
2638
 */
2639
YAHOO.widget.AutoComplete.prototype._onContainerResize = function(v,oSelf) {
2640
    oSelf._toggleContainerHelpers(oSelf._bContainerOpen);
2641
};
2642
2643
2644
/**
2645
 * Handles textbox keydown events of functional keys, mainly for UI behavior.
2646
 *
2647
 * @method _onTextboxKeyDown
2648
 * @param v {HTMLEvent} The keydown event.
2649
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2650
 * @private
2651
 */
2652
YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown = function(v,oSelf) {
2653
    var nKeyCode = v.keyCode;
2654
2655
    // Clear timeout
2656
    if(oSelf._nTypeAheadDelayID != -1) {
2657
        clearTimeout(oSelf._nTypeAheadDelayID);
2658
    }
2659
    
2660
    switch (nKeyCode) {
2661
        case 9: // tab
2662
            if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2663
                // select an item or clear out
2664
                if(oSelf._elCurListItem) {
2665
                    if(oSelf.delimChar && (oSelf._nKeyCode != nKeyCode)) {
2666
                        if(oSelf._bContainerOpen) {
2667
                            YAHOO.util.Event.stopEvent(v);
2668
                        }
2669
                    }
2670
                    oSelf._selectItem(oSelf._elCurListItem);
2671
                }
2672
                else {
2673
                    oSelf._toggleContainer(false);
2674
                }
2675
            }
2676
            break;
2677
        case 13: // enter
2678
            if(!YAHOO.env.ua.opera && (navigator.userAgent.toLowerCase().indexOf("mac") == -1) || (YAHOO.env.ua.webkit>420)) {
2679
                if(oSelf._elCurListItem) {
2680
                    if(oSelf._nKeyCode != nKeyCode) {
2681
                        if(oSelf._bContainerOpen) {
2682
                            YAHOO.util.Event.stopEvent(v);
2683
                        }
2684
                    }
2685
                    oSelf._selectItem(oSelf._elCurListItem);
2686
                }
2687
                else {
2688
                    oSelf._toggleContainer(false);
2689
                }
2690
            }
2691
            break;
2692
        case 27: // esc
2693
            oSelf._toggleContainer(false);
2694
            return;
2695
        case 39: // right
2696
            oSelf._jumpSelection();
2697
            break;
2698
        case 38: // up
2699
            if(oSelf._bContainerOpen) {
2700
                YAHOO.util.Event.stopEvent(v);
2701
                oSelf._moveSelection(nKeyCode);
2702
            }
2703
            break;
2704
        case 40: // down
2705
            if(oSelf._bContainerOpen) {
2706
                YAHOO.util.Event.stopEvent(v);
2707
                oSelf._moveSelection(nKeyCode);
2708
            }
2709
            break;
2710
        default: 
2711
            oSelf._bItemSelected = false;
2712
            oSelf._toggleHighlight(oSelf._elCurListItem, "from");
2713
2714
            oSelf.textboxKeyEvent.fire(oSelf, nKeyCode);
2715
            break;
2716
    }
2717
2718
    if(nKeyCode === 18){
2719
        oSelf._enableIntervalDetection();
2720
    }    
2721
    oSelf._nKeyCode = nKeyCode;
2722
};
2723
2724
/**
2725
 * Handles textbox keypress events.
2726
 * @method _onTextboxKeyPress
2727
 * @param v {HTMLEvent} The keypress event.
2728
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2729
 * @private
2730
 */
2731
YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress = function(v,oSelf) {
2732
    var nKeyCode = v.keyCode;
2733
2734
        // Expose only to non SF3 (bug 1978549) Mac browsers (bug 790337) and  Opera browsers (bug 583531),
2735
        // where stopEvent is ineffective on keydown events 
2736
        if(YAHOO.env.ua.opera || (navigator.userAgent.toLowerCase().indexOf("mac") != -1) && (YAHOO.env.ua.webkit < 420)) {
2737
            switch (nKeyCode) {
2738
            case 9: // tab
2739
                // select an item or clear out
2740
                if(oSelf._bContainerOpen) {
2741
                    if(oSelf.delimChar) {
2742
                        YAHOO.util.Event.stopEvent(v);
2743
                    }
2744
                    if(oSelf._elCurListItem) {
2745
                        oSelf._selectItem(oSelf._elCurListItem);
2746
                    }
2747
                    else {
2748
                        oSelf._toggleContainer(false);
2749
                    }
2750
                }
2751
                break;
2752
            case 13: // enter
2753
                if(oSelf._bContainerOpen) {
2754
                    YAHOO.util.Event.stopEvent(v);
2755
                    if(oSelf._elCurListItem) {
2756
                        oSelf._selectItem(oSelf._elCurListItem);
2757
                    }
2758
                    else {
2759
                        oSelf._toggleContainer(false);
2760
                    }
2761
                }
2762
                break;
2763
            default:
2764
                break;
2765
            }
2766
        }
2767
2768
        //TODO: (?) limit only to non-IE, non-Mac-FF for Korean IME support (bug 811948)
2769
        // Korean IME detected
2770
        else if(nKeyCode == 229) {
2771
            oSelf._enableIntervalDetection();
2772
        }
2773
};
2774
2775
/**
2776
 * Handles textbox keyup events to trigger queries.
2777
 *
2778
 * @method _onTextboxKeyUp
2779
 * @param v {HTMLEvent} The keyup event.
2780
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2781
 * @private
2782
 */
2783
YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp = function(v,oSelf) {
2784
    var sText = this.value; //string in textbox
2785
    
2786
    // Check to see if any of the public properties have been updated
2787
    oSelf._initProps();
2788
2789
    // Filter out chars that don't trigger queries
2790
    var nKeyCode = v.keyCode;
2791
    if(oSelf._isIgnoreKey(nKeyCode)) {
2792
        return;
2793
    }
2794
2795
    // Clear previous timeout
2796
    if(oSelf._nDelayID != -1) {
2797
        clearTimeout(oSelf._nDelayID);
2798
    }
2799
2800
    // Set new timeout
2801
    oSelf._nDelayID = setTimeout(function(){
2802
            oSelf._sendQuery(sText);
2803
        },(oSelf.queryDelay * 1000));
2804
};
2805
2806
/**
2807
 * Handles text input box receiving focus.
2808
 *
2809
 * @method _onTextboxFocus
2810
 * @param v {HTMLEvent} The focus event.
2811
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2812
 * @private
2813
 */
2814
YAHOO.widget.AutoComplete.prototype._onTextboxFocus = function (v,oSelf) {
2815
    // Start of a new interaction
2816
    if(!oSelf._bFocused) {
2817
        oSelf._elTextbox.setAttribute("autocomplete","off");
2818
        oSelf._bFocused = true;
2819
        oSelf._sInitInputValue = oSelf._elTextbox.value;
2820
        oSelf.textboxFocusEvent.fire(oSelf);
2821
    }
2822
};
2823
2824
/**
2825
 * Handles text input box losing focus.
2826
 *
2827
 * @method _onTextboxBlur
2828
 * @param v {HTMLEvent} The focus event.
2829
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2830
 * @private
2831
 */
2832
YAHOO.widget.AutoComplete.prototype._onTextboxBlur = function (v,oSelf) {
2833
    // Is a true blur
2834
    if(!oSelf._bOverContainer || (oSelf._nKeyCode == 9)) {
2835
        // Current query needs to be validated as a selection
2836
        if(!oSelf._bItemSelected) {
2837
            var elMatchListItem = oSelf._textMatchesOption();
2838
            // Container is closed or current query doesn't match any result
2839
            if(!oSelf._bContainerOpen || (oSelf._bContainerOpen && (elMatchListItem === null))) {
2840
                // Force selection is enabled so clear the current query
2841
                if(oSelf.forceSelection) {
2842
                    oSelf._clearSelection();
2843
                }
2844
                // Treat current query as a valid selection
2845
                else {
2846
                    oSelf.unmatchedItemSelectEvent.fire(oSelf, oSelf._sCurQuery);
2847
                }
2848
            }
2849
            // Container is open and current query matches a result
2850
            else {
2851
                // Force a selection when textbox is blurred with a match
2852
                if(oSelf.forceSelection) {
2853
                    oSelf._selectItem(elMatchListItem);
2854
                }
2855
            }
2856
        }
2857
2858
        oSelf._clearInterval();
2859
        oSelf._bFocused = false;
2860
        if(oSelf._sInitInputValue !== oSelf._elTextbox.value) {
2861
            oSelf.textboxChangeEvent.fire(oSelf);
2862
        }
2863
        oSelf.textboxBlurEvent.fire(oSelf);
2864
2865
        oSelf._toggleContainer(false);
2866
    }
2867
    // Not a true blur if it was a selection via mouse click
2868
    else {
2869
        oSelf._focus();
2870
    }
2871
};
2872
2873
/**
2874
 * Handles window unload event.
2875
 *
2876
 * @method _onWindowUnload
2877
 * @param v {HTMLEvent} The unload event.
2878
 * @param oSelf {YAHOO.widget.AutoComplete} The AutoComplete instance.
2879
 * @private
2880
 */
2881
YAHOO.widget.AutoComplete.prototype._onWindowUnload = function(v,oSelf) {
2882
    if(oSelf && oSelf._elTextbox && oSelf.allowBrowserAutocomplete) {
2883
        oSelf._elTextbox.setAttribute("autocomplete","on");
2884
    }
2885
};
2886
2887
/////////////////////////////////////////////////////////////////////////////
2888
//
2889
// Deprecated for Backwards Compatibility
2890
//
2891
/////////////////////////////////////////////////////////////////////////////
2892
/**
2893
 * @method doBeforeSendQuery
2894
 * @deprecated Use generateRequest.
2895
 */
2896
YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery = function(sQuery) {
2897
    return this.generateRequest(sQuery);
2898
};
2899
2900
/**
2901
 * @method getListItems
2902
 * @deprecated Use getListEl().childNodes.
2903
 */
2904
YAHOO.widget.AutoComplete.prototype.getListItems = function() {
2905
    var allListItemEls = [],
2906
        els = this._elList.childNodes;
2907
    for(var i=els.length-1; i>=0; i--) {
2908
        allListItemEls[i] = els[i];
2909
    }
2910
    return allListItemEls;
2911
};
2912
2913
/////////////////////////////////////////////////////////////////////////
2914
//
2915
// Private static methods
2916
//
2917
/////////////////////////////////////////////////////////////////////////
2918
2919
/**
2920
 * Clones object literal or array of object literals.
2921
 *
2922
 * @method AutoComplete._cloneObject
2923
 * @param o {Object} Object.
2924
 * @private
2925
 * @static     
2926
 */
2927
YAHOO.widget.AutoComplete._cloneObject = function(o) {
2928
    if(!YAHOO.lang.isValue(o)) {
2929
        return o;
2930
    }
2931
    
2932
    var copy = {};
2933
    
2934
    if(YAHOO.lang.isFunction(o)) {
2935
        copy = o;
2936
    }
2937
    else if(YAHOO.lang.isArray(o)) {
2938
        var array = [];
2939
        for(var i=0,len=o.length;i<len;i++) {
2940
            array[i] = YAHOO.widget.AutoComplete._cloneObject(o[i]);
2941
        }
2942
        copy = array;
2943
    }
2944
    else if(YAHOO.lang.isObject(o)) { 
2945
        for (var x in o){
2946
            if(YAHOO.lang.hasOwnProperty(o, x)) {
2947
                if(YAHOO.lang.isValue(o[x]) && YAHOO.lang.isObject(o[x]) || YAHOO.lang.isArray(o[x])) {
2948
                    copy[x] = YAHOO.widget.AutoComplete._cloneObject(o[x]);
2949
                }
2950
                else {
2951
                    copy[x] = o[x];
2952
                }
2953
            }
2954
        }
2955
    }
2956
    else {
2957
        copy = o;
2958
    }
2959
2960
    return copy;
2961
};
2962
2963
2964
2965
2966
YAHOO.register("autocomplete", YAHOO.widget.AutoComplete, {version: "2.8.0r4", build: "2449"});
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/datasource/datasource-debug.js (-3067 lines)
Lines 1-3067 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
(function () {
8
9
var lang   = YAHOO.lang,
10
    util   = YAHOO.util,
11
    Ev     = util.Event;
12
13
/**
14
 * The DataSource utility provides a common configurable interface for widgets to
15
 * access a variety of data, from JavaScript arrays to online database servers.
16
 *
17
 * @module datasource
18
 * @requires yahoo, event
19
 * @optional json, get, connection 
20
 * @title DataSource Utility
21
 */
22
23
/****************************************************************************/
24
/****************************************************************************/
25
/****************************************************************************/
26
27
/**
28
 * Base class for the YUI DataSource utility.
29
 *
30
 * @namespace YAHOO.util
31
 * @class YAHOO.util.DataSourceBase
32
 * @constructor
33
 * @param oLiveData {HTMLElement}  Pointer to live data.
34
 * @param oConfigs {object} (optional) Object literal of configuration values.
35
 */
36
util.DataSourceBase = function(oLiveData, oConfigs) {
37
    if(oLiveData === null || oLiveData === undefined) {
38
        YAHOO.log("Could not instantiate DataSource due to invalid live database",
39
                "error", this.toString());
40
        return;
41
    }
42
    
43
    this.liveData = oLiveData;
44
    this._oQueue = {interval:null, conn:null, requests:[]};
45
    this.responseSchema = {};   
46
47
    // Set any config params passed in to override defaults
48
    if(oConfigs && (oConfigs.constructor == Object)) {
49
        for(var sConfig in oConfigs) {
50
            if(sConfig) {
51
                this[sConfig] = oConfigs[sConfig];
52
            }
53
        }
54
    }
55
    
56
    // Validate and initialize public configs
57
    var maxCacheEntries = this.maxCacheEntries;
58
    if(!lang.isNumber(maxCacheEntries) || (maxCacheEntries < 0)) {
59
        maxCacheEntries = 0;
60
    }
61
62
    // Initialize interval tracker
63
    this._aIntervals = [];
64
65
    /////////////////////////////////////////////////////////////////////////////
66
    //
67
    // Custom Events
68
    //
69
    /////////////////////////////////////////////////////////////////////////////
70
71
    /**
72
     * Fired when a request is made to the local cache.
73
     *
74
     * @event cacheRequestEvent
75
     * @param oArgs.request {Object} The request object.
76
     * @param oArgs.callback {Object} The callback object.
77
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
78
     */
79
    this.createEvent("cacheRequestEvent");
80
81
    /**
82
     * Fired when data is retrieved from the local cache.
83
     *
84
     * @event cacheResponseEvent
85
     * @param oArgs.request {Object} The request object.
86
     * @param oArgs.response {Object} The response object.
87
     * @param oArgs.callback {Object} The callback object.
88
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
89
     */
90
    this.createEvent("cacheResponseEvent");
91
92
    /**
93
     * Fired when a request is sent to the live data source.
94
     *
95
     * @event requestEvent
96
     * @param oArgs.request {Object} The request object.
97
     * @param oArgs.callback {Object} The callback object.
98
     * @param oArgs.tId {Number} Transaction ID.     
99
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
100
     */
101
    this.createEvent("requestEvent");
102
103
    /**
104
     * Fired when live data source sends response.
105
     *
106
     * @event responseEvent
107
     * @param oArgs.request {Object} The request object.
108
     * @param oArgs.response {Object} The raw response object.
109
     * @param oArgs.callback {Object} The callback object.
110
     * @param oArgs.tId {Number} Transaction ID.     
111
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
112
     */
113
    this.createEvent("responseEvent");
114
115
    /**
116
     * Fired when response is parsed.
117
     *
118
     * @event responseParseEvent
119
     * @param oArgs.request {Object} The request object.
120
     * @param oArgs.response {Object} The parsed response object.
121
     * @param oArgs.callback {Object} The callback object.
122
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
123
     */
124
    this.createEvent("responseParseEvent");
125
126
    /**
127
     * Fired when response is cached.
128
     *
129
     * @event responseCacheEvent
130
     * @param oArgs.request {Object} The request object.
131
     * @param oArgs.response {Object} The parsed response object.
132
     * @param oArgs.callback {Object} The callback object.
133
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
134
     */
135
    this.createEvent("responseCacheEvent");
136
    /**
137
     * Fired when an error is encountered with the live data source.
138
     *
139
     * @event dataErrorEvent
140
     * @param oArgs.request {Object} The request object.
141
     * @param oArgs.response {String} The response object (if available).
142
     * @param oArgs.callback {Object} The callback object.
143
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
144
     * @param oArgs.message {String} The error message.
145
     */
146
    this.createEvent("dataErrorEvent");
147
148
    /**
149
     * Fired when the local cache is flushed.
150
     *
151
     * @event cacheFlushEvent
152
     */
153
    this.createEvent("cacheFlushEvent");
154
155
    var DS = util.DataSourceBase;
156
    this._sName = "DataSource instance" + DS._nIndex;
157
    DS._nIndex++;
158
    YAHOO.log("DataSource initialized", "info", this.toString());
159
};
160
161
var DS = util.DataSourceBase;
162
163
lang.augmentObject(DS, {
164
165
/////////////////////////////////////////////////////////////////////////////
166
//
167
// DataSourceBase public constants
168
//
169
/////////////////////////////////////////////////////////////////////////////
170
171
/**
172
 * Type is unknown.
173
 *
174
 * @property TYPE_UNKNOWN
175
 * @type Number
176
 * @final
177
 * @default -1
178
 */
179
TYPE_UNKNOWN : -1,
180
181
/**
182
 * Type is a JavaScript Array.
183
 *
184
 * @property TYPE_JSARRAY
185
 * @type Number
186
 * @final
187
 * @default 0
188
 */
189
TYPE_JSARRAY : 0,
190
191
/**
192
 * Type is a JavaScript Function.
193
 *
194
 * @property TYPE_JSFUNCTION
195
 * @type Number
196
 * @final
197
 * @default 1
198
 */
199
TYPE_JSFUNCTION : 1,
200
201
/**
202
 * Type is hosted on a server via an XHR connection.
203
 *
204
 * @property TYPE_XHR
205
 * @type Number
206
 * @final
207
 * @default 2
208
 */
209
TYPE_XHR : 2,
210
211
/**
212
 * Type is JSON.
213
 *
214
 * @property TYPE_JSON
215
 * @type Number
216
 * @final
217
 * @default 3
218
 */
219
TYPE_JSON : 3,
220
221
/**
222
 * Type is XML.
223
 *
224
 * @property TYPE_XML
225
 * @type Number
226
 * @final
227
 * @default 4
228
 */
229
TYPE_XML : 4,
230
231
/**
232
 * Type is plain text.
233
 *
234
 * @property TYPE_TEXT
235
 * @type Number
236
 * @final
237
 * @default 5
238
 */
239
TYPE_TEXT : 5,
240
241
/**
242
 * Type is an HTML TABLE element. Data is parsed out of TR elements from all TBODY elements.
243
 *
244
 * @property TYPE_HTMLTABLE
245
 * @type Number
246
 * @final
247
 * @default 6
248
 */
249
TYPE_HTMLTABLE : 6,
250
251
/**
252
 * Type is hosted on a server via a dynamic script node.
253
 *
254
 * @property TYPE_SCRIPTNODE
255
 * @type Number
256
 * @final
257
 * @default 7
258
 */
259
TYPE_SCRIPTNODE : 7,
260
261
/**
262
 * Type is local.
263
 *
264
 * @property TYPE_LOCAL
265
 * @type Number
266
 * @final
267
 * @default 8
268
 */
269
TYPE_LOCAL : 8,
270
271
/**
272
 * Error message for invalid dataresponses.
273
 *
274
 * @property ERROR_DATAINVALID
275
 * @type String
276
 * @final
277
 * @default "Invalid data"
278
 */
279
ERROR_DATAINVALID : "Invalid data",
280
281
/**
282
 * Error message for null data responses.
283
 *
284
 * @property ERROR_DATANULL
285
 * @type String
286
 * @final
287
 * @default "Null data"
288
 */
289
ERROR_DATANULL : "Null data",
290
291
/////////////////////////////////////////////////////////////////////////////
292
//
293
// DataSourceBase private static properties
294
//
295
/////////////////////////////////////////////////////////////////////////////
296
297
/**
298
 * Internal class variable to index multiple DataSource instances.
299
 *
300
 * @property DataSourceBase._nIndex
301
 * @type Number
302
 * @private
303
 * @static
304
 */
305
_nIndex : 0,
306
307
/**
308
 * Internal class variable to assign unique transaction IDs.
309
 *
310
 * @property DataSourceBase._nTransactionId
311
 * @type Number
312
 * @private
313
 * @static
314
 */
315
_nTransactionId : 0,
316
317
/////////////////////////////////////////////////////////////////////////////
318
//
319
// DataSourceBase private static methods
320
//
321
/////////////////////////////////////////////////////////////////////////////
322
323
/**
324
 * Get an XPath-specified value for a given field from an XML node or document.
325
 *
326
 * @method _getLocationValue
327
 * @param field {String | Object} Field definition.
328
 * @param context {Object} XML node or document to search within.
329
 * @return {Object} Data value or null.
330
 * @static
331
 * @private
332
 */
333
_getLocationValue: function(field, context) {
334
    var locator = field.locator || field.key || field,
335
        xmldoc = context.ownerDocument || context,
336
        result, res, value = null;
337
338
    try {
339
        // Standards mode
340
        if(!lang.isUndefined(xmldoc.evaluate)) {
341
            result = xmldoc.evaluate(locator, context, xmldoc.createNSResolver(!context.ownerDocument ? context.documentElement : context.ownerDocument.documentElement), 0, null);
342
            while(res = result.iterateNext()) {
343
                value = res.textContent;
344
            }
345
        }
346
        // IE mode
347
        else {
348
            xmldoc.setProperty("SelectionLanguage", "XPath");
349
            result = context.selectNodes(locator)[0];
350
            value = result.value || result.text || null;
351
        }
352
        return value;
353
354
    }
355
    catch(e) {
356
    }
357
},
358
359
/////////////////////////////////////////////////////////////////////////////
360
//
361
// DataSourceBase public static methods
362
//
363
/////////////////////////////////////////////////////////////////////////////
364
365
/**
366
 * Executes a configured callback.  For object literal callbacks, the third
367
 * param determines whether to execute the success handler or failure handler.
368
 *  
369
 * @method issueCallback
370
 * @param callback {Function|Object} the callback to execute
371
 * @param params {Array} params to be passed to the callback method
372
 * @param error {Boolean} whether an error occurred
373
 * @param scope {Object} the scope from which to execute the callback
374
 * (deprecated - use an object literal callback)
375
 * @static     
376
 */
377
issueCallback : function (callback,params,error,scope) {
378
    if (lang.isFunction(callback)) {
379
        callback.apply(scope, params);
380
    } else if (lang.isObject(callback)) {
381
        scope = callback.scope || scope || window;
382
        var callbackFunc = callback.success;
383
        if (error) {
384
            callbackFunc = callback.failure;
385
        }
386
        if (callbackFunc) {
387
            callbackFunc.apply(scope, params.concat([callback.argument]));
388
        }
389
    }
390
},
391
392
/**
393
 * Converts data to type String.
394
 *
395
 * @method DataSourceBase.parseString
396
 * @param oData {String | Number | Boolean | Date | Array | Object} Data to parse.
397
 * The special values null and undefined will return null.
398
 * @return {String} A string, or null.
399
 * @static
400
 */
401
parseString : function(oData) {
402
    // Special case null and undefined
403
    if(!lang.isValue(oData)) {
404
        return null;
405
    }
406
    
407
    //Convert to string
408
    var string = oData + "";
409
410
    // Validate
411
    if(lang.isString(string)) {
412
        return string;
413
    }
414
    else {
415
        YAHOO.log("Could not convert data " + lang.dump(oData) + " to type String", "warn", this.toString());
416
        return null;
417
    }
418
},
419
420
/**
421
 * Converts data to type Number.
422
 *
423
 * @method DataSourceBase.parseNumber
424
 * @param oData {String | Number | Boolean} Data to convert. Note, the following
425
 * values return as null: null, undefined, NaN, "". 
426
 * @return {Number} A number, or null.
427
 * @static
428
 */
429
parseNumber : function(oData) {
430
    if(!lang.isValue(oData) || (oData === "")) {
431
        return null;
432
    }
433
434
    //Convert to number
435
    var number = oData * 1;
436
    
437
    // Validate
438
    if(lang.isNumber(number)) {
439
        return number;
440
    }
441
    else {
442
        YAHOO.log("Could not convert data " + lang.dump(oData) + " to type Number", "warn", this.toString());
443
        return null;
444
    }
445
},
446
// Backward compatibility
447
convertNumber : function(oData) {
448
    YAHOO.log("The method YAHOO.util.DataSourceBase.convertNumber() has been" +
449
    " deprecated in favor of YAHOO.util.DataSourceBase.parseNumber()", "warn",
450
    this.toString());
451
    return DS.parseNumber(oData);
452
},
453
454
/**
455
 * Converts data to type Date.
456
 *
457
 * @method DataSourceBase.parseDate
458
 * @param oData {Date | String | Number} Data to convert.
459
 * @return {Date} A Date instance.
460
 * @static
461
 */
462
parseDate : function(oData) {
463
    var date = null;
464
    
465
    //Convert to date
466
    if(!(oData instanceof Date)) {
467
        date = new Date(oData);
468
    }
469
    else {
470
        return oData;
471
    }
472
    
473
    // Validate
474
    if(date instanceof Date) {
475
        return date;
476
    }
477
    else {
478
        YAHOO.log("Could not convert data " + lang.dump(oData) + " to type Date", "warn", this.toString());
479
        return null;
480
    }
481
},
482
// Backward compatibility
483
convertDate : function(oData) {
484
    YAHOO.log("The method YAHOO.util.DataSourceBase.convertDate() has been" +
485
    " deprecated in favor of YAHOO.util.DataSourceBase.parseDate()", "warn",
486
    this.toString());
487
    return DS.parseDate(oData);
488
}
489
490
});
491
492
// Done in separate step so referenced functions are defined.
493
/**
494
 * Data parsing functions.
495
 * @property DataSource.Parser
496
 * @type Object
497
 * @static
498
 */
499
DS.Parser = {
500
    string   : DS.parseString,
501
    number   : DS.parseNumber,
502
    date     : DS.parseDate
503
};
504
505
// Prototype properties and methods
506
DS.prototype = {
507
508
/////////////////////////////////////////////////////////////////////////////
509
//
510
// DataSourceBase private properties
511
//
512
/////////////////////////////////////////////////////////////////////////////
513
514
/**
515
 * Name of DataSource instance.
516
 *
517
 * @property _sName
518
 * @type String
519
 * @private
520
 */
521
_sName : null,
522
523
/**
524
 * Local cache of data result object literals indexed chronologically.
525
 *
526
 * @property _aCache
527
 * @type Object[]
528
 * @private
529
 */
530
_aCache : null,
531
532
/**
533
 * Local queue of request connections, enabled if queue needs to be managed.
534
 *
535
 * @property _oQueue
536
 * @type Object
537
 * @private
538
 */
539
_oQueue : null,
540
541
/**
542
 * Array of polling interval IDs that have been enabled, needed to clear all intervals.
543
 *
544
 * @property _aIntervals
545
 * @type Array
546
 * @private
547
 */
548
_aIntervals : null,
549
550
/////////////////////////////////////////////////////////////////////////////
551
//
552
// DataSourceBase public properties
553
//
554
/////////////////////////////////////////////////////////////////////////////
555
556
/**
557
 * Max size of the local cache.  Set to 0 to turn off caching.  Caching is
558
 * useful to reduce the number of server connections.  Recommended only for data
559
 * sources that return comprehensive results for queries or when stale data is
560
 * not an issue.
561
 *
562
 * @property maxCacheEntries
563
 * @type Number
564
 * @default 0
565
 */
566
maxCacheEntries : 0,
567
568
 /**
569
 * Pointer to live database.
570
 *
571
 * @property liveData
572
 * @type Object
573
 */
574
liveData : null,
575
576
/**
577
 * Where the live data is held:
578
 * 
579
 * <dl>  
580
 *    <dt>TYPE_UNKNOWN</dt>
581
 *    <dt>TYPE_LOCAL</dt>
582
 *    <dt>TYPE_XHR</dt>
583
 *    <dt>TYPE_SCRIPTNODE</dt>
584
 *    <dt>TYPE_JSFUNCTION</dt>
585
 * </dl> 
586
 *  
587
 * @property dataType
588
 * @type Number
589
 * @default YAHOO.util.DataSourceBase.TYPE_UNKNOWN
590
 *
591
 */
592
dataType : DS.TYPE_UNKNOWN,
593
594
/**
595
 * Format of response:
596
 *  
597
 * <dl>  
598
 *    <dt>TYPE_UNKNOWN</dt>
599
 *    <dt>TYPE_JSARRAY</dt>
600
 *    <dt>TYPE_JSON</dt>
601
 *    <dt>TYPE_XML</dt>
602
 *    <dt>TYPE_TEXT</dt>
603
 *    <dt>TYPE_HTMLTABLE</dt> 
604
 * </dl> 
605
 *
606
 * @property responseType
607
 * @type Number
608
 * @default YAHOO.util.DataSourceBase.TYPE_UNKNOWN
609
 */
610
responseType : DS.TYPE_UNKNOWN,
611
612
/**
613
 * Response schema object literal takes a combination of the following properties:
614
 *
615
 * <dl>
616
 * <dt>resultsList</dt> <dd>Pointer to array of tabular data</dd>
617
 * <dt>resultNode</dt> <dd>Pointer to node name of row data (XML data only)</dd>
618
 * <dt>recordDelim</dt> <dd>Record delimiter (text data only)</dd>
619
 * <dt>fieldDelim</dt> <dd>Field delimiter (text data only)</dd>
620
 * <dt>fields</dt> <dd>Array of field names (aka keys), or array of object literals
621
 * such as: {key:"fieldname",parser:YAHOO.util.DataSourceBase.parseDate}</dd>
622
 * <dt>metaFields</dt> <dd>Object literal of keys to include in the oParsedResponse.meta collection</dd>
623
 * <dt>metaNode</dt> <dd>Name of the node under which to search for meta information in XML response data</dd>
624
 * </dl>
625
 *
626
 * @property responseSchema
627
 * @type Object
628
 */
629
responseSchema : null,
630
631
/**
632
 * Additional arguments passed to the JSON parse routine.  The JSON string
633
 * is the assumed first argument (where applicable).  This property is not
634
 * set by default, but the parse methods will use it if present.
635
 *
636
 * @property parseJSONArgs
637
 * @type {MIXED|Array} If an Array, contents are used as individual arguments.
638
 *                     Otherwise, value is used as an additional argument.
639
 */
640
// property intentionally undefined
641
 
642
/**
643
 * When working with XML data, setting this property to true enables support for
644
 * XPath-syntaxed locators in schema definitions.
645
 *
646
 * @property useXPath
647
 * @type Boolean
648
 * @default false
649
 */
650
useXPath : false,
651
652
/////////////////////////////////////////////////////////////////////////////
653
//
654
// DataSourceBase public methods
655
//
656
/////////////////////////////////////////////////////////////////////////////
657
658
/**
659
 * Public accessor to the unique name of the DataSource instance.
660
 *
661
 * @method toString
662
 * @return {String} Unique name of the DataSource instance.
663
 */
664
toString : function() {
665
    return this._sName;
666
},
667
668
/**
669
 * Overridable method passes request to cache and returns cached response if any,
670
 * refreshing the hit in the cache as the newest item. Returns null if there is
671
 * no cache hit.
672
 *
673
 * @method getCachedResponse
674
 * @param oRequest {Object} Request object.
675
 * @param oCallback {Object} Callback object.
676
 * @param oCaller {Object} (deprecated) Use callback object.
677
 * @return {Object} Cached response object or null.
678
 */
679
getCachedResponse : function(oRequest, oCallback, oCaller) {
680
    var aCache = this._aCache;
681
682
    // If cache is enabled...
683
    if(this.maxCacheEntries > 0) {        
684
        // Initialize local cache
685
        if(!aCache) {
686
            this._aCache = [];
687
            YAHOO.log("Cache initialized", "info", this.toString());
688
        }
689
        // Look in local cache
690
        else {
691
            var nCacheLength = aCache.length;
692
            if(nCacheLength > 0) {
693
                var oResponse = null;
694
                this.fireEvent("cacheRequestEvent", {request:oRequest,callback:oCallback,caller:oCaller});
695
        
696
                // Loop through each cached element
697
                for(var i = nCacheLength-1; i >= 0; i--) {
698
                    var oCacheElem = aCache[i];
699
        
700
                    // Defer cache hit logic to a public overridable method
701
                    if(this.isCacheHit(oRequest,oCacheElem.request)) {
702
                        // The cache returned a hit!
703
                        // Grab the cached response
704
                        oResponse = oCacheElem.response;
705
                        this.fireEvent("cacheResponseEvent", {request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});
706
                        
707
                        // Refresh the position of the cache hit
708
                        if(i < nCacheLength-1) {
709
                            // Remove element from its original location
710
                            aCache.splice(i,1);
711
                            // Add as newest
712
                            this.addToCache(oRequest, oResponse);
713
                            YAHOO.log("Refreshed cache position of the response for \"" +  oRequest + "\"", "info", this.toString());
714
                        }
715
                        
716
                        // Add a cache flag
717
                        oResponse.cached = true;
718
                        break;
719
                    }
720
                }
721
                YAHOO.log("The cached response for \"" + lang.dump(oRequest) +
722
                        "\" is " + lang.dump(oResponse), "info", this.toString());
723
                return oResponse;
724
            }
725
        }
726
    }
727
    else if(aCache) {
728
        this._aCache = null;
729
        YAHOO.log("Cache destroyed", "info", this.toString());
730
    }
731
    return null;
732
},
733
734
/**
735
 * Default overridable method matches given request to given cached request.
736
 * Returns true if is a hit, returns false otherwise.  Implementers should
737
 * override this method to customize the cache-matching algorithm.
738
 *
739
 * @method isCacheHit
740
 * @param oRequest {Object} Request object.
741
 * @param oCachedRequest {Object} Cached request object.
742
 * @return {Boolean} True if given request matches cached request, false otherwise.
743
 */
744
isCacheHit : function(oRequest, oCachedRequest) {
745
    return (oRequest === oCachedRequest);
746
},
747
748
/**
749
 * Adds a new item to the cache. If cache is full, evicts the stalest item
750
 * before adding the new item.
751
 *
752
 * @method addToCache
753
 * @param oRequest {Object} Request object.
754
 * @param oResponse {Object} Response object to cache.
755
 */
756
addToCache : function(oRequest, oResponse) {
757
    var aCache = this._aCache;
758
    if(!aCache) {
759
        return;
760
    }
761
762
    // If the cache is full, make room by removing stalest element (index=0)
763
    while(aCache.length >= this.maxCacheEntries) {
764
        aCache.shift();
765
    }
766
767
    // Add to cache in the newest position, at the end of the array
768
    var oCacheElem = {request:oRequest,response:oResponse};
769
    aCache[aCache.length] = oCacheElem;
770
    this.fireEvent("responseCacheEvent", {request:oRequest,response:oResponse});
771
    YAHOO.log("Cached the response for \"" +  oRequest + "\"", "info", this.toString());
772
},
773
774
/**
775
 * Flushes cache.
776
 *
777
 * @method flushCache
778
 */
779
flushCache : function() {
780
    if(this._aCache) {
781
        this._aCache = [];
782
        this.fireEvent("cacheFlushEvent");
783
        YAHOO.log("Flushed the cache", "info", this.toString());
784
    }
785
},
786
787
/**
788
 * Sets up a polling mechanism to send requests at set intervals and forward
789
 * responses to given callback.
790
 *
791
 * @method setInterval
792
 * @param nMsec {Number} Length of interval in milliseconds.
793
 * @param oRequest {Object} Request object.
794
 * @param oCallback {Function} Handler function to receive the response.
795
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
796
 * @return {Number} Interval ID.
797
 */
798
setInterval : function(nMsec, oRequest, oCallback, oCaller) {
799
    if(lang.isNumber(nMsec) && (nMsec >= 0)) {
800
        YAHOO.log("Enabling polling to live data for \"" + oRequest + "\" at interval " + nMsec, "info", this.toString());
801
        var oSelf = this;
802
        var nId = setInterval(function() {
803
            oSelf.makeConnection(oRequest, oCallback, oCaller);
804
        }, nMsec);
805
        this._aIntervals.push(nId);
806
        return nId;
807
    }
808
    else {
809
        YAHOO.log("Could not enable polling to live data for \"" + oRequest + "\" at interval " + nMsec, "info", this.toString());
810
    }
811
},
812
813
/**
814
 * Disables polling mechanism associated with the given interval ID.
815
 *
816
 * @method clearInterval
817
 * @param nId {Number} Interval ID.
818
 */
819
clearInterval : function(nId) {
820
    // Remove from tracker if there
821
    var tracker = this._aIntervals || [];
822
    for(var i=tracker.length-1; i>-1; i--) {
823
        if(tracker[i] === nId) {
824
            tracker.splice(i,1);
825
            clearInterval(nId);
826
        }
827
    }
828
},
829
830
/**
831
 * Disables all known polling intervals.
832
 *
833
 * @method clearAllIntervals
834
 */
835
clearAllIntervals : function() {
836
    var tracker = this._aIntervals || [];
837
    for(var i=tracker.length-1; i>-1; i--) {
838
        clearInterval(tracker[i]);
839
    }
840
    tracker = [];
841
},
842
843
/**
844
 * First looks for cached response, then sends request to live data. The
845
 * following arguments are passed to the callback function:
846
 *     <dl>
847
 *     <dt><code>oRequest</code></dt>
848
 *     <dd>The same value that was passed in as the first argument to sendRequest.</dd>
849
 *     <dt><code>oParsedResponse</code></dt>
850
 *     <dd>An object literal containing the following properties:
851
 *         <dl>
852
 *         <dt><code>tId</code></dt>
853
 *         <dd>Unique transaction ID number.</dd>
854
 *         <dt><code>results</code></dt>
855
 *         <dd>Schema-parsed data results.</dd>
856
 *         <dt><code>error</code></dt>
857
 *         <dd>True in cases of data error.</dd>
858
 *         <dt><code>cached</code></dt>
859
 *         <dd>True when response is returned from DataSource cache.</dd> 
860
 *         <dt><code>meta</code></dt>
861
 *         <dd>Schema-parsed meta data.</dd>
862
 *         </dl>
863
 *     <dt><code>oPayload</code></dt>
864
 *     <dd>The same value as was passed in as <code>argument</code> in the oCallback object literal.</dd>
865
 *     </dl> 
866
 *
867
 * @method sendRequest
868
 * @param oRequest {Object} Request object.
869
 * @param oCallback {Object} An object literal with the following properties:
870
 *     <dl>
871
 *     <dt><code>success</code></dt>
872
 *     <dd>The function to call when the data is ready.</dd>
873
 *     <dt><code>failure</code></dt>
874
 *     <dd>The function to call upon a response failure condition.</dd>
875
 *     <dt><code>scope</code></dt>
876
 *     <dd>The object to serve as the scope for the success and failure handlers.</dd>
877
 *     <dt><code>argument</code></dt>
878
 *     <dd>Arbitrary data that will be passed back to the success and failure handlers.</dd>
879
 *     </dl> 
880
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
881
 * @return {Number} Transaction ID, or null if response found in cache.
882
 */
883
sendRequest : function(oRequest, oCallback, oCaller) {
884
    // First look in cache
885
    var oCachedResponse = this.getCachedResponse(oRequest, oCallback, oCaller);
886
    if(oCachedResponse) {
887
        DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);
888
        return null;
889
    }
890
891
892
    // Not in cache, so forward request to live data
893
    YAHOO.log("Making connection to live data for \"" + oRequest + "\"", "info", this.toString());
894
    return this.makeConnection(oRequest, oCallback, oCaller);
895
},
896
897
/**
898
 * Overridable default method generates a unique transaction ID and passes 
899
 * the live data reference directly to the  handleResponse function. This
900
 * method should be implemented by subclasses to achieve more complex behavior
901
 * or to access remote data.          
902
 *
903
 * @method makeConnection
904
 * @param oRequest {Object} Request object.
905
 * @param oCallback {Object} Callback object literal.
906
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
907
 * @return {Number} Transaction ID.
908
 */
909
makeConnection : function(oRequest, oCallback, oCaller) {
910
    var tId = DS._nTransactionId++;
911
    this.fireEvent("requestEvent", {tId:tId, request:oRequest,callback:oCallback,caller:oCaller});
912
913
    /* accounts for the following cases:
914
    YAHOO.util.DataSourceBase.TYPE_UNKNOWN
915
    YAHOO.util.DataSourceBase.TYPE_JSARRAY
916
    YAHOO.util.DataSourceBase.TYPE_JSON
917
    YAHOO.util.DataSourceBase.TYPE_HTMLTABLE
918
    YAHOO.util.DataSourceBase.TYPE_XML
919
    YAHOO.util.DataSourceBase.TYPE_TEXT
920
    */
921
    var oRawResponse = this.liveData;
922
    
923
    this.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
924
    return tId;
925
},
926
927
/**
928
 * Receives raw data response and type converts to XML, JSON, etc as necessary.
929
 * Forwards oFullResponse to appropriate parsing function to get turned into
930
 * oParsedResponse. Calls doBeforeCallback() and adds oParsedResponse to 
931
 * the cache when appropriate before calling issueCallback().
932
 * 
933
 * The oParsedResponse object literal has the following properties:
934
 * <dl>
935
 *     <dd><dt>tId {Number}</dt> Unique transaction ID</dd>
936
 *     <dd><dt>results {Array}</dt> Array of parsed data results</dd>
937
 *     <dd><dt>meta {Object}</dt> Object literal of meta values</dd> 
938
 *     <dd><dt>error {Boolean}</dt> (optional) True if there was an error</dd>
939
 *     <dd><dt>cached {Boolean}</dt> (optional) True if response was cached</dd>
940
 * </dl>
941
 *
942
 * @method handleResponse
943
 * @param oRequest {Object} Request object
944
 * @param oRawResponse {Object} The raw response from the live database.
945
 * @param oCallback {Object} Callback object literal.
946
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
947
 * @param tId {Number} Transaction ID.
948
 */
949
handleResponse : function(oRequest, oRawResponse, oCallback, oCaller, tId) {
950
    this.fireEvent("responseEvent", {tId:tId, request:oRequest, response:oRawResponse,
951
            callback:oCallback, caller:oCaller});
952
    YAHOO.log("Received live data response for \"" + oRequest + "\"", "info", this.toString());
953
    var xhr = (this.dataType == DS.TYPE_XHR) ? true : false;
954
    var oParsedResponse = null;
955
    var oFullResponse = oRawResponse;
956
    
957
    // Try to sniff data type if it has not been defined
958
    if(this.responseType === DS.TYPE_UNKNOWN) {
959
        var ctype = (oRawResponse && oRawResponse.getResponseHeader) ? oRawResponse.getResponseHeader["Content-Type"] : null;
960
        if(ctype) {
961
             // xml
962
            if(ctype.indexOf("text/xml") > -1) {
963
                this.responseType = DS.TYPE_XML;
964
            }
965
            else if(ctype.indexOf("application/json") > -1) { // json
966
                this.responseType = DS.TYPE_JSON;
967
            }
968
            else if(ctype.indexOf("text/plain") > -1) { // text
969
                this.responseType = DS.TYPE_TEXT;
970
            }
971
        }
972
        else {
973
            if(YAHOO.lang.isArray(oRawResponse)) { // array
974
                this.responseType = DS.TYPE_JSARRAY;
975
            }
976
             // xml
977
            else if(oRawResponse && oRawResponse.nodeType && (oRawResponse.nodeType === 9 || oRawResponse.nodeType === 1 || oRawResponse.nodeType === 11)) {
978
                this.responseType = DS.TYPE_XML;
979
            }
980
            else if(oRawResponse && oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
981
                this.responseType = DS.TYPE_HTMLTABLE;
982
            }    
983
            else if(YAHOO.lang.isObject(oRawResponse)) { // json
984
                this.responseType = DS.TYPE_JSON;
985
            }
986
            else if(YAHOO.lang.isString(oRawResponse)) { // text
987
                this.responseType = DS.TYPE_TEXT;
988
            }
989
        }
990
    }
991
992
    switch(this.responseType) {
993
        case DS.TYPE_JSARRAY:
994
            if(xhr && oRawResponse && oRawResponse.responseText) {
995
                oFullResponse = oRawResponse.responseText; 
996
            }
997
            try {
998
                // Convert to JS array if it's a string
999
                if(lang.isString(oFullResponse)) {
1000
                    var parseArgs = [oFullResponse].concat(this.parseJSONArgs);
1001
                    // Check for YUI JSON Util
1002
                    if(lang.JSON) {
1003
                        oFullResponse = lang.JSON.parse.apply(lang.JSON,parseArgs);
1004
                    }
1005
                    // Look for JSON parsers using an API similar to json2.js
1006
                    else if(window.JSON && JSON.parse) {
1007
                        oFullResponse = JSON.parse.apply(JSON,parseArgs);
1008
                    }
1009
                    // Look for JSON parsers using an API similar to json.js
1010
                    else if(oFullResponse.parseJSON) {
1011
                        oFullResponse = oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));
1012
                    }
1013
                    // No JSON lib found so parse the string
1014
                    else {
1015
                        // Trim leading spaces
1016
                        while (oFullResponse.length > 0 &&
1017
                                (oFullResponse.charAt(0) != "{") &&
1018
                                (oFullResponse.charAt(0) != "[")) {
1019
                            oFullResponse = oFullResponse.substring(1, oFullResponse.length);
1020
                        }
1021
1022
                        if(oFullResponse.length > 0) {
1023
                            // Strip extraneous stuff at the end
1024
                            var arrayEnd =
1025
Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));
1026
                            oFullResponse = oFullResponse.substring(0,arrayEnd+1);
1027
1028
                            // Turn the string into an object literal...
1029
                            // ...eval is necessary here
1030
                            oFullResponse = eval("(" + oFullResponse + ")");
1031
1032
                        }
1033
                    }
1034
                }
1035
            }
1036
            catch(e1) {
1037
            }
1038
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1039
            oParsedResponse = this.parseArrayData(oRequest, oFullResponse);
1040
            break;
1041
        case DS.TYPE_JSON:
1042
            if(xhr && oRawResponse && oRawResponse.responseText) {
1043
                oFullResponse = oRawResponse.responseText;
1044
            }
1045
            try {
1046
                // Convert to JSON object if it's a string
1047
                if(lang.isString(oFullResponse)) {
1048
                    var parseArgs = [oFullResponse].concat(this.parseJSONArgs);
1049
                    // Check for YUI JSON Util
1050
                    if(lang.JSON) {
1051
                        oFullResponse = lang.JSON.parse.apply(lang.JSON,parseArgs);
1052
                    }
1053
                    // Look for JSON parsers using an API similar to json2.js
1054
                    else if(window.JSON && JSON.parse) {
1055
                        oFullResponse = JSON.parse.apply(JSON,parseArgs);
1056
                    }
1057
                    // Look for JSON parsers using an API similar to json.js
1058
                    else if(oFullResponse.parseJSON) {
1059
                        oFullResponse = oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));
1060
                    }
1061
                    // No JSON lib found so parse the string
1062
                    else {
1063
                        // Trim leading spaces
1064
                        while (oFullResponse.length > 0 &&
1065
                                (oFullResponse.charAt(0) != "{") &&
1066
                                (oFullResponse.charAt(0) != "[")) {
1067
                            oFullResponse = oFullResponse.substring(1, oFullResponse.length);
1068
                        }
1069
    
1070
                        if(oFullResponse.length > 0) {
1071
                            // Strip extraneous stuff at the end
1072
                            var objEnd = Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));
1073
                            oFullResponse = oFullResponse.substring(0,objEnd+1);
1074
    
1075
                            // Turn the string into an object literal...
1076
                            // ...eval is necessary here
1077
                            oFullResponse = eval("(" + oFullResponse + ")");
1078
    
1079
                        }
1080
                    }
1081
                }
1082
            }
1083
            catch(e) {
1084
            }
1085
1086
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1087
            oParsedResponse = this.parseJSONData(oRequest, oFullResponse);
1088
            break;
1089
        case DS.TYPE_HTMLTABLE:
1090
            if(xhr && oRawResponse.responseText) {
1091
                var el = document.createElement('div');
1092
                el.innerHTML = oRawResponse.responseText;
1093
                oFullResponse = el.getElementsByTagName('table')[0];
1094
            }
1095
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1096
            oParsedResponse = this.parseHTMLTableData(oRequest, oFullResponse);
1097
            break;
1098
        case DS.TYPE_XML:
1099
            if(xhr && oRawResponse.responseXML) {
1100
                oFullResponse = oRawResponse.responseXML;
1101
            }
1102
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1103
            oParsedResponse = this.parseXMLData(oRequest, oFullResponse);
1104
            break;
1105
        case DS.TYPE_TEXT:
1106
            if(xhr && lang.isString(oRawResponse.responseText)) {
1107
                oFullResponse = oRawResponse.responseText;
1108
            }
1109
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1110
            oParsedResponse = this.parseTextData(oRequest, oFullResponse);
1111
            break;
1112
        default:
1113
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1114
            oParsedResponse = this.parseData(oRequest, oFullResponse);
1115
            break;
1116
    }
1117
1118
1119
    // Clean up for consistent signature
1120
    oParsedResponse = oParsedResponse || {};
1121
    if(!oParsedResponse.results) {
1122
        oParsedResponse.results = [];
1123
    }
1124
    if(!oParsedResponse.meta) {
1125
        oParsedResponse.meta = {};
1126
    }
1127
1128
    // Success
1129
    if(!oParsedResponse.error) {
1130
        // Last chance to touch the raw response or the parsed response
1131
        oParsedResponse = this.doBeforeCallback(oRequest, oFullResponse, oParsedResponse, oCallback);
1132
        this.fireEvent("responseParseEvent", {request:oRequest,
1133
                response:oParsedResponse, callback:oCallback, caller:oCaller});
1134
        // Cache the response
1135
        this.addToCache(oRequest, oParsedResponse);
1136
    }
1137
    // Error
1138
    else {
1139
        // Be sure the error flag is on
1140
        oParsedResponse.error = true;
1141
        this.fireEvent("dataErrorEvent", {request:oRequest, response: oRawResponse, callback:oCallback, 
1142
                caller:oCaller, message:DS.ERROR_DATANULL});
1143
        YAHOO.log(DS.ERROR_DATANULL, "error", this.toString());
1144
    }
1145
1146
    // Send the response back to the caller
1147
    oParsedResponse.tId = tId;
1148
    DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);
1149
},
1150
1151
/**
1152
 * Overridable method gives implementers access to the original full response
1153
 * before the data gets parsed. Implementers should take care not to return an
1154
 * unparsable or otherwise invalid response.
1155
 *
1156
 * @method doBeforeParseData
1157
 * @param oRequest {Object} Request object.
1158
 * @param oFullResponse {Object} The full response from the live database.
1159
 * @param oCallback {Object} The callback object.  
1160
 * @return {Object} Full response for parsing.
1161
  
1162
 */
1163
doBeforeParseData : function(oRequest, oFullResponse, oCallback) {
1164
    return oFullResponse;
1165
},
1166
1167
/**
1168
 * Overridable method gives implementers access to the original full response and
1169
 * the parsed response (parsed against the given schema) before the data
1170
 * is added to the cache (if applicable) and then sent back to callback function.
1171
 * This is your chance to access the raw response and/or populate the parsed
1172
 * response with any custom data.
1173
 *
1174
 * @method doBeforeCallback
1175
 * @param oRequest {Object} Request object.
1176
 * @param oFullResponse {Object} The full response from the live database.
1177
 * @param oParsedResponse {Object} The parsed response to return to calling object.
1178
 * @param oCallback {Object} The callback object. 
1179
 * @return {Object} Parsed response object.
1180
 */
1181
doBeforeCallback : function(oRequest, oFullResponse, oParsedResponse, oCallback) {
1182
    return oParsedResponse;
1183
},
1184
1185
/**
1186
 * Overridable method parses data of generic RESPONSE_TYPE into a response object.
1187
 *
1188
 * @method parseData
1189
 * @param oRequest {Object} Request object.
1190
 * @param oFullResponse {Object} The full Array from the live database.
1191
 * @return {Object} Parsed response object with the following properties:<br>
1192
 *     - results {Array} Array of parsed data results<br>
1193
 *     - meta {Object} Object literal of meta values<br>
1194
 *     - error {Boolean} (optional) True if there was an error<br>
1195
 */
1196
parseData : function(oRequest, oFullResponse) {
1197
    if(lang.isValue(oFullResponse)) {
1198
        var oParsedResponse = {results:oFullResponse,meta:{}};
1199
        YAHOO.log("Parsed generic data is " +
1200
                lang.dump(oParsedResponse), "info", this.toString());
1201
        return oParsedResponse;
1202
1203
    }
1204
    YAHOO.log("Generic data could not be parsed: " + lang.dump(oFullResponse), 
1205
            "error", this.toString());
1206
    return null;
1207
},
1208
1209
/**
1210
 * Overridable method parses Array data into a response object.
1211
 *
1212
 * @method parseArrayData
1213
 * @param oRequest {Object} Request object.
1214
 * @param oFullResponse {Object} The full Array from the live database.
1215
 * @return {Object} Parsed response object with the following properties:<br>
1216
 *     - results (Array) Array of parsed data results<br>
1217
 *     - error (Boolean) True if there was an error
1218
 */
1219
parseArrayData : function(oRequest, oFullResponse) {
1220
    if(lang.isArray(oFullResponse)) {
1221
        var results = [],
1222
            i, j,
1223
            rec, field, data;
1224
        
1225
        // Parse for fields
1226
        if(lang.isArray(this.responseSchema.fields)) {
1227
            var fields = this.responseSchema.fields;
1228
            for (i = fields.length - 1; i >= 0; --i) {
1229
                if (typeof fields[i] !== 'object') {
1230
                    fields[i] = { key : fields[i] };
1231
                }
1232
            }
1233
1234
            var parsers = {}, p;
1235
            for (i = fields.length - 1; i >= 0; --i) {
1236
                p = (typeof fields[i].parser === 'function' ?
1237
                          fields[i].parser :
1238
                          DS.Parser[fields[i].parser+'']) || fields[i].converter;
1239
                if (p) {
1240
                    parsers[fields[i].key] = p;
1241
                }
1242
            }
1243
1244
            var arrType = lang.isArray(oFullResponse[0]);
1245
            for(i=oFullResponse.length-1; i>-1; i--) {
1246
                var oResult = {};
1247
                rec = oFullResponse[i];
1248
                if (typeof rec === 'object') {
1249
                    for(j=fields.length-1; j>-1; j--) {
1250
                        field = fields[j];
1251
                        data = arrType ? rec[j] : rec[field.key];
1252
1253
                        if (parsers[field.key]) {
1254
                            data = parsers[field.key].call(this,data);
1255
                        }
1256
1257
                        // Safety measure
1258
                        if(data === undefined) {
1259
                            data = null;
1260
                        }
1261
1262
                        oResult[field.key] = data;
1263
                    }
1264
                }
1265
                else if (lang.isString(rec)) {
1266
                    for(j=fields.length-1; j>-1; j--) {
1267
                        field = fields[j];
1268
                        data = rec;
1269
1270
                        if (parsers[field.key]) {
1271
                            data = parsers[field.key].call(this,data);
1272
                        }
1273
1274
                        // Safety measure
1275
                        if(data === undefined) {
1276
                            data = null;
1277
                        }
1278
1279
                        oResult[field.key] = data;
1280
                    }                
1281
                }
1282
                results[i] = oResult;
1283
            }    
1284
        }
1285
        // Return entire data set
1286
        else {
1287
            results = oFullResponse;
1288
        }
1289
        var oParsedResponse = {results:results};
1290
        YAHOO.log("Parsed array data is " +
1291
                lang.dump(oParsedResponse), "info", this.toString());
1292
        return oParsedResponse;
1293
1294
    }
1295
    YAHOO.log("Array data could not be parsed: " + lang.dump(oFullResponse), 
1296
            "error", this.toString());
1297
    return null;
1298
},
1299
1300
/**
1301
 * Overridable method parses plain text data into a response object.
1302
 *
1303
 * @method parseTextData
1304
 * @param oRequest {Object} Request object.
1305
 * @param oFullResponse {Object} The full text response from the live database.
1306
 * @return {Object} Parsed response object with the following properties:<br>
1307
 *     - results (Array) Array of parsed data results<br>
1308
 *     - error (Boolean) True if there was an error
1309
 */
1310
parseTextData : function(oRequest, oFullResponse) {
1311
    if(lang.isString(oFullResponse)) {
1312
        if(lang.isString(this.responseSchema.recordDelim) &&
1313
                lang.isString(this.responseSchema.fieldDelim)) {
1314
            var oParsedResponse = {results:[]};
1315
            var recDelim = this.responseSchema.recordDelim;
1316
            var fieldDelim = this.responseSchema.fieldDelim;
1317
            if(oFullResponse.length > 0) {
1318
                // Delete the last line delimiter at the end of the data if it exists
1319
                var newLength = oFullResponse.length-recDelim.length;
1320
                if(oFullResponse.substr(newLength) == recDelim) {
1321
                    oFullResponse = oFullResponse.substr(0, newLength);
1322
                }
1323
                if(oFullResponse.length > 0) {
1324
                    // Split along record delimiter to get an array of strings
1325
                    var recordsarray = oFullResponse.split(recDelim);
1326
                    // Cycle through each record
1327
                    for(var i = 0, len = recordsarray.length, recIdx = 0; i < len; ++i) {
1328
                        var bError = false,
1329
                            sRecord = recordsarray[i];
1330
                        if (lang.isString(sRecord) && (sRecord.length > 0)) {
1331
                            // Split each record along field delimiter to get data
1332
                            var fielddataarray = recordsarray[i].split(fieldDelim);
1333
                            var oResult = {};
1334
                            
1335
                            // Filter for fields data
1336
                            if(lang.isArray(this.responseSchema.fields)) {
1337
                                var fields = this.responseSchema.fields;
1338
                                for(var j=fields.length-1; j>-1; j--) {
1339
                                    try {
1340
                                        // Remove quotation marks from edges, if applicable
1341
                                        var data = fielddataarray[j];
1342
                                        if (lang.isString(data)) {
1343
                                            if(data.charAt(0) == "\"") {
1344
                                                data = data.substr(1);
1345
                                            }
1346
                                            if(data.charAt(data.length-1) == "\"") {
1347
                                                data = data.substr(0,data.length-1);
1348
                                            }
1349
                                            var field = fields[j];
1350
                                            var key = (lang.isValue(field.key)) ? field.key : field;
1351
                                            // Backward compatibility
1352
                                            if(!field.parser && field.converter) {
1353
                                                field.parser = field.converter;
1354
                                                YAHOO.log("The field property converter has been deprecated" +
1355
                                                        " in favor of parser", "warn", this.toString());
1356
                                            }
1357
                                            var parser = (typeof field.parser === 'function') ?
1358
                                                field.parser :
1359
                                                DS.Parser[field.parser+''];
1360
                                            if(parser) {
1361
                                                data = parser.call(this, data);
1362
                                            }
1363
                                            // Safety measure
1364
                                            if(data === undefined) {
1365
                                                data = null;
1366
                                            }
1367
                                            oResult[key] = data;
1368
                                        }
1369
                                        else {
1370
                                            bError = true;
1371
                                        }
1372
                                    }
1373
                                    catch(e) {
1374
                                        bError = true;
1375
                                    }
1376
                                }
1377
                            }            
1378
                            // No fields defined so pass along all data as an array
1379
                            else {
1380
                                oResult = fielddataarray;
1381
                            }
1382
                            if(!bError) {
1383
                                oParsedResponse.results[recIdx++] = oResult;
1384
                            }
1385
                        }
1386
                    }
1387
                }
1388
            }
1389
            YAHOO.log("Parsed text data is " +
1390
                    lang.dump(oParsedResponse), "info", this.toString());
1391
            return oParsedResponse;
1392
        }
1393
    }
1394
    YAHOO.log("Text data could not be parsed: " + lang.dump(oFullResponse), 
1395
            "error", this.toString());
1396
    return null;
1397
            
1398
},
1399
1400
/**
1401
 * Overridable method parses XML data for one result into an object literal.
1402
 *
1403
 * @method parseXMLResult
1404
 * @param result {XML} XML for one result.
1405
 * @return {Object} Object literal of data for one result.
1406
 */
1407
parseXMLResult : function(result) {
1408
    var oResult = {},
1409
        schema = this.responseSchema;
1410
        
1411
    try {
1412
        // Loop through each data field in each result using the schema
1413
        for(var m = schema.fields.length-1; m >= 0 ; m--) {
1414
            var field = schema.fields[m];
1415
            var key = (lang.isValue(field.key)) ? field.key : field;
1416
            var data = null;
1417
1418
            if(this.useXPath) {
1419
                data = YAHOO.util.DataSource._getLocationValue(field, result);
1420
            }
1421
            else {
1422
                // Values may be held in an attribute...
1423
                var xmlAttr = result.attributes.getNamedItem(key);
1424
                if(xmlAttr) {
1425
                    data = xmlAttr.value;
1426
                }
1427
                // ...or in a node
1428
                else {
1429
                    var xmlNode = result.getElementsByTagName(key);
1430
                    if(xmlNode && xmlNode.item(0)) {
1431
                        var item = xmlNode.item(0);
1432
                        // For IE, then DOM...
1433
                        data = (item) ? ((item.text) ? item.text : (item.textContent) ? item.textContent : null) : null;
1434
                        // ...then fallback, but check for multiple child nodes
1435
                        if(!data) {
1436
                            var datapieces = [];
1437
                            for(var j=0, len=item.childNodes.length; j<len; j++) {
1438
                                if(item.childNodes[j].nodeValue) {
1439
                                    datapieces[datapieces.length] = item.childNodes[j].nodeValue;
1440
                                }
1441
                            }
1442
                            if(datapieces.length > 0) {
1443
                                data = datapieces.join("");
1444
                            }
1445
                        }
1446
                    }
1447
                }
1448
            }
1449
            
1450
            
1451
            // Safety net
1452
            if(data === null) {
1453
                   data = "";
1454
            }
1455
            // Backward compatibility
1456
            if(!field.parser && field.converter) {
1457
                field.parser = field.converter;
1458
                YAHOO.log("The field property converter has been deprecated" +
1459
                        " in favor of parser", "warn", this.toString());
1460
            }
1461
            var parser = (typeof field.parser === 'function') ?
1462
                field.parser :
1463
                DS.Parser[field.parser+''];
1464
            if(parser) {
1465
                data = parser.call(this, data);
1466
            }
1467
            // Safety measure
1468
            if(data === undefined) {
1469
                data = null;
1470
            }
1471
            oResult[key] = data;
1472
        }
1473
    }
1474
    catch(e) {
1475
        YAHOO.log("Error while parsing XML result: " + e.message);
1476
    }
1477
1478
    return oResult;
1479
},
1480
1481
1482
1483
/**
1484
 * Overridable method parses XML data into a response object.
1485
 *
1486
 * @method parseXMLData
1487
 * @param oRequest {Object} Request object.
1488
 * @param oFullResponse {Object} The full XML response from the live database.
1489
 * @return {Object} Parsed response object with the following properties<br>
1490
 *     - results (Array) Array of parsed data results<br>
1491
 *     - error (Boolean) True if there was an error
1492
 */
1493
parseXMLData : function(oRequest, oFullResponse) {
1494
    var bError = false,
1495
        schema = this.responseSchema,
1496
        oParsedResponse = {meta:{}},
1497
        xmlList = null,
1498
        metaNode      = schema.metaNode,
1499
        metaLocators  = schema.metaFields || {},
1500
        i,k,loc,v;
1501
1502
    // In case oFullResponse is something funky
1503
    try {
1504
        // Pull any meta identified
1505
        if(this.useXPath) {
1506
            for (k in metaLocators) {
1507
                oParsedResponse.meta[k] = YAHOO.util.DataSource._getLocationValue(metaLocators[k], oFullResponse);
1508
            }
1509
        }
1510
        else {
1511
            metaNode = metaNode ? oFullResponse.getElementsByTagName(metaNode)[0] :
1512
                       oFullResponse;
1513
1514
            if (metaNode) {
1515
                for (k in metaLocators) {
1516
                    if (lang.hasOwnProperty(metaLocators, k)) {
1517
                        loc = metaLocators[k];
1518
                        // Look for a node
1519
                        v = metaNode.getElementsByTagName(loc)[0];
1520
1521
                        if (v) {
1522
                            v = v.firstChild.nodeValue;
1523
                        } else {
1524
                            // Look for an attribute
1525
                            v = metaNode.attributes.getNamedItem(loc);
1526
                            if (v) {
1527
                                v = v.value;
1528
                            }
1529
                        }
1530
1531
                        if (lang.isValue(v)) {
1532
                            oParsedResponse.meta[k] = v;
1533
                        }
1534
                    }
1535
                }
1536
            }
1537
        }
1538
        
1539
        // For result data
1540
        xmlList = (schema.resultNode) ?
1541
            oFullResponse.getElementsByTagName(schema.resultNode) :
1542
            null;
1543
    }
1544
    catch(e) {
1545
        YAHOO.log("Error while parsing XML data: " + e.message);
1546
    }
1547
    if(!xmlList || !lang.isArray(schema.fields)) {
1548
        bError = true;
1549
    }
1550
    // Loop through each result
1551
    else {
1552
        oParsedResponse.results = [];
1553
        for(i = xmlList.length-1; i >= 0 ; --i) {
1554
            var oResult = this.parseXMLResult(xmlList.item(i));
1555
            // Capture each array of values into an array of results
1556
            oParsedResponse.results[i] = oResult;
1557
        }
1558
    }
1559
    if(bError) {
1560
        YAHOO.log("XML data could not be parsed: " +
1561
                lang.dump(oFullResponse), "error", this.toString());
1562
        oParsedResponse.error = true;
1563
    }
1564
    else {
1565
        YAHOO.log("Parsed XML data is " +
1566
                lang.dump(oParsedResponse), "info", this.toString());
1567
    }
1568
    return oParsedResponse;
1569
},
1570
1571
/**
1572
 * Overridable method parses JSON data into a response object.
1573
 *
1574
 * @method parseJSONData
1575
 * @param oRequest {Object} Request object.
1576
 * @param oFullResponse {Object} The full JSON from the live database.
1577
 * @return {Object} Parsed response object with the following properties<br>
1578
 *     - results (Array) Array of parsed data results<br>
1579
 *     - error (Boolean) True if there was an error
1580
 */
1581
parseJSONData : function(oRequest, oFullResponse) {
1582
    var oParsedResponse = {results:[],meta:{}};
1583
    
1584
    if(lang.isObject(oFullResponse) && this.responseSchema.resultsList) {
1585
        var schema = this.responseSchema,
1586
            fields          = schema.fields,
1587
            resultsList     = oFullResponse,
1588
            results         = [],
1589
            metaFields      = schema.metaFields || {},
1590
            fieldParsers    = [],
1591
            fieldPaths      = [],
1592
            simpleFields    = [],
1593
            bError          = false,
1594
            i,len,j,v,key,parser,path;
1595
1596
        // Function to convert the schema's fields into walk paths
1597
        var buildPath = function (needle) {
1598
            var path = null, keys = [], i = 0;
1599
            if (needle) {
1600
                // Strip the ["string keys"] and [1] array indexes
1601
                needle = needle.
1602
                    replace(/\[(['"])(.*?)\1\]/g,
1603
                    function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}).
1604
                    replace(/\[(\d+)\]/g,
1605
                    function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}).
1606
                    replace(/^\./,''); // remove leading dot
1607
1608
                // If the cleaned needle contains invalid characters, the
1609
                // path is invalid
1610
                if (!/[^\w\.\$@]/.test(needle)) {
1611
                    path = needle.split('.');
1612
                    for (i=path.length-1; i >= 0; --i) {
1613
                        if (path[i].charAt(0) === '@') {
1614
                            path[i] = keys[parseInt(path[i].substr(1),10)];
1615
                        }
1616
                    }
1617
                }
1618
                else {
1619
                    YAHOO.log("Invalid locator: " + needle, "error", this.toString());
1620
                }
1621
            }
1622
            return path;
1623
        };
1624
1625
1626
        // Function to walk a path and return the pot of gold
1627
        var walkPath = function (path, origin) {
1628
            var v=origin,i=0,len=path.length;
1629
            for (;i<len && v;++i) {
1630
                v = v[path[i]];
1631
            }
1632
            return v;
1633
        };
1634
1635
        // Parse the response
1636
        // Step 1. Pull the resultsList from oFullResponse (default assumes
1637
        // oFullResponse IS the resultsList)
1638
        path = buildPath(schema.resultsList);
1639
        if (path) {
1640
            resultsList = walkPath(path, oFullResponse);
1641
            if (resultsList === undefined) {
1642
                bError = true;
1643
            }
1644
        } else {
1645
            bError = true;
1646
        }
1647
        
1648
        if (!resultsList) {
1649
            resultsList = [];
1650
        }
1651
1652
        if (!lang.isArray(resultsList)) {
1653
            resultsList = [resultsList];
1654
        }
1655
1656
        if (!bError) {
1657
            // Step 2. Parse out field data if identified
1658
            if(schema.fields) {
1659
                var field;
1660
                // Build the field parser map and location paths
1661
                for (i=0, len=fields.length; i<len; i++) {
1662
                    field = fields[i];
1663
                    key    = field.key || field;
1664
                    parser = ((typeof field.parser === 'function') ?
1665
                        field.parser :
1666
                        DS.Parser[field.parser+'']) || field.converter;
1667
                    path   = buildPath(key);
1668
    
1669
                    if (parser) {
1670
                        fieldParsers[fieldParsers.length] = {key:key,parser:parser};
1671
                    }
1672
    
1673
                    if (path) {
1674
                        if (path.length > 1) {
1675
                            fieldPaths[fieldPaths.length] = {key:key,path:path};
1676
                        } else {
1677
                            simpleFields[simpleFields.length] = {key:key,path:path[0]};
1678
                        }
1679
                    } else {
1680
                        YAHOO.log("Invalid key syntax: " + key,"warn",this.toString());
1681
                    }
1682
                }
1683
1684
                // Process the results, flattening the records and/or applying parsers if needed
1685
                for (i = resultsList.length - 1; i >= 0; --i) {
1686
                    var r = resultsList[i], rec = {};
1687
                    if(r) {
1688
                        for (j = simpleFields.length - 1; j >= 0; --j) {
1689
                            // Bug 1777850: data might be held in an array
1690
                            rec[simpleFields[j].key] =
1691
                                    (r[simpleFields[j].path] !== undefined) ?
1692
                                    r[simpleFields[j].path] : r[j];
1693
                        }
1694
1695
                        for (j = fieldPaths.length - 1; j >= 0; --j) {
1696
                            rec[fieldPaths[j].key] = walkPath(fieldPaths[j].path,r);
1697
                        }
1698
1699
                        for (j = fieldParsers.length - 1; j >= 0; --j) {
1700
                            var p = fieldParsers[j].key;
1701
                            rec[p] = fieldParsers[j].parser(rec[p]);
1702
                            if (rec[p] === undefined) {
1703
                                rec[p] = null;
1704
                            }
1705
                        }
1706
                    }
1707
                    results[i] = rec;
1708
                }
1709
            }
1710
            else {
1711
                results = resultsList;
1712
            }
1713
1714
            for (key in metaFields) {
1715
                if (lang.hasOwnProperty(metaFields,key)) {
1716
                    path = buildPath(metaFields[key]);
1717
                    if (path) {
1718
                        v = walkPath(path, oFullResponse);
1719
                        oParsedResponse.meta[key] = v;
1720
                    }
1721
                }
1722
            }
1723
1724
        } else {
1725
            YAHOO.log("JSON data could not be parsed due to invalid responseSchema.resultsList or invalid response: " +
1726
                    lang.dump(oFullResponse), "error", this.toString());
1727
1728
            oParsedResponse.error = true;
1729
        }
1730
1731
        oParsedResponse.results = results;
1732
    }
1733
    else {
1734
        YAHOO.log("JSON data could not be parsed: " +
1735
                lang.dump(oFullResponse), "error", this.toString());
1736
        oParsedResponse.error = true;
1737
    }
1738
1739
    return oParsedResponse;
1740
},
1741
1742
/**
1743
 * Overridable method parses an HTML TABLE element reference into a response object.
1744
 * Data is parsed out of TR elements from all TBODY elements. 
1745
 *
1746
 * @method parseHTMLTableData
1747
 * @param oRequest {Object} Request object.
1748
 * @param oFullResponse {Object} The full HTML element reference from the live database.
1749
 * @return {Object} Parsed response object with the following properties<br>
1750
 *     - results (Array) Array of parsed data results<br>
1751
 *     - error (Boolean) True if there was an error
1752
 */
1753
parseHTMLTableData : function(oRequest, oFullResponse) {
1754
    var bError = false;
1755
    var elTable = oFullResponse;
1756
    var fields = this.responseSchema.fields;
1757
    var oParsedResponse = {results:[]};
1758
1759
    if(lang.isArray(fields)) {
1760
        // Iterate through each TBODY
1761
        for(var i=0; i<elTable.tBodies.length; i++) {
1762
            var elTbody = elTable.tBodies[i];
1763
    
1764
            // Iterate through each TR
1765
            for(var j=elTbody.rows.length-1; j>-1; j--) {
1766
                var elRow = elTbody.rows[j];
1767
                var oResult = {};
1768
                
1769
                for(var k=fields.length-1; k>-1; k--) {
1770
                    var field = fields[k];
1771
                    var key = (lang.isValue(field.key)) ? field.key : field;
1772
                    var data = elRow.cells[k].innerHTML;
1773
    
1774
                    // Backward compatibility
1775
                    if(!field.parser && field.converter) {
1776
                        field.parser = field.converter;
1777
                        YAHOO.log("The field property converter has been deprecated" +
1778
                                " in favor of parser", "warn", this.toString());
1779
                    }
1780
                    var parser = (typeof field.parser === 'function') ?
1781
                        field.parser :
1782
                        DS.Parser[field.parser+''];
1783
                    if(parser) {
1784
                        data = parser.call(this, data);
1785
                    }
1786
                    // Safety measure
1787
                    if(data === undefined) {
1788
                        data = null;
1789
                    }
1790
                    oResult[key] = data;
1791
                }
1792
                oParsedResponse.results[j] = oResult;
1793
            }
1794
        }
1795
    }
1796
    else {
1797
        bError = true;
1798
        YAHOO.log("Invalid responseSchema.fields", "error", this.toString());
1799
    }
1800
1801
    if(bError) {
1802
        YAHOO.log("HTML TABLE data could not be parsed: " +
1803
                lang.dump(oFullResponse), "error", this.toString());
1804
        oParsedResponse.error = true;
1805
    }
1806
    else {
1807
        YAHOO.log("Parsed HTML TABLE data is " +
1808
                lang.dump(oParsedResponse), "info", this.toString());
1809
    }
1810
    return oParsedResponse;
1811
}
1812
1813
};
1814
1815
// DataSourceBase uses EventProvider
1816
lang.augmentProto(DS, util.EventProvider);
1817
1818
1819
1820
/****************************************************************************/
1821
/****************************************************************************/
1822
/****************************************************************************/
1823
1824
/**
1825
 * LocalDataSource class for in-memory data structs including JavaScript arrays,
1826
 * JavaScript object literals (JSON), XML documents, and HTML tables.
1827
 *
1828
 * @namespace YAHOO.util
1829
 * @class YAHOO.util.LocalDataSource
1830
 * @extends YAHOO.util.DataSourceBase 
1831
 * @constructor
1832
 * @param oLiveData {HTMLElement}  Pointer to live data.
1833
 * @param oConfigs {object} (optional) Object literal of configuration values.
1834
 */
1835
util.LocalDataSource = function(oLiveData, oConfigs) {
1836
    this.dataType = DS.TYPE_LOCAL;
1837
    
1838
    if(oLiveData) {
1839
        if(YAHOO.lang.isArray(oLiveData)) { // array
1840
            this.responseType = DS.TYPE_JSARRAY;
1841
        }
1842
         // xml
1843
        else if(oLiveData.nodeType && oLiveData.nodeType == 9) {
1844
            this.responseType = DS.TYPE_XML;
1845
        }
1846
        else if(oLiveData.nodeName && (oLiveData.nodeName.toLowerCase() == "table")) { // table
1847
            this.responseType = DS.TYPE_HTMLTABLE;
1848
            oLiveData = oLiveData.cloneNode(true);
1849
        }    
1850
        else if(YAHOO.lang.isString(oLiveData)) { // text
1851
            this.responseType = DS.TYPE_TEXT;
1852
        }
1853
        else if(YAHOO.lang.isObject(oLiveData)) { // json
1854
            this.responseType = DS.TYPE_JSON;
1855
        }
1856
    }
1857
    else {
1858
        oLiveData = [];
1859
        this.responseType = DS.TYPE_JSARRAY;
1860
    }
1861
    
1862
    util.LocalDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
1863
};
1864
1865
// LocalDataSource extends DataSourceBase
1866
lang.extend(util.LocalDataSource, DS);
1867
1868
// Copy static members to LocalDataSource class
1869
lang.augmentObject(util.LocalDataSource, DS);
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
/****************************************************************************/
1884
/****************************************************************************/
1885
/****************************************************************************/
1886
1887
/**
1888
 * FunctionDataSource class for JavaScript functions.
1889
 *
1890
 * @namespace YAHOO.util
1891
 * @class YAHOO.util.FunctionDataSource
1892
 * @extends YAHOO.util.DataSourceBase  
1893
 * @constructor
1894
 * @param oLiveData {HTMLElement}  Pointer to live data.
1895
 * @param oConfigs {object} (optional) Object literal of configuration values.
1896
 */
1897
util.FunctionDataSource = function(oLiveData, oConfigs) {
1898
    this.dataType = DS.TYPE_JSFUNCTION;
1899
    oLiveData = oLiveData || function() {};
1900
    
1901
    util.FunctionDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
1902
};
1903
1904
// FunctionDataSource extends DataSourceBase
1905
lang.extend(util.FunctionDataSource, DS, {
1906
1907
/////////////////////////////////////////////////////////////////////////////
1908
//
1909
// FunctionDataSource public properties
1910
//
1911
/////////////////////////////////////////////////////////////////////////////
1912
1913
/**
1914
 * Context in which to execute the function. By default, is the DataSource
1915
 * instance itself. If set, the function will receive the DataSource instance
1916
 * as an additional argument. 
1917
 *
1918
 * @property scope
1919
 * @type Object
1920
 * @default null
1921
 */
1922
scope : null,
1923
1924
1925
/////////////////////////////////////////////////////////////////////////////
1926
//
1927
// FunctionDataSource public methods
1928
//
1929
/////////////////////////////////////////////////////////////////////////////
1930
1931
/**
1932
 * Overriding method passes query to a function. The returned response is then
1933
 * forwarded to the handleResponse function.
1934
 *
1935
 * @method makeConnection
1936
 * @param oRequest {Object} Request object.
1937
 * @param oCallback {Object} Callback object literal.
1938
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
1939
 * @return {Number} Transaction ID.
1940
 */
1941
makeConnection : function(oRequest, oCallback, oCaller) {
1942
    var tId = DS._nTransactionId++;
1943
    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
1944
1945
    // Pass the request in as a parameter and
1946
    // forward the return value to the handler
1947
    
1948
    
1949
    var oRawResponse = (this.scope) ? this.liveData.call(this.scope, oRequest, this) : this.liveData(oRequest);
1950
    
1951
    // Try to sniff data type if it has not been defined
1952
    if(this.responseType === DS.TYPE_UNKNOWN) {
1953
        if(YAHOO.lang.isArray(oRawResponse)) { // array
1954
            this.responseType = DS.TYPE_JSARRAY;
1955
        }
1956
         // xml
1957
        else if(oRawResponse && oRawResponse.nodeType && oRawResponse.nodeType == 9) {
1958
            this.responseType = DS.TYPE_XML;
1959
        }
1960
        else if(oRawResponse && oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
1961
            this.responseType = DS.TYPE_HTMLTABLE;
1962
        }    
1963
        else if(YAHOO.lang.isObject(oRawResponse)) { // json
1964
            this.responseType = DS.TYPE_JSON;
1965
        }
1966
        else if(YAHOO.lang.isString(oRawResponse)) { // text
1967
            this.responseType = DS.TYPE_TEXT;
1968
        }
1969
    }
1970
1971
    this.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
1972
    return tId;
1973
}
1974
1975
});
1976
1977
// Copy static members to FunctionDataSource class
1978
lang.augmentObject(util.FunctionDataSource, DS);
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
/****************************************************************************/
1993
/****************************************************************************/
1994
/****************************************************************************/
1995
1996
/**
1997
 * ScriptNodeDataSource class for accessing remote data via the YUI Get Utility. 
1998
 *
1999
 * @namespace YAHOO.util
2000
 * @class YAHOO.util.ScriptNodeDataSource
2001
 * @extends YAHOO.util.DataSourceBase  
2002
 * @constructor
2003
 * @param oLiveData {HTMLElement}  Pointer to live data.
2004
 * @param oConfigs {object} (optional) Object literal of configuration values.
2005
 */
2006
util.ScriptNodeDataSource = function(oLiveData, oConfigs) {
2007
    this.dataType = DS.TYPE_SCRIPTNODE;
2008
    oLiveData = oLiveData || "";
2009
    
2010
    util.ScriptNodeDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
2011
};
2012
2013
// ScriptNodeDataSource extends DataSourceBase
2014
lang.extend(util.ScriptNodeDataSource, DS, {
2015
2016
/////////////////////////////////////////////////////////////////////////////
2017
//
2018
// ScriptNodeDataSource public properties
2019
//
2020
/////////////////////////////////////////////////////////////////////////////
2021
2022
/**
2023
 * Alias to YUI Get Utility, to allow implementers to use a custom class.
2024
 *
2025
 * @property getUtility
2026
 * @type Object
2027
 * @default YAHOO.util.Get
2028
 */
2029
getUtility : util.Get,
2030
2031
/**
2032
 * Defines request/response management in the following manner:
2033
 * <dl>
2034
 *     <!--<dt>queueRequests</dt>
2035
 *     <dd>If a request is already in progress, wait until response is returned before sending the next request.</dd>
2036
 *     <dt>cancelStaleRequests</dt>
2037
 *     <dd>If a request is already in progress, cancel it before sending the next request.</dd>-->
2038
 *     <dt>ignoreStaleResponses</dt>
2039
 *     <dd>Send all requests, but handle only the response for the most recently sent request.</dd>
2040
 *     <dt>allowAll</dt>
2041
 *     <dd>Send all requests and handle all responses.</dd>
2042
 * </dl>
2043
 *
2044
 * @property asyncMode
2045
 * @type String
2046
 * @default "allowAll"
2047
 */
2048
asyncMode : "allowAll",
2049
2050
/**
2051
 * Callback string parameter name sent to the remote script. By default,
2052
 * requests are sent to
2053
 * &#60;URI&#62;?&#60;scriptCallbackParam&#62;=callbackFunction
2054
 *
2055
 * @property scriptCallbackParam
2056
 * @type String
2057
 * @default "callback"
2058
 */
2059
scriptCallbackParam : "callback",
2060
2061
2062
/////////////////////////////////////////////////////////////////////////////
2063
//
2064
// ScriptNodeDataSource public methods
2065
//
2066
/////////////////////////////////////////////////////////////////////////////
2067
2068
/**
2069
 * Creates a request callback that gets appended to the script URI. Implementers
2070
 * can customize this string to match their server's query syntax.
2071
 *
2072
 * @method generateRequestCallback
2073
 * @return {String} String fragment that gets appended to script URI that 
2074
 * specifies the callback function 
2075
 */
2076
generateRequestCallback : function(id) {
2077
    return "&" + this.scriptCallbackParam + "=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]" ;
2078
},
2079
2080
/**
2081
 * Overridable method gives implementers access to modify the URI before the dynamic
2082
 * script node gets inserted. Implementers should take care not to return an
2083
 * invalid URI.
2084
 *
2085
 * @method doBeforeGetScriptNode
2086
 * @param {String} URI to the script 
2087
 * @return {String} URI to the script
2088
 */
2089
doBeforeGetScriptNode : function(sUri) {
2090
    return sUri;
2091
},
2092
2093
/**
2094
 * Overriding method passes query to Get Utility. The returned
2095
 * response is then forwarded to the handleResponse function.
2096
 *
2097
 * @method makeConnection
2098
 * @param oRequest {Object} Request object.
2099
 * @param oCallback {Object} Callback object literal.
2100
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
2101
 * @return {Number} Transaction ID.
2102
 */
2103
makeConnection : function(oRequest, oCallback, oCaller) {
2104
    var tId = DS._nTransactionId++;
2105
    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
2106
    
2107
    // If there are no global pending requests, it is safe to purge global callback stack and global counter
2108
    if(util.ScriptNodeDataSource._nPending === 0) {
2109
        util.ScriptNodeDataSource.callbacks = [];
2110
        util.ScriptNodeDataSource._nId = 0;
2111
    }
2112
    
2113
    // ID for this request
2114
    var id = util.ScriptNodeDataSource._nId;
2115
    util.ScriptNodeDataSource._nId++;
2116
    
2117
    // Dynamically add handler function with a closure to the callback stack
2118
    var oSelf = this;
2119
    util.ScriptNodeDataSource.callbacks[id] = function(oRawResponse) {
2120
        if((oSelf.asyncMode !== "ignoreStaleResponses")||
2121
                (id === util.ScriptNodeDataSource.callbacks.length-1)) { // Must ignore stale responses
2122
                
2123
            // Try to sniff data type if it has not been defined
2124
            if(oSelf.responseType === DS.TYPE_UNKNOWN) {
2125
                if(YAHOO.lang.isArray(oRawResponse)) { // array
2126
                    oSelf.responseType = DS.TYPE_JSARRAY;
2127
                }
2128
                 // xml
2129
                else if(oRawResponse.nodeType && oRawResponse.nodeType == 9) {
2130
                    oSelf.responseType = DS.TYPE_XML;
2131
                }
2132
                else if(oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
2133
                    oSelf.responseType = DS.TYPE_HTMLTABLE;
2134
                }    
2135
                else if(YAHOO.lang.isObject(oRawResponse)) { // json
2136
                    oSelf.responseType = DS.TYPE_JSON;
2137
                }
2138
                else if(YAHOO.lang.isString(oRawResponse)) { // text
2139
                    oSelf.responseType = DS.TYPE_TEXT;
2140
                }
2141
            }
2142
2143
            oSelf.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
2144
        }
2145
        else {
2146
            YAHOO.log("DataSource ignored stale response for tId " + tId + "(" + oRequest + ")", "info", oSelf.toString());
2147
        }
2148
    
2149
        delete util.ScriptNodeDataSource.callbacks[id];
2150
    };
2151
    
2152
    // We are now creating a request
2153
    util.ScriptNodeDataSource._nPending++;
2154
    var sUri = this.liveData + oRequest + this.generateRequestCallback(id);
2155
    sUri = this.doBeforeGetScriptNode(sUri);
2156
    YAHOO.log("DataSource is querying URL " + sUri, "info", this.toString());
2157
    this.getUtility.script(sUri,
2158
            {autopurge: true,
2159
            onsuccess: util.ScriptNodeDataSource._bumpPendingDown,
2160
            onfail: util.ScriptNodeDataSource._bumpPendingDown});
2161
2162
    return tId;
2163
}
2164
2165
});
2166
2167
// Copy static members to ScriptNodeDataSource class
2168
lang.augmentObject(util.ScriptNodeDataSource, DS);
2169
2170
// Copy static members to ScriptNodeDataSource class
2171
lang.augmentObject(util.ScriptNodeDataSource,  {
2172
2173
/////////////////////////////////////////////////////////////////////////////
2174
//
2175
// ScriptNodeDataSource private static properties
2176
//
2177
/////////////////////////////////////////////////////////////////////////////
2178
2179
/**
2180
 * Unique ID to track requests.
2181
 *
2182
 * @property _nId
2183
 * @type Number
2184
 * @private
2185
 * @static
2186
 */
2187
_nId : 0,
2188
2189
/**
2190
 * Counter for pending requests. When this is 0, it is safe to purge callbacks
2191
 * array.
2192
 *
2193
 * @property _nPending
2194
 * @type Number
2195
 * @private
2196
 * @static
2197
 */
2198
_nPending : 0,
2199
2200
/**
2201
 * Global array of callback functions, one for each request sent.
2202
 *
2203
 * @property callbacks
2204
 * @type Function[]
2205
 * @static
2206
 */
2207
callbacks : []
2208
2209
});
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
/****************************************************************************/
2225
/****************************************************************************/
2226
/****************************************************************************/
2227
2228
/**
2229
 * XHRDataSource class for accessing remote data via the YUI Connection Manager
2230
 * Utility
2231
 *
2232
 * @namespace YAHOO.util
2233
 * @class YAHOO.util.XHRDataSource
2234
 * @extends YAHOO.util.DataSourceBase  
2235
 * @constructor
2236
 * @param oLiveData {HTMLElement}  Pointer to live data.
2237
 * @param oConfigs {object} (optional) Object literal of configuration values.
2238
 */
2239
util.XHRDataSource = function(oLiveData, oConfigs) {
2240
    this.dataType = DS.TYPE_XHR;
2241
    this.connMgr = this.connMgr || util.Connect;
2242
    oLiveData = oLiveData || "";
2243
    
2244
    util.XHRDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
2245
};
2246
2247
// XHRDataSource extends DataSourceBase
2248
lang.extend(util.XHRDataSource, DS, {
2249
2250
/////////////////////////////////////////////////////////////////////////////
2251
//
2252
// XHRDataSource public properties
2253
//
2254
/////////////////////////////////////////////////////////////////////////////
2255
2256
 /**
2257
 * Alias to YUI Connection Manager, to allow implementers to use a custom class.
2258
 *
2259
 * @property connMgr
2260
 * @type Object
2261
 * @default YAHOO.util.Connect
2262
 */
2263
connMgr: null,
2264
2265
 /**
2266
 * Defines request/response management in the following manner:
2267
 * <dl>
2268
 *     <dt>queueRequests</dt>
2269
 *     <dd>If a request is already in progress, wait until response is returned
2270
 *     before sending the next request.</dd>
2271
 *
2272
 *     <dt>cancelStaleRequests</dt>
2273
 *     <dd>If a request is already in progress, cancel it before sending the next
2274
 *     request.</dd>
2275
 *
2276
 *     <dt>ignoreStaleResponses</dt>
2277
 *     <dd>Send all requests, but handle only the response for the most recently
2278
 *     sent request.</dd>
2279
 *
2280
 *     <dt>allowAll</dt>
2281
 *     <dd>Send all requests and handle all responses.</dd>
2282
 *
2283
 * </dl>
2284
 *
2285
 * @property connXhrMode
2286
 * @type String
2287
 * @default "allowAll"
2288
 */
2289
connXhrMode: "allowAll",
2290
2291
 /**
2292
 * True if data is to be sent via POST. By default, data will be sent via GET.
2293
 *
2294
 * @property connMethodPost
2295
 * @type Boolean
2296
 * @default false
2297
 */
2298
connMethodPost: false,
2299
2300
 /**
2301
 * The connection timeout defines how many  milliseconds the XHR connection will
2302
 * wait for a server response. Any non-zero value will enable the Connection Manager's
2303
 * Auto-Abort feature.
2304
 *
2305
 * @property connTimeout
2306
 * @type Number
2307
 * @default 0
2308
 */
2309
connTimeout: 0,
2310
2311
/////////////////////////////////////////////////////////////////////////////
2312
//
2313
// XHRDataSource public methods
2314
//
2315
/////////////////////////////////////////////////////////////////////////////
2316
2317
/**
2318
 * Overriding method passes query to Connection Manager. The returned
2319
 * response is then forwarded to the handleResponse function.
2320
 *
2321
 * @method makeConnection
2322
 * @param oRequest {Object} Request object.
2323
 * @param oCallback {Object} Callback object literal.
2324
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
2325
 * @return {Number} Transaction ID.
2326
 */
2327
makeConnection : function(oRequest, oCallback, oCaller) {
2328
2329
    var oRawResponse = null;
2330
    var tId = DS._nTransactionId++;
2331
    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
2332
2333
    // Set up the callback object and
2334
    // pass the request in as a URL query and
2335
    // forward the response to the handler
2336
    var oSelf = this;
2337
    var oConnMgr = this.connMgr;
2338
    var oQueue = this._oQueue;
2339
2340
    /**
2341
     * Define Connection Manager success handler
2342
     *
2343
     * @method _xhrSuccess
2344
     * @param oResponse {Object} HTTPXMLRequest object
2345
     * @private
2346
     */
2347
    var _xhrSuccess = function(oResponse) {
2348
        // If response ID does not match last made request ID,
2349
        // silently fail and wait for the next response
2350
        if(oResponse && (this.connXhrMode == "ignoreStaleResponses") &&
2351
                (oResponse.tId != oQueue.conn.tId)) {
2352
            YAHOO.log("Ignored stale response", "warn", this.toString());
2353
            return null;
2354
        }
2355
        // Error if no response
2356
        else if(!oResponse) {
2357
            this.fireEvent("dataErrorEvent", {request:oRequest, response:null,
2358
                    callback:oCallback, caller:oCaller,
2359
                    message:DS.ERROR_DATANULL});
2360
            YAHOO.log(DS.ERROR_DATANULL, "error", this.toString());
2361
2362
            // Send error response back to the caller with the error flag on
2363
            DS.issueCallback(oCallback,[oRequest, {error:true}], true, oCaller);
2364
2365
            return null;
2366
        }
2367
        // Forward to handler
2368
        else {
2369
            // Try to sniff data type if it has not been defined
2370
            if(this.responseType === DS.TYPE_UNKNOWN) {
2371
                var ctype = (oResponse.getResponseHeader) ? oResponse.getResponseHeader["Content-Type"] : null;
2372
                if(ctype) {
2373
                    // xml
2374
                    if(ctype.indexOf("text/xml") > -1) {
2375
                        this.responseType = DS.TYPE_XML;
2376
                    }
2377
                    else if(ctype.indexOf("application/json") > -1) { // json
2378
                        this.responseType = DS.TYPE_JSON;
2379
                    }
2380
                    else if(ctype.indexOf("text/plain") > -1) { // text
2381
                        this.responseType = DS.TYPE_TEXT;
2382
                    }
2383
                }
2384
            }
2385
            this.handleResponse(oRequest, oResponse, oCallback, oCaller, tId);
2386
        }
2387
    };
2388
2389
    /**
2390
     * Define Connection Manager failure handler
2391
     *
2392
     * @method _xhrFailure
2393
     * @param oResponse {Object} HTTPXMLRequest object
2394
     * @private
2395
     */
2396
    var _xhrFailure = function(oResponse) {
2397
        this.fireEvent("dataErrorEvent", {request:oRequest, response: oResponse,
2398
                callback:oCallback, caller:oCaller,
2399
                message:DS.ERROR_DATAINVALID});
2400
        YAHOO.log(DS.ERROR_DATAINVALID + ": " +
2401
                oResponse.statusText, "error", this.toString());
2402
2403
        // Backward compatibility
2404
        if(lang.isString(this.liveData) && lang.isString(oRequest) &&
2405
            (this.liveData.lastIndexOf("?") !== this.liveData.length-1) &&
2406
            (oRequest.indexOf("?") !== 0)){
2407
                YAHOO.log("DataSources using XHR no longer automatically supply " + 
2408
                "a \"?\" between the host and query parameters" +
2409
                " -- please check that the request URL is correct", "warn", this.toString());
2410
        }
2411
2412
        // Send failure response back to the caller with the error flag on
2413
        oResponse = oResponse || {};
2414
        oResponse.error = true;
2415
        DS.issueCallback(oCallback,[oRequest,oResponse],true, oCaller);
2416
2417
        return null;
2418
    };
2419
2420
    /**
2421
     * Define Connection Manager callback object
2422
     *
2423
     * @property _xhrCallback
2424
     * @param oResponse {Object} HTTPXMLRequest object
2425
     * @private
2426
     */
2427
     var _xhrCallback = {
2428
        success:_xhrSuccess,
2429
        failure:_xhrFailure,
2430
        scope: this
2431
    };
2432
2433
    // Apply Connection Manager timeout
2434
    if(lang.isNumber(this.connTimeout)) {
2435
        _xhrCallback.timeout = this.connTimeout;
2436
    }
2437
2438
    // Cancel stale requests
2439
    if(this.connXhrMode == "cancelStaleRequests") {
2440
            // Look in queue for stale requests
2441
            if(oQueue.conn) {
2442
                if(oConnMgr.abort) {
2443
                    oConnMgr.abort(oQueue.conn);
2444
                    oQueue.conn = null;
2445
                    YAHOO.log("Canceled stale request", "warn", this.toString());
2446
                }
2447
                else {
2448
                    YAHOO.log("Could not find Connection Manager abort() function", "error", this.toString());
2449
                }
2450
            }
2451
    }
2452
2453
    // Get ready to send the request URL
2454
    if(oConnMgr && oConnMgr.asyncRequest) {
2455
        var sLiveData = this.liveData;
2456
        var isPost = this.connMethodPost;
2457
        var sMethod = (isPost) ? "POST" : "GET";
2458
        // Validate request
2459
        var sUri = (isPost || !lang.isValue(oRequest)) ? sLiveData : sLiveData+oRequest;
2460
        var sRequest = (isPost) ? oRequest : null;
2461
2462
        // Send the request right away
2463
        if(this.connXhrMode != "queueRequests") {
2464
            oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, _xhrCallback, sRequest);
2465
        }
2466
        // Queue up then send the request
2467
        else {
2468
            // Found a request already in progress
2469
            if(oQueue.conn) {
2470
                var allRequests = oQueue.requests;
2471
                // Add request to queue
2472
                allRequests.push({request:oRequest, callback:_xhrCallback});
2473
2474
                // Interval needs to be started
2475
                if(!oQueue.interval) {
2476
                    oQueue.interval = setInterval(function() {
2477
                        // Connection is in progress
2478
                        if(oConnMgr.isCallInProgress(oQueue.conn)) {
2479
                            return;
2480
                        }
2481
                        else {
2482
                            // Send next request
2483
                            if(allRequests.length > 0) {
2484
                                // Validate request
2485
                                sUri = (isPost || !lang.isValue(allRequests[0].request)) ? sLiveData : sLiveData+allRequests[0].request;
2486
                                sRequest = (isPost) ? allRequests[0].request : null;
2487
                                oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, allRequests[0].callback, sRequest);
2488
2489
                                // Remove request from queue
2490
                                allRequests.shift();
2491
                            }
2492
                            // No more requests
2493
                            else {
2494
                                clearInterval(oQueue.interval);
2495
                                oQueue.interval = null;
2496
                            }
2497
                        }
2498
                    }, 50);
2499
                }
2500
            }
2501
            // Nothing is in progress
2502
            else {
2503
                oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, _xhrCallback, sRequest);
2504
            }
2505
        }
2506
    }
2507
    else {
2508
        YAHOO.log("Could not find Connection Manager asyncRequest() function", "error", this.toString());
2509
        // Send null response back to the caller with the error flag on
2510
        DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);
2511
    }
2512
2513
    return tId;
2514
}
2515
2516
});
2517
2518
// Copy static members to XHRDataSource class
2519
lang.augmentObject(util.XHRDataSource, DS);
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
/****************************************************************************/
2534
/****************************************************************************/
2535
/****************************************************************************/
2536
2537
/**
2538
 * Factory class for creating a BaseDataSource subclass instance. The sublcass is
2539
 * determined by oLiveData's type, unless the dataType config is explicitly passed in.  
2540
 *
2541
 * @namespace YAHOO.util
2542
 * @class YAHOO.util.DataSource
2543
 * @constructor
2544
 * @param oLiveData {HTMLElement}  Pointer to live data.
2545
 * @param oConfigs {object} (optional) Object literal of configuration values.
2546
 */
2547
util.DataSource = function(oLiveData, oConfigs) {
2548
    oConfigs = oConfigs || {};
2549
    
2550
    // Point to one of the subclasses, first by dataType if given, then by sniffing oLiveData type.
2551
    var dataType = oConfigs.dataType;
2552
    if(dataType) {
2553
        if(dataType == DS.TYPE_LOCAL) {
2554
            lang.augmentObject(util.DataSource, util.LocalDataSource);
2555
            return new util.LocalDataSource(oLiveData, oConfigs);            
2556
        }
2557
        else if(dataType == DS.TYPE_XHR) {
2558
            lang.augmentObject(util.DataSource, util.XHRDataSource);
2559
            return new util.XHRDataSource(oLiveData, oConfigs);            
2560
        }
2561
        else if(dataType == DS.TYPE_SCRIPTNODE) {
2562
            lang.augmentObject(util.DataSource, util.ScriptNodeDataSource);
2563
            return new util.ScriptNodeDataSource(oLiveData, oConfigs);            
2564
        }
2565
        else if(dataType == DS.TYPE_JSFUNCTION) {
2566
            lang.augmentObject(util.DataSource, util.FunctionDataSource);
2567
            return new util.FunctionDataSource(oLiveData, oConfigs);            
2568
        }
2569
    }
2570
    
2571
    if(YAHOO.lang.isString(oLiveData)) { // strings default to xhr
2572
        lang.augmentObject(util.DataSource, util.XHRDataSource);
2573
        return new util.XHRDataSource(oLiveData, oConfigs);
2574
    }
2575
    else if(YAHOO.lang.isFunction(oLiveData)) {
2576
        lang.augmentObject(util.DataSource, util.FunctionDataSource);
2577
        return new util.FunctionDataSource(oLiveData, oConfigs);
2578
    }
2579
    else { // ultimate default is local
2580
        lang.augmentObject(util.DataSource, util.LocalDataSource);
2581
        return new util.LocalDataSource(oLiveData, oConfigs);
2582
    }
2583
};
2584
2585
// Copy static members to DataSource class
2586
lang.augmentObject(util.DataSource, DS);
2587
2588
})();
2589
2590
/****************************************************************************/
2591
/****************************************************************************/
2592
/****************************************************************************/
2593
2594
/**
2595
 * The static Number class provides helper functions to deal with data of type
2596
 * Number.
2597
 *
2598
 * @namespace YAHOO.util
2599
 * @requires yahoo
2600
 * @class Number
2601
 * @static
2602
 */
2603
 YAHOO.util.Number = {
2604
 
2605
     /**
2606
     * Takes a native JavaScript Number and formats to string for display to user.
2607
     *
2608
     * @method format
2609
     * @param nData {Number} Number.
2610
     * @param oConfig {Object} (Optional) Optional configuration values:
2611
     *  <dl>
2612
     *   <dt>prefix {String}</dd>
2613
     *   <dd>String prepended before each number, like a currency designator "$"</dd>
2614
     *   <dt>decimalPlaces {Number}</dd>
2615
     *   <dd>Number of decimal places to round.</dd>
2616
     *   <dt>decimalSeparator {String}</dd>
2617
     *   <dd>Decimal separator</dd>
2618
     *   <dt>thousandsSeparator {String}</dd>
2619
     *   <dd>Thousands separator</dd>
2620
     *   <dt>suffix {String}</dd>
2621
     *   <dd>String appended after each number, like " items" (note the space)</dd>
2622
     *   <dt>negativeFormat</dt>
2623
     *   <dd>String used as a guide for how to indicate negative numbers.  The first '#' character in the string will be replaced by the number.  Default '-#'.</dd>
2624
     *  </dl>
2625
     * @return {String} Formatted number for display. Note, the following values
2626
     * return as "": null, undefined, NaN, "".
2627
     */
2628
    format : function(n, cfg) {
2629
        if (!isFinite(+n)) {
2630
            return '';
2631
        }
2632
2633
        n   = !isFinite(+n) ? 0 : +n;
2634
        cfg = YAHOO.lang.merge(YAHOO.util.Number.format.defaults, (cfg || {}));
2635
2636
        var neg    = n < 0,        absN   = Math.abs(n),
2637
            places = cfg.decimalPlaces,
2638
            sep    = cfg.thousandsSeparator,
2639
            s, bits, i;
2640
2641
        if (places < 0) {
2642
            // Get rid of the decimal info
2643
            s = absN - (absN % 1) + '';
2644
            i = s.length + places;
2645
2646
            // avoid 123 vs decimalPlaces -4 (should return "0")
2647
            if (i > 0) {
2648
                    // leverage toFixed by making 123 => 0.123 for the rounding
2649
                    // operation, then add the appropriate number of zeros back on
2650
                s = Number('.' + s).toFixed(i).slice(2) +
2651
                    new Array(s.length - i + 1).join('0');
2652
            } else {
2653
                s = "0";
2654
            }
2655
        } else {        // There is a bug in IE's toFixed implementation:
2656
            // for n in {(-0.94, -0.5], [0.5, 0.94)} n.toFixed() returns 0
2657
            // instead of -1 and 1. Manually handle that case.
2658
            s = absN < 1 && absN >= 0.5 && !places ? '1' : absN.toFixed(places);
2659
        }
2660
2661
        if (absN > 1000) {
2662
            bits  = s.split(/\D/);
2663
            i  = bits[0].length % 3 || 3;
2664
2665
            bits[0] = bits[0].slice(0,i) +
2666
                      bits[0].slice(i).replace(/(\d{3})/g, sep + '$1');
2667
2668
            s = bits.join(cfg.decimalSeparator);
2669
        }
2670
2671
        s = cfg.prefix + s + cfg.suffix;
2672
2673
        return neg ? cfg.negativeFormat.replace(/#/,s) : s;
2674
    }
2675
};
2676
YAHOO.util.Number.format.defaults = {
2677
    decimalSeparator : '.',
2678
    decimalPlaces    : null,
2679
    thousandsSeparator : '',
2680
    prefix : '',
2681
    suffix : '',
2682
    negativeFormat : '-#'
2683
};
2684
2685
2686
/****************************************************************************/
2687
/****************************************************************************/
2688
/****************************************************************************/
2689
2690
(function () {
2691
2692
var xPad=function (x, pad, r)
2693
{
2694
    if(typeof r === 'undefined')
2695
    {
2696
        r=10;
2697
    }
2698
    for( ; parseInt(x, 10)<r && r>1; r/=10) {
2699
        x = pad.toString() + x;
2700
    }
2701
    return x.toString();
2702
};
2703
2704
2705
/**
2706
 * The static Date class provides helper functions to deal with data of type Date.
2707
 *
2708
 * @namespace YAHOO.util
2709
 * @requires yahoo
2710
 * @class Date
2711
 * @static
2712
 */
2713
 var Dt = {
2714
    formats: {
2715
        a: function (d, l) { return l.a[d.getDay()]; },
2716
        A: function (d, l) { return l.A[d.getDay()]; },
2717
        b: function (d, l) { return l.b[d.getMonth()]; },
2718
        B: function (d, l) { return l.B[d.getMonth()]; },
2719
        C: function (d) { return xPad(parseInt(d.getFullYear()/100, 10), 0); },
2720
        d: ['getDate', '0'],
2721
        e: ['getDate', ' '],
2722
        g: function (d) { return xPad(parseInt(Dt.formats.G(d)%100, 10), 0); },
2723
        G: function (d) {
2724
                var y = d.getFullYear();
2725
                var V = parseInt(Dt.formats.V(d), 10);
2726
                var W = parseInt(Dt.formats.W(d), 10);
2727
    
2728
                if(W > V) {
2729
                    y++;
2730
                } else if(W===0 && V>=52) {
2731
                    y--;
2732
                }
2733
    
2734
                return y;
2735
            },
2736
        H: ['getHours', '0'],
2737
        I: function (d) { var I=d.getHours()%12; return xPad(I===0?12:I, 0); },
2738
        j: function (d) {
2739
                var gmd_1 = new Date('' + d.getFullYear() + '/1/1 GMT');
2740
                var gmdate = new Date('' + d.getFullYear() + '/' + (d.getMonth()+1) + '/' + d.getDate() + ' GMT');
2741
                var ms = gmdate - gmd_1;
2742
                var doy = parseInt(ms/60000/60/24, 10)+1;
2743
                return xPad(doy, 0, 100);
2744
            },
2745
        k: ['getHours', ' '],
2746
        l: function (d) { var I=d.getHours()%12; return xPad(I===0?12:I, ' '); },
2747
        m: function (d) { return xPad(d.getMonth()+1, 0); },
2748
        M: ['getMinutes', '0'],
2749
        p: function (d, l) { return l.p[d.getHours() >= 12 ? 1 : 0 ]; },
2750
        P: function (d, l) { return l.P[d.getHours() >= 12 ? 1 : 0 ]; },
2751
        s: function (d, l) { return parseInt(d.getTime()/1000, 10); },
2752
        S: ['getSeconds', '0'],
2753
        u: function (d) { var dow = d.getDay(); return dow===0?7:dow; },
2754
        U: function (d) {
2755
                var doy = parseInt(Dt.formats.j(d), 10);
2756
                var rdow = 6-d.getDay();
2757
                var woy = parseInt((doy+rdow)/7, 10);
2758
                return xPad(woy, 0);
2759
            },
2760
        V: function (d) {
2761
                var woy = parseInt(Dt.formats.W(d), 10);
2762
                var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
2763
                // First week is 01 and not 00 as in the case of %U and %W,
2764
                // so we add 1 to the final result except if day 1 of the year
2765
                // is a Monday (then %W returns 01).
2766
                // We also need to subtract 1 if the day 1 of the year is 
2767
                // Friday-Sunday, so the resulting equation becomes:
2768
                var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
2769
                if(idow === 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
2770
                {
2771
                    idow = 1;
2772
                }
2773
                else if(idow === 0)
2774
                {
2775
                    idow = Dt.formats.V(new Date('' + (d.getFullYear()-1) + '/12/31'));
2776
                }
2777
    
2778
                return xPad(idow, 0);
2779
            },
2780
        w: 'getDay',
2781
        W: function (d) {
2782
                var doy = parseInt(Dt.formats.j(d), 10);
2783
                var rdow = 7-Dt.formats.u(d);
2784
                var woy = parseInt((doy+rdow)/7, 10);
2785
                return xPad(woy, 0, 10);
2786
            },
2787
        y: function (d) { return xPad(d.getFullYear()%100, 0); },
2788
        Y: 'getFullYear',
2789
        z: function (d) {
2790
                var o = d.getTimezoneOffset();
2791
                var H = xPad(parseInt(Math.abs(o/60), 10), 0);
2792
                var M = xPad(Math.abs(o%60), 0);
2793
                return (o>0?'-':'+') + H + M;
2794
            },
2795
        Z: function (d) {
2796
		var tz = d.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/, '$2').replace(/[a-z ]/g, '');
2797
		if(tz.length > 4) {
2798
			tz = Dt.formats.z(d);
2799
		}
2800
		return tz;
2801
	},
2802
        '%': function (d) { return '%'; }
2803
    },
2804
2805
    aggregates: {
2806
        c: 'locale',
2807
        D: '%m/%d/%y',
2808
        F: '%Y-%m-%d',
2809
        h: '%b',
2810
        n: '\n',
2811
        r: 'locale',
2812
        R: '%H:%M',
2813
        t: '\t',
2814
        T: '%H:%M:%S',
2815
        x: 'locale',
2816
        X: 'locale'
2817
        //'+': '%a %b %e %T %Z %Y'
2818
    },
2819
2820
     /**
2821
     * Takes a native JavaScript Date and formats to string for display to user.
2822
     *
2823
     * @method format
2824
     * @param oDate {Date} Date.
2825
     * @param oConfig {Object} (Optional) Object literal of configuration values:
2826
     *  <dl>
2827
     *   <dt>format &lt;String&gt;</dt>
2828
     *   <dd>
2829
     *   <p>
2830
     *   Any strftime string is supported, such as "%I:%M:%S %p". strftime has several format specifiers defined by the Open group at 
2831
     *   <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html">http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html</a>
2832
     *   </p>
2833
     *   <p>   
2834
     *   PHP added a few of its own, defined at <a href="http://www.php.net/strftime">http://www.php.net/strftime</a>
2835
     *   </p>
2836
     *   <p>
2837
     *   This javascript implementation supports all the PHP specifiers and a few more.  The full list is below:
2838
     *   </p>
2839
     *   <dl>
2840
     *    <dt>%a</dt> <dd>abbreviated weekday name according to the current locale</dd>
2841
     *    <dt>%A</dt> <dd>full weekday name according to the current locale</dd>
2842
     *    <dt>%b</dt> <dd>abbreviated month name according to the current locale</dd>
2843
     *    <dt>%B</dt> <dd>full month name according to the current locale</dd>
2844
     *    <dt>%c</dt> <dd>preferred date and time representation for the current locale</dd>
2845
     *    <dt>%C</dt> <dd>century number (the year divided by 100 and truncated to an integer, range 00 to 99)</dd>
2846
     *    <dt>%d</dt> <dd>day of the month as a decimal number (range 01 to 31)</dd>
2847
     *    <dt>%D</dt> <dd>same as %m/%d/%y</dd>
2848
     *    <dt>%e</dt> <dd>day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31')</dd>
2849
     *    <dt>%F</dt> <dd>same as %Y-%m-%d (ISO 8601 date format)</dd>
2850
     *    <dt>%g</dt> <dd>like %G, but without the century</dd>
2851
     *    <dt>%G</dt> <dd>The 4-digit year corresponding to the ISO week number</dd>
2852
     *    <dt>%h</dt> <dd>same as %b</dd>
2853
     *    <dt>%H</dt> <dd>hour as a decimal number using a 24-hour clock (range 00 to 23)</dd>
2854
     *    <dt>%I</dt> <dd>hour as a decimal number using a 12-hour clock (range 01 to 12)</dd>
2855
     *    <dt>%j</dt> <dd>day of the year as a decimal number (range 001 to 366)</dd>
2856
     *    <dt>%k</dt> <dd>hour as a decimal number using a 24-hour clock (range 0 to 23); single digits are preceded by a blank. (See also %H.)</dd>
2857
     *    <dt>%l</dt> <dd>hour as a decimal number using a 12-hour clock (range 1 to 12); single digits are preceded by a blank. (See also %I.) </dd>
2858
     *    <dt>%m</dt> <dd>month as a decimal number (range 01 to 12)</dd>
2859
     *    <dt>%M</dt> <dd>minute as a decimal number</dd>
2860
     *    <dt>%n</dt> <dd>newline character</dd>
2861
     *    <dt>%p</dt> <dd>either `AM' or `PM' according to the given time value, or the corresponding strings for the current locale</dd>
2862
     *    <dt>%P</dt> <dd>like %p, but lower case</dd>
2863
     *    <dt>%r</dt> <dd>time in a.m. and p.m. notation equal to %I:%M:%S %p</dd>
2864
     *    <dt>%R</dt> <dd>time in 24 hour notation equal to %H:%M</dd>
2865
     *    <dt>%s</dt> <dd>number of seconds since the Epoch, ie, since 1970-01-01 00:00:00 UTC</dd>
2866
     *    <dt>%S</dt> <dd>second as a decimal number</dd>
2867
     *    <dt>%t</dt> <dd>tab character</dd>
2868
     *    <dt>%T</dt> <dd>current time, equal to %H:%M:%S</dd>
2869
     *    <dt>%u</dt> <dd>weekday as a decimal number [1,7], with 1 representing Monday</dd>
2870
     *    <dt>%U</dt> <dd>week number of the current year as a decimal number, starting with the
2871
     *            first Sunday as the first day of the first week</dd>
2872
     *    <dt>%V</dt> <dd>The ISO 8601:1988 week number of the current year as a decimal number,
2873
     *            range 01 to 53, where week 1 is the first week that has at least 4 days
2874
     *            in the current year, and with Monday as the first day of the week.</dd>
2875
     *    <dt>%w</dt> <dd>day of the week as a decimal, Sunday being 0</dd>
2876
     *    <dt>%W</dt> <dd>week number of the current year as a decimal number, starting with the
2877
     *            first Monday as the first day of the first week</dd>
2878
     *    <dt>%x</dt> <dd>preferred date representation for the current locale without the time</dd>
2879
     *    <dt>%X</dt> <dd>preferred time representation for the current locale without the date</dd>
2880
     *    <dt>%y</dt> <dd>year as a decimal number without a century (range 00 to 99)</dd>
2881
     *    <dt>%Y</dt> <dd>year as a decimal number including the century</dd>
2882
     *    <dt>%z</dt> <dd>numerical time zone representation</dd>
2883
     *    <dt>%Z</dt> <dd>time zone name or abbreviation</dd>
2884
     *    <dt>%%</dt> <dd>a literal `%' character</dd>
2885
     *   </dl>
2886
     *  </dd>
2887
     * </dl>
2888
     * @param sLocale {String} (Optional) The locale to use when displaying days of week,
2889
     *  months of the year, and other locale specific strings.  The following locales are
2890
     *  built in:
2891
     *  <dl>
2892
     *   <dt>en</dt>
2893
     *   <dd>English</dd>
2894
     *   <dt>en-US</dt>
2895
     *   <dd>US English</dd>
2896
     *   <dt>en-GB</dt>
2897
     *   <dd>British English</dd>
2898
     *   <dt>en-AU</dt>
2899
     *   <dd>Australian English (identical to British English)</dd>
2900
     *  </dl>
2901
     *  More locales may be added by subclassing of YAHOO.util.DateLocale.
2902
     *  See YAHOO.util.DateLocale for more information.
2903
     * @return {String} Formatted date for display.
2904
     * @sa YAHOO.util.DateLocale
2905
     */
2906
    format : function (oDate, oConfig, sLocale) {
2907
        oConfig = oConfig || {};
2908
        
2909
        if(!(oDate instanceof Date)) {
2910
            return YAHOO.lang.isValue(oDate) ? oDate : "";
2911
        }
2912
2913
        var format = oConfig.format || "%m/%d/%Y";
2914
2915
        // Be backwards compatible, support strings that are
2916
        // exactly equal to YYYY/MM/DD, DD/MM/YYYY and MM/DD/YYYY
2917
        if(format === 'YYYY/MM/DD') {
2918
            format = '%Y/%m/%d';
2919
        } else if(format === 'DD/MM/YYYY') {
2920
            format = '%d/%m/%Y';
2921
        } else if(format === 'MM/DD/YYYY') {
2922
            format = '%m/%d/%Y';
2923
        }
2924
        // end backwards compatibility block
2925
 
2926
        sLocale = sLocale || "en";
2927
2928
        // Make sure we have a definition for the requested locale, or default to en.
2929
        if(!(sLocale in YAHOO.util.DateLocale)) {
2930
            if(sLocale.replace(/-[a-zA-Z]+$/, '') in YAHOO.util.DateLocale) {
2931
                sLocale = sLocale.replace(/-[a-zA-Z]+$/, '');
2932
            } else {
2933
                sLocale = "en";
2934
            }
2935
        }
2936
2937
        var aLocale = YAHOO.util.DateLocale[sLocale];
2938
2939
        var replace_aggs = function (m0, m1) {
2940
            var f = Dt.aggregates[m1];
2941
            return (f === 'locale' ? aLocale[m1] : f);
2942
        };
2943
2944
        var replace_formats = function (m0, m1) {
2945
            var f = Dt.formats[m1];
2946
            if(typeof f === 'string') {             // string => built in date function
2947
                return oDate[f]();
2948
            } else if(typeof f === 'function') {    // function => our own function
2949
                return f.call(oDate, oDate, aLocale);
2950
            } else if(typeof f === 'object' && typeof f[0] === 'string') {  // built in function with padding
2951
                return xPad(oDate[f[0]](), f[1]);
2952
            } else {
2953
                return m1;
2954
            }
2955
        };
2956
2957
        // First replace aggregates (run in a loop because an agg may be made up of other aggs)
2958
        while(format.match(/%[cDFhnrRtTxX]/)) {
2959
            format = format.replace(/%([cDFhnrRtTxX])/g, replace_aggs);
2960
        }
2961
2962
        // Now replace formats (do not run in a loop otherwise %%a will be replace with the value of %a)
2963
        var str = format.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g, replace_formats);
2964
2965
        replace_aggs = replace_formats = undefined;
2966
2967
        return str;
2968
    }
2969
 };
2970
 
2971
 YAHOO.namespace("YAHOO.util");
2972
 YAHOO.util.Date = Dt;
2973
2974
/**
2975
 * The DateLocale class is a container and base class for all
2976
 * localised date strings used by YAHOO.util.Date. It is used
2977
 * internally, but may be extended to provide new date localisations.
2978
 *
2979
 * To create your own DateLocale, follow these steps:
2980
 * <ol>
2981
 *  <li>Find an existing locale that matches closely with your needs</li>
2982
 *  <li>Use this as your base class.  Use YAHOO.util.DateLocale if nothing
2983
 *   matches.</li>
2984
 *  <li>Create your own class as an extension of the base class using
2985
 *   YAHOO.lang.merge, and add your own localisations where needed.</li>
2986
 * </ol>
2987
 * See the YAHOO.util.DateLocale['en-US'] and YAHOO.util.DateLocale['en-GB']
2988
 * classes which extend YAHOO.util.DateLocale['en'].
2989
 *
2990
 * For example, to implement locales for French french and Canadian french,
2991
 * we would do the following:
2992
 * <ol>
2993
 *  <li>For French french, we have no existing similar locale, so use
2994
 *   YAHOO.util.DateLocale as the base, and extend it:
2995
 *   <pre>
2996
 *      YAHOO.util.DateLocale['fr'] = YAHOO.lang.merge(YAHOO.util.DateLocale, {
2997
 *          a: ['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam'],
2998
 *          A: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
2999
 *          b: ['jan', 'f&eacute;v', 'mar', 'avr', 'mai', 'jun', 'jui', 'ao&ucirc;', 'sep', 'oct', 'nov', 'd&eacute;c'],
3000
 *          B: ['janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre'],
3001
 *          c: '%a %d %b %Y %T %Z',
3002
 *          p: ['', ''],
3003
 *          P: ['', ''],
3004
 *          x: '%d.%m.%Y',
3005
 *          X: '%T'
3006
 *      });
3007
 *   </pre>
3008
 *  </li>
3009
 *  <li>For Canadian french, we start with French french and change the meaning of \%x:
3010
 *   <pre>
3011
 *      YAHOO.util.DateLocale['fr-CA'] = YAHOO.lang.merge(YAHOO.util.DateLocale['fr'], {
3012
 *          x: '%Y-%m-%d'
3013
 *      });
3014
 *   </pre>
3015
 *  </li>
3016
 * </ol>
3017
 *
3018
 * With that, you can use your new locales:
3019
 * <pre>
3020
 *    var d = new Date("2008/04/22");
3021
 *    YAHOO.util.Date.format(d, {format: "%A, %d %B == %x"}, "fr");
3022
 * </pre>
3023
 * will return:
3024
 * <pre>
3025
 *    mardi, 22 avril == 22.04.2008
3026
 * </pre>
3027
 * And
3028
 * <pre>
3029
 *    YAHOO.util.Date.format(d, {format: "%A, %d %B == %x"}, "fr-CA");
3030
 * </pre>
3031
 * Will return:
3032
 * <pre>
3033
 *   mardi, 22 avril == 2008-04-22
3034
 * </pre>
3035
 * @namespace YAHOO.util
3036
 * @requires yahoo
3037
 * @class DateLocale
3038
 */
3039
 YAHOO.util.DateLocale = {
3040
        a: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
3041
        A: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
3042
        b: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
3043
        B: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
3044
        c: '%a %d %b %Y %T %Z',
3045
        p: ['AM', 'PM'],
3046
        P: ['am', 'pm'],
3047
        r: '%I:%M:%S %p',
3048
        x: '%d/%m/%y',
3049
        X: '%T'
3050
 };
3051
3052
 YAHOO.util.DateLocale['en'] = YAHOO.lang.merge(YAHOO.util.DateLocale, {});
3053
3054
 YAHOO.util.DateLocale['en-US'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en'], {
3055
        c: '%a %d %b %Y %I:%M:%S %p %Z',
3056
        x: '%m/%d/%Y',
3057
        X: '%I:%M:%S %p'
3058
 });
3059
3060
 YAHOO.util.DateLocale['en-GB'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en'], {
3061
        r: '%l:%M:%S %P %Z'
3062
 });
3063
 YAHOO.util.DateLocale['en-AU'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en']);
3064
3065
})();
3066
3067
YAHOO.register("datasource", YAHOO.util.DataSource, {version: "2.8.0r4", build: "2449"});
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/datasource/datasource-min.js (-12 lines)
Lines 1-12 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
(function(){var lang=YAHOO.lang,util=YAHOO.util,Ev=util.Event;util.DataSourceBase=function(oLiveData,oConfigs){if(oLiveData===null||oLiveData===undefined){return;}this.liveData=oLiveData;this._oQueue={interval:null,conn:null,requests:[]};this.responseSchema={};if(oConfigs&&(oConfigs.constructor==Object)){for(var sConfig in oConfigs){if(sConfig){this[sConfig]=oConfigs[sConfig];}}}var maxCacheEntries=this.maxCacheEntries;if(!lang.isNumber(maxCacheEntries)||(maxCacheEntries<0)){maxCacheEntries=0;}this._aIntervals=[];this.createEvent("cacheRequestEvent");this.createEvent("cacheResponseEvent");this.createEvent("requestEvent");this.createEvent("responseEvent");this.createEvent("responseParseEvent");this.createEvent("responseCacheEvent");this.createEvent("dataErrorEvent");this.createEvent("cacheFlushEvent");var DS=util.DataSourceBase;this._sName="DataSource instance"+DS._nIndex;DS._nIndex++;};var DS=util.DataSourceBase;lang.augmentObject(DS,{TYPE_UNKNOWN:-1,TYPE_JSARRAY:0,TYPE_JSFUNCTION:1,TYPE_XHR:2,TYPE_JSON:3,TYPE_XML:4,TYPE_TEXT:5,TYPE_HTMLTABLE:6,TYPE_SCRIPTNODE:7,TYPE_LOCAL:8,ERROR_DATAINVALID:"Invalid data",ERROR_DATANULL:"Null data",_nIndex:0,_nTransactionId:0,_getLocationValue:function(field,context){var locator=field.locator||field.key||field,xmldoc=context.ownerDocument||context,result,res,value=null;try{if(!lang.isUndefined(xmldoc.evaluate)){result=xmldoc.evaluate(locator,context,xmldoc.createNSResolver(!context.ownerDocument?context.documentElement:context.ownerDocument.documentElement),0,null);while(res=result.iterateNext()){value=res.textContent;}}else{xmldoc.setProperty("SelectionLanguage","XPath");result=context.selectNodes(locator)[0];value=result.value||result.text||null;}return value;}catch(e){}},issueCallback:function(callback,params,error,scope){if(lang.isFunction(callback)){callback.apply(scope,params);}else{if(lang.isObject(callback)){scope=callback.scope||scope||window;var callbackFunc=callback.success;if(error){callbackFunc=callback.failure;}if(callbackFunc){callbackFunc.apply(scope,params.concat([callback.argument]));}}}},parseString:function(oData){if(!lang.isValue(oData)){return null;}var string=oData+"";if(lang.isString(string)){return string;}else{return null;}},parseNumber:function(oData){if(!lang.isValue(oData)||(oData==="")){return null;}var number=oData*1;if(lang.isNumber(number)){return number;}else{return null;}},convertNumber:function(oData){return DS.parseNumber(oData);},parseDate:function(oData){var date=null;if(!(oData instanceof Date)){date=new Date(oData);}else{return oData;}if(date instanceof Date){return date;}else{return null;}},convertDate:function(oData){return DS.parseDate(oData);}});DS.Parser={string:DS.parseString,number:DS.parseNumber,date:DS.parseDate};DS.prototype={_sName:null,_aCache:null,_oQueue:null,_aIntervals:null,maxCacheEntries:0,liveData:null,dataType:DS.TYPE_UNKNOWN,responseType:DS.TYPE_UNKNOWN,responseSchema:null,useXPath:false,toString:function(){return this._sName;},getCachedResponse:function(oRequest,oCallback,oCaller){var aCache=this._aCache;if(this.maxCacheEntries>0){if(!aCache){this._aCache=[];}else{var nCacheLength=aCache.length;if(nCacheLength>0){var oResponse=null;this.fireEvent("cacheRequestEvent",{request:oRequest,callback:oCallback,caller:oCaller});for(var i=nCacheLength-1;i>=0;i--){var oCacheElem=aCache[i];if(this.isCacheHit(oRequest,oCacheElem.request)){oResponse=oCacheElem.response;this.fireEvent("cacheResponseEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});if(i<nCacheLength-1){aCache.splice(i,1);this.addToCache(oRequest,oResponse);}oResponse.cached=true;break;}}return oResponse;}}}else{if(aCache){this._aCache=null;}}return null;},isCacheHit:function(oRequest,oCachedRequest){return(oRequest===oCachedRequest);},addToCache:function(oRequest,oResponse){var aCache=this._aCache;if(!aCache){return;}while(aCache.length>=this.maxCacheEntries){aCache.shift();}var oCacheElem={request:oRequest,response:oResponse};aCache[aCache.length]=oCacheElem;this.fireEvent("responseCacheEvent",{request:oRequest,response:oResponse});},flushCache:function(){if(this._aCache){this._aCache=[];this.fireEvent("cacheFlushEvent");}},setInterval:function(nMsec,oRequest,oCallback,oCaller){if(lang.isNumber(nMsec)&&(nMsec>=0)){var oSelf=this;var nId=setInterval(function(){oSelf.makeConnection(oRequest,oCallback,oCaller);},nMsec);this._aIntervals.push(nId);return nId;}else{}},clearInterval:function(nId){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){if(tracker[i]===nId){tracker.splice(i,1);clearInterval(nId);}}},clearAllIntervals:function(){var tracker=this._aIntervals||[];for(var i=tracker.length-1;i>-1;i--){clearInterval(tracker[i]);}tracker=[];},sendRequest:function(oRequest,oCallback,oCaller){var oCachedResponse=this.getCachedResponse(oRequest,oCallback,oCaller);if(oCachedResponse){DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);return null;}return this.makeConnection(oRequest,oCallback,oCaller);},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=this.liveData;this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;},handleResponse:function(oRequest,oRawResponse,oCallback,oCaller,tId){this.fireEvent("responseEvent",{tId:tId,request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller});var xhr=(this.dataType==DS.TYPE_XHR)?true:false;var oParsedResponse=null;var oFullResponse=oRawResponse;if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oRawResponse&&oRawResponse.getResponseHeader)?oRawResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}else{if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;
8
}else{if(oRawResponse&&oRawResponse.nodeType&&(oRawResponse.nodeType===9||oRawResponse.nodeType===1||oRawResponse.nodeType===11)){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}}switch(this.responseType){case DS.TYPE_JSARRAY:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var arrayEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,arrayEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e1){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseArrayData(oRequest,oFullResponse);break;case DS.TYPE_JSON:if(xhr&&oRawResponse&&oRawResponse.responseText){oFullResponse=oRawResponse.responseText;}try{if(lang.isString(oFullResponse)){var parseArgs=[oFullResponse].concat(this.parseJSONArgs);if(lang.JSON){oFullResponse=lang.JSON.parse.apply(lang.JSON,parseArgs);}else{if(window.JSON&&JSON.parse){oFullResponse=JSON.parse.apply(JSON,parseArgs);}else{if(oFullResponse.parseJSON){oFullResponse=oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));}else{while(oFullResponse.length>0&&(oFullResponse.charAt(0)!="{")&&(oFullResponse.charAt(0)!="[")){oFullResponse=oFullResponse.substring(1,oFullResponse.length);}if(oFullResponse.length>0){var objEnd=Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));oFullResponse=oFullResponse.substring(0,objEnd+1);oFullResponse=eval("("+oFullResponse+")");}}}}}}catch(e){}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseJSONData(oRequest,oFullResponse);break;case DS.TYPE_HTMLTABLE:if(xhr&&oRawResponse.responseText){var el=document.createElement("div");el.innerHTML=oRawResponse.responseText;oFullResponse=el.getElementsByTagName("table")[0];}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseHTMLTableData(oRequest,oFullResponse);break;case DS.TYPE_XML:if(xhr&&oRawResponse.responseXML){oFullResponse=oRawResponse.responseXML;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseXMLData(oRequest,oFullResponse);break;case DS.TYPE_TEXT:if(xhr&&lang.isString(oRawResponse.responseText)){oFullResponse=oRawResponse.responseText;}oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseTextData(oRequest,oFullResponse);break;default:oFullResponse=this.doBeforeParseData(oRequest,oFullResponse,oCallback);oParsedResponse=this.parseData(oRequest,oFullResponse);break;}oParsedResponse=oParsedResponse||{};if(!oParsedResponse.results){oParsedResponse.results=[];}if(!oParsedResponse.meta){oParsedResponse.meta={};}if(!oParsedResponse.error){oParsedResponse=this.doBeforeCallback(oRequest,oFullResponse,oParsedResponse,oCallback);this.fireEvent("responseParseEvent",{request:oRequest,response:oParsedResponse,callback:oCallback,caller:oCaller});this.addToCache(oRequest,oParsedResponse);}else{oParsedResponse.error=true;this.fireEvent("dataErrorEvent",{request:oRequest,response:oRawResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});}oParsedResponse.tId=tId;DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);},doBeforeParseData:function(oRequest,oFullResponse,oCallback){return oFullResponse;},doBeforeCallback:function(oRequest,oFullResponse,oParsedResponse,oCallback){return oParsedResponse;},parseData:function(oRequest,oFullResponse){if(lang.isValue(oFullResponse)){var oParsedResponse={results:oFullResponse,meta:{}};return oParsedResponse;}return null;},parseArrayData:function(oRequest,oFullResponse){if(lang.isArray(oFullResponse)){var results=[],i,j,rec,field,data;if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(i=fields.length-1;i>=0;--i){if(typeof fields[i]!=="object"){fields[i]={key:fields[i]};}}var parsers={},p;for(i=fields.length-1;i>=0;--i){p=(typeof fields[i].parser==="function"?fields[i].parser:DS.Parser[fields[i].parser+""])||fields[i].converter;if(p){parsers[fields[i].key]=p;}}var arrType=lang.isArray(oFullResponse[0]);for(i=oFullResponse.length-1;i>-1;i--){var oResult={};rec=oFullResponse[i];if(typeof rec==="object"){for(j=fields.length-1;j>-1;j--){field=fields[j];data=arrType?rec[j]:rec[field.key];if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}else{if(lang.isString(rec)){for(j=fields.length-1;j>-1;j--){field=fields[j];data=rec;if(parsers[field.key]){data=parsers[field.key].call(this,data);}if(data===undefined){data=null;}oResult[field.key]=data;}}}results[i]=oResult;}}else{results=oFullResponse;}var oParsedResponse={results:results};return oParsedResponse;}return null;},parseTextData:function(oRequest,oFullResponse){if(lang.isString(oFullResponse)){if(lang.isString(this.responseSchema.recordDelim)&&lang.isString(this.responseSchema.fieldDelim)){var oParsedResponse={results:[]};var recDelim=this.responseSchema.recordDelim;var fieldDelim=this.responseSchema.fieldDelim;if(oFullResponse.length>0){var newLength=oFullResponse.length-recDelim.length;if(oFullResponse.substr(newLength)==recDelim){oFullResponse=oFullResponse.substr(0,newLength);
9
}if(oFullResponse.length>0){var recordsarray=oFullResponse.split(recDelim);for(var i=0,len=recordsarray.length,recIdx=0;i<len;++i){var bError=false,sRecord=recordsarray[i];if(lang.isString(sRecord)&&(sRecord.length>0)){var fielddataarray=recordsarray[i].split(fieldDelim);var oResult={};if(lang.isArray(this.responseSchema.fields)){var fields=this.responseSchema.fields;for(var j=fields.length-1;j>-1;j--){try{var data=fielddataarray[j];if(lang.isString(data)){if(data.charAt(0)=='"'){data=data.substr(1);}if(data.charAt(data.length-1)=='"'){data=data.substr(0,data.length-1);}var field=fields[j];var key=(lang.isValue(field.key))?field.key:field;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}else{bError=true;}}catch(e){bError=true;}}}else{oResult=fielddataarray;}if(!bError){oParsedResponse.results[recIdx++]=oResult;}}}}}return oParsedResponse;}}return null;},parseXMLResult:function(result){var oResult={},schema=this.responseSchema;try{for(var m=schema.fields.length-1;m>=0;m--){var field=schema.fields[m];var key=(lang.isValue(field.key))?field.key:field;var data=null;if(this.useXPath){data=YAHOO.util.DataSource._getLocationValue(field,result);}else{var xmlAttr=result.attributes.getNamedItem(key);if(xmlAttr){data=xmlAttr.value;}else{var xmlNode=result.getElementsByTagName(key);if(xmlNode&&xmlNode.item(0)){var item=xmlNode.item(0);data=(item)?((item.text)?item.text:(item.textContent)?item.textContent:null):null;if(!data){var datapieces=[];for(var j=0,len=item.childNodes.length;j<len;j++){if(item.childNodes[j].nodeValue){datapieces[datapieces.length]=item.childNodes[j].nodeValue;}}if(datapieces.length>0){data=datapieces.join("");}}}}}if(data===null){data="";}if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}}catch(e){}return oResult;},parseXMLData:function(oRequest,oFullResponse){var bError=false,schema=this.responseSchema,oParsedResponse={meta:{}},xmlList=null,metaNode=schema.metaNode,metaLocators=schema.metaFields||{},i,k,loc,v;try{if(this.useXPath){for(k in metaLocators){oParsedResponse.meta[k]=YAHOO.util.DataSource._getLocationValue(metaLocators[k],oFullResponse);}}else{metaNode=metaNode?oFullResponse.getElementsByTagName(metaNode)[0]:oFullResponse;if(metaNode){for(k in metaLocators){if(lang.hasOwnProperty(metaLocators,k)){loc=metaLocators[k];v=metaNode.getElementsByTagName(loc)[0];if(v){v=v.firstChild.nodeValue;}else{v=metaNode.attributes.getNamedItem(loc);if(v){v=v.value;}}if(lang.isValue(v)){oParsedResponse.meta[k]=v;}}}}}xmlList=(schema.resultNode)?oFullResponse.getElementsByTagName(schema.resultNode):null;}catch(e){}if(!xmlList||!lang.isArray(schema.fields)){bError=true;}else{oParsedResponse.results=[];for(i=xmlList.length-1;i>=0;--i){var oResult=this.parseXMLResult(xmlList.item(i));oParsedResponse.results[i]=oResult;}}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;},parseJSONData:function(oRequest,oFullResponse){var oParsedResponse={results:[],meta:{}};if(lang.isObject(oFullResponse)&&this.responseSchema.resultsList){var schema=this.responseSchema,fields=schema.fields,resultsList=oFullResponse,results=[],metaFields=schema.metaFields||{},fieldParsers=[],fieldPaths=[],simpleFields=[],bError=false,i,len,j,v,key,parser,path;var buildPath=function(needle){var path=null,keys=[],i=0;if(needle){needle=needle.replace(/\[(['"])(.*?)\1\]/g,function(x,$1,$2){keys[i]=$2;return".@"+(i++);}).replace(/\[(\d+)\]/g,function(x,$1){keys[i]=parseInt($1,10)|0;return".@"+(i++);}).replace(/^\./,"");if(!/[^\w\.\$@]/.test(needle)){path=needle.split(".");for(i=path.length-1;i>=0;--i){if(path[i].charAt(0)==="@"){path[i]=keys[parseInt(path[i].substr(1),10)];}}}else{}}return path;};var walkPath=function(path,origin){var v=origin,i=0,len=path.length;for(;i<len&&v;++i){v=v[path[i]];}return v;};path=buildPath(schema.resultsList);if(path){resultsList=walkPath(path,oFullResponse);if(resultsList===undefined){bError=true;}}else{bError=true;}if(!resultsList){resultsList=[];}if(!lang.isArray(resultsList)){resultsList=[resultsList];}if(!bError){if(schema.fields){var field;for(i=0,len=fields.length;i<len;i++){field=fields[i];key=field.key||field;parser=((typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""])||field.converter;path=buildPath(key);if(parser){fieldParsers[fieldParsers.length]={key:key,parser:parser};}if(path){if(path.length>1){fieldPaths[fieldPaths.length]={key:key,path:path};}else{simpleFields[simpleFields.length]={key:key,path:path[0]};}}else{}}for(i=resultsList.length-1;i>=0;--i){var r=resultsList[i],rec={};if(r){for(j=simpleFields.length-1;j>=0;--j){rec[simpleFields[j].key]=(r[simpleFields[j].path]!==undefined)?r[simpleFields[j].path]:r[j];}for(j=fieldPaths.length-1;j>=0;--j){rec[fieldPaths[j].key]=walkPath(fieldPaths[j].path,r);}for(j=fieldParsers.length-1;j>=0;--j){var p=fieldParsers[j].key;rec[p]=fieldParsers[j].parser(rec[p]);if(rec[p]===undefined){rec[p]=null;}}}results[i]=rec;}}else{results=resultsList;}for(key in metaFields){if(lang.hasOwnProperty(metaFields,key)){path=buildPath(metaFields[key]);if(path){v=walkPath(path,oFullResponse);oParsedResponse.meta[key]=v;}}}}else{oParsedResponse.error=true;}oParsedResponse.results=results;}else{oParsedResponse.error=true;}return oParsedResponse;},parseHTMLTableData:function(oRequest,oFullResponse){var bError=false;var elTable=oFullResponse;var fields=this.responseSchema.fields;var oParsedResponse={results:[]};if(lang.isArray(fields)){for(var i=0;i<elTable.tBodies.length;i++){var elTbody=elTable.tBodies[i];for(var j=elTbody.rows.length-1;j>-1;j--){var elRow=elTbody.rows[j];var oResult={};for(var k=fields.length-1;k>-1;k--){var field=fields[k];var key=(lang.isValue(field.key))?field.key:field;
10
var data=elRow.cells[k].innerHTML;if(!field.parser&&field.converter){field.parser=field.converter;}var parser=(typeof field.parser==="function")?field.parser:DS.Parser[field.parser+""];if(parser){data=parser.call(this,data);}if(data===undefined){data=null;}oResult[key]=data;}oParsedResponse.results[j]=oResult;}}}else{bError=true;}if(bError){oParsedResponse.error=true;}else{}return oParsedResponse;}};lang.augmentProto(DS,util.EventProvider);util.LocalDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_LOCAL;if(oLiveData){if(YAHOO.lang.isArray(oLiveData)){this.responseType=DS.TYPE_JSARRAY;}else{if(oLiveData.nodeType&&oLiveData.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oLiveData.nodeName&&(oLiveData.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;oLiveData=oLiveData.cloneNode(true);}else{if(YAHOO.lang.isString(oLiveData)){this.responseType=DS.TYPE_TEXT;}else{if(YAHOO.lang.isObject(oLiveData)){this.responseType=DS.TYPE_JSON;}}}}}}else{oLiveData=[];this.responseType=DS.TYPE_JSARRAY;}util.LocalDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.LocalDataSource,DS);lang.augmentObject(util.LocalDataSource,DS);util.FunctionDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_JSFUNCTION;oLiveData=oLiveData||function(){};util.FunctionDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.FunctionDataSource,DS,{scope:null,makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oRawResponse=(this.scope)?this.liveData.call(this.scope,oRequest,this):this.liveData(oRequest);if(this.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){this.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse&&oRawResponse.nodeType&&oRawResponse.nodeType==9){this.responseType=DS.TYPE_XML;}else{if(oRawResponse&&oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){this.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){this.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){this.responseType=DS.TYPE_TEXT;}}}}}}this.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);return tId;}});lang.augmentObject(util.FunctionDataSource,DS);util.ScriptNodeDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_SCRIPTNODE;oLiveData=oLiveData||"";util.ScriptNodeDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.ScriptNodeDataSource,DS,{getUtility:util.Get,asyncMode:"allowAll",scriptCallbackParam:"callback",generateRequestCallback:function(id){return"&"+this.scriptCallbackParam+"=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]";},doBeforeGetScriptNode:function(sUri){return sUri;},makeConnection:function(oRequest,oCallback,oCaller){var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});if(util.ScriptNodeDataSource._nPending===0){util.ScriptNodeDataSource.callbacks=[];util.ScriptNodeDataSource._nId=0;}var id=util.ScriptNodeDataSource._nId;util.ScriptNodeDataSource._nId++;var oSelf=this;util.ScriptNodeDataSource.callbacks[id]=function(oRawResponse){if((oSelf.asyncMode!=="ignoreStaleResponses")||(id===util.ScriptNodeDataSource.callbacks.length-1)){if(oSelf.responseType===DS.TYPE_UNKNOWN){if(YAHOO.lang.isArray(oRawResponse)){oSelf.responseType=DS.TYPE_JSARRAY;}else{if(oRawResponse.nodeType&&oRawResponse.nodeType==9){oSelf.responseType=DS.TYPE_XML;}else{if(oRawResponse.nodeName&&(oRawResponse.nodeName.toLowerCase()=="table")){oSelf.responseType=DS.TYPE_HTMLTABLE;}else{if(YAHOO.lang.isObject(oRawResponse)){oSelf.responseType=DS.TYPE_JSON;}else{if(YAHOO.lang.isString(oRawResponse)){oSelf.responseType=DS.TYPE_TEXT;}}}}}}oSelf.handleResponse(oRequest,oRawResponse,oCallback,oCaller,tId);}else{}delete util.ScriptNodeDataSource.callbacks[id];};util.ScriptNodeDataSource._nPending++;var sUri=this.liveData+oRequest+this.generateRequestCallback(id);sUri=this.doBeforeGetScriptNode(sUri);this.getUtility.script(sUri,{autopurge:true,onsuccess:util.ScriptNodeDataSource._bumpPendingDown,onfail:util.ScriptNodeDataSource._bumpPendingDown});return tId;}});lang.augmentObject(util.ScriptNodeDataSource,DS);lang.augmentObject(util.ScriptNodeDataSource,{_nId:0,_nPending:0,callbacks:[]});util.XHRDataSource=function(oLiveData,oConfigs){this.dataType=DS.TYPE_XHR;this.connMgr=this.connMgr||util.Connect;oLiveData=oLiveData||"";util.XHRDataSource.superclass.constructor.call(this,oLiveData,oConfigs);};lang.extend(util.XHRDataSource,DS,{connMgr:null,connXhrMode:"allowAll",connMethodPost:false,connTimeout:0,makeConnection:function(oRequest,oCallback,oCaller){var oRawResponse=null;var tId=DS._nTransactionId++;this.fireEvent("requestEvent",{tId:tId,request:oRequest,callback:oCallback,caller:oCaller});var oSelf=this;var oConnMgr=this.connMgr;var oQueue=this._oQueue;var _xhrSuccess=function(oResponse){if(oResponse&&(this.connXhrMode=="ignoreStaleResponses")&&(oResponse.tId!=oQueue.conn.tId)){return null;}else{if(!oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:null,callback:oCallback,caller:oCaller,message:DS.ERROR_DATANULL});DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);return null;}else{if(this.responseType===DS.TYPE_UNKNOWN){var ctype=(oResponse.getResponseHeader)?oResponse.getResponseHeader["Content-Type"]:null;if(ctype){if(ctype.indexOf("text/xml")>-1){this.responseType=DS.TYPE_XML;}else{if(ctype.indexOf("application/json")>-1){this.responseType=DS.TYPE_JSON;}else{if(ctype.indexOf("text/plain")>-1){this.responseType=DS.TYPE_TEXT;}}}}}this.handleResponse(oRequest,oResponse,oCallback,oCaller,tId);}}};var _xhrFailure=function(oResponse){this.fireEvent("dataErrorEvent",{request:oRequest,response:oResponse,callback:oCallback,caller:oCaller,message:DS.ERROR_DATAINVALID});if(lang.isString(this.liveData)&&lang.isString(oRequest)&&(this.liveData.lastIndexOf("?")!==this.liveData.length-1)&&(oRequest.indexOf("?")!==0)){}oResponse=oResponse||{};
11
oResponse.error=true;DS.issueCallback(oCallback,[oRequest,oResponse],true,oCaller);return null;};var _xhrCallback={success:_xhrSuccess,failure:_xhrFailure,scope:this};if(lang.isNumber(this.connTimeout)){_xhrCallback.timeout=this.connTimeout;}if(this.connXhrMode=="cancelStaleRequests"){if(oQueue.conn){if(oConnMgr.abort){oConnMgr.abort(oQueue.conn);oQueue.conn=null;}else{}}}if(oConnMgr&&oConnMgr.asyncRequest){var sLiveData=this.liveData;var isPost=this.connMethodPost;var sMethod=(isPost)?"POST":"GET";var sUri=(isPost||!lang.isValue(oRequest))?sLiveData:sLiveData+oRequest;var sRequest=(isPost)?oRequest:null;if(this.connXhrMode!="queueRequests"){oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}else{if(oQueue.conn){var allRequests=oQueue.requests;allRequests.push({request:oRequest,callback:_xhrCallback});if(!oQueue.interval){oQueue.interval=setInterval(function(){if(oConnMgr.isCallInProgress(oQueue.conn)){return;}else{if(allRequests.length>0){sUri=(isPost||!lang.isValue(allRequests[0].request))?sLiveData:sLiveData+allRequests[0].request;sRequest=(isPost)?allRequests[0].request:null;oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,allRequests[0].callback,sRequest);allRequests.shift();}else{clearInterval(oQueue.interval);oQueue.interval=null;}}},50);}}else{oQueue.conn=oConnMgr.asyncRequest(sMethod,sUri,_xhrCallback,sRequest);}}}else{DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);}return tId;}});lang.augmentObject(util.XHRDataSource,DS);util.DataSource=function(oLiveData,oConfigs){oConfigs=oConfigs||{};var dataType=oConfigs.dataType;if(dataType){if(dataType==DS.TYPE_LOCAL){lang.augmentObject(util.DataSource,util.LocalDataSource);return new util.LocalDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_XHR){lang.augmentObject(util.DataSource,util.XHRDataSource);return new util.XHRDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_SCRIPTNODE){lang.augmentObject(util.DataSource,util.ScriptNodeDataSource);return new util.ScriptNodeDataSource(oLiveData,oConfigs);}else{if(dataType==DS.TYPE_JSFUNCTION){lang.augmentObject(util.DataSource,util.FunctionDataSource);return new util.FunctionDataSource(oLiveData,oConfigs);}}}}}if(YAHOO.lang.isString(oLiveData)){lang.augmentObject(util.DataSource,util.XHRDataSource);return new util.XHRDataSource(oLiveData,oConfigs);}else{if(YAHOO.lang.isFunction(oLiveData)){lang.augmentObject(util.DataSource,util.FunctionDataSource);return new util.FunctionDataSource(oLiveData,oConfigs);}else{lang.augmentObject(util.DataSource,util.LocalDataSource);return new util.LocalDataSource(oLiveData,oConfigs);}}};lang.augmentObject(util.DataSource,DS);})();YAHOO.util.Number={format:function(B,E){if(!isFinite(+B)){return"";}B=!isFinite(+B)?0:+B;E=YAHOO.lang.merge(YAHOO.util.Number.format.defaults,(E||{}));var C=B<0,F=Math.abs(B),A=E.decimalPlaces,I=E.thousandsSeparator,H,G,D;if(A<0){H=F-(F%1)+"";D=H.length+A;if(D>0){H=Number("."+H).toFixed(D).slice(2)+new Array(H.length-D+1).join("0");}else{H="0";}}else{H=F<1&&F>=0.5&&!A?"1":F.toFixed(A);}if(F>1000){G=H.split(/\D/);D=G[0].length%3||3;G[0]=G[0].slice(0,D)+G[0].slice(D).replace(/(\d{3})/g,I+"$1");H=G.join(E.decimalSeparator);}H=E.prefix+H+E.suffix;return C?E.negativeFormat.replace(/#/,H):H;}};YAHOO.util.Number.format.defaults={decimalSeparator:".",decimalPlaces:null,thousandsSeparator:"",prefix:"",suffix:"",negativeFormat:"-#"};(function(){var A=function(C,E,D){if(typeof D==="undefined"){D=10;}for(;parseInt(C,10)<D&&D>1;D/=10){C=E.toString()+C;}return C.toString();};var B={formats:{a:function(D,C){return C.a[D.getDay()];},A:function(D,C){return C.A[D.getDay()];},b:function(D,C){return C.b[D.getMonth()];},B:function(D,C){return C.B[D.getMonth()];},C:function(C){return A(parseInt(C.getFullYear()/100,10),0);},d:["getDate","0"],e:["getDate"," "],g:function(C){return A(parseInt(B.formats.G(C)%100,10),0);},G:function(E){var F=E.getFullYear();var D=parseInt(B.formats.V(E),10);var C=parseInt(B.formats.W(E),10);if(C>D){F++;}else{if(C===0&&D>=52){F--;}}return F;},H:["getHours","0"],I:function(D){var C=D.getHours()%12;return A(C===0?12:C,0);},j:function(G){var F=new Date(""+G.getFullYear()+"/1/1 GMT");var D=new Date(""+G.getFullYear()+"/"+(G.getMonth()+1)+"/"+G.getDate()+" GMT");var C=D-F;var E=parseInt(C/60000/60/24,10)+1;return A(E,0,100);},k:["getHours"," "],l:function(D){var C=D.getHours()%12;return A(C===0?12:C," ");},m:function(C){return A(C.getMonth()+1,0);},M:["getMinutes","0"],p:function(D,C){return C.p[D.getHours()>=12?1:0];},P:function(D,C){return C.P[D.getHours()>=12?1:0];},s:function(D,C){return parseInt(D.getTime()/1000,10);},S:["getSeconds","0"],u:function(C){var D=C.getDay();return D===0?7:D;},U:function(F){var C=parseInt(B.formats.j(F),10);var E=6-F.getDay();var D=parseInt((C+E)/7,10);return A(D,0);},V:function(F){var E=parseInt(B.formats.W(F),10);var C=(new Date(""+F.getFullYear()+"/1/1")).getDay();var D=E+(C>4||C<=1?0:1);if(D===53&&(new Date(""+F.getFullYear()+"/12/31")).getDay()<4){D=1;}else{if(D===0){D=B.formats.V(new Date(""+(F.getFullYear()-1)+"/12/31"));}}return A(D,0);},w:"getDay",W:function(F){var C=parseInt(B.formats.j(F),10);var E=7-B.formats.u(F);var D=parseInt((C+E)/7,10);return A(D,0,10);},y:function(C){return A(C.getFullYear()%100,0);},Y:"getFullYear",z:function(E){var D=E.getTimezoneOffset();var C=A(parseInt(Math.abs(D/60),10),0);var F=A(Math.abs(D%60),0);return(D>0?"-":"+")+C+F;},Z:function(C){var D=C.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/,"$2").replace(/[a-z ]/g,"");if(D.length>4){D=B.formats.z(C);}return D;},"%":function(C){return"%";}},aggregates:{c:"locale",D:"%m/%d/%y",F:"%Y-%m-%d",h:"%b",n:"\n",r:"locale",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"},format:function(G,F,D){F=F||{};if(!(G instanceof Date)){return YAHOO.lang.isValue(G)?G:"";}var H=F.format||"%m/%d/%Y";if(H==="YYYY/MM/DD"){H="%Y/%m/%d";}else{if(H==="DD/MM/YYYY"){H="%d/%m/%Y";}else{if(H==="MM/DD/YYYY"){H="%m/%d/%Y";}}}D=D||"en";if(!(D in YAHOO.util.DateLocale)){if(D.replace(/-[a-zA-Z]+$/,"") in YAHOO.util.DateLocale){D=D.replace(/-[a-zA-Z]+$/,"");
12
}else{D="en";}}var J=YAHOO.util.DateLocale[D];var C=function(L,K){var M=B.aggregates[K];return(M==="locale"?J[K]:M);};var E=function(L,K){var M=B.formats[K];if(typeof M==="string"){return G[M]();}else{if(typeof M==="function"){return M.call(G,G,J);}else{if(typeof M==="object"&&typeof M[0]==="string"){return A(G[M[0]](),M[1]);}else{return K;}}}};while(H.match(/%[cDFhnrRtTxX]/)){H=H.replace(/%([cDFhnrRtTxX])/g,C);}var I=H.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g,E);C=E=undefined;return I;}};YAHOO.namespace("YAHOO.util");YAHOO.util.Date=B;YAHOO.util.DateLocale={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],r:"%I:%M:%S %p",x:"%d/%m/%y",X:"%T"};YAHOO.util.DateLocale["en"]=YAHOO.lang.merge(YAHOO.util.DateLocale,{});YAHOO.util.DateLocale["en-US"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{c:"%a %d %b %Y %I:%M:%S %p %Z",x:"%m/%d/%Y",X:"%I:%M:%S %p"});YAHOO.util.DateLocale["en-GB"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"],{r:"%l:%M:%S %P %Z"});YAHOO.util.DateLocale["en-AU"]=YAHOO.lang.merge(YAHOO.util.DateLocale["en"]);})();YAHOO.register("datasource",YAHOO.util.DataSource,{version:"2.8.0r4",build:"2449"});
(-)a/koha-tmpl/intranet-tmpl/prog/en/lib/yui/datasource/datasource.js (-2997 lines)
Lines 1-2996 Link Here
1
/*
2
Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3
Code licensed under the BSD License:
4
http://developer.yahoo.net/yui/license.txt
5
version: 2.8.0r4
6
*/
7
(function () {
8
9
var lang   = YAHOO.lang,
10
    util   = YAHOO.util,
11
    Ev     = util.Event;
12
13
/**
14
 * The DataSource utility provides a common configurable interface for widgets to
15
 * access a variety of data, from JavaScript arrays to online database servers.
16
 *
17
 * @module datasource
18
 * @requires yahoo, event
19
 * @optional json, get, connection 
20
 * @title DataSource Utility
21
 */
22
23
/****************************************************************************/
24
/****************************************************************************/
25
/****************************************************************************/
26
27
/**
28
 * Base class for the YUI DataSource utility.
29
 *
30
 * @namespace YAHOO.util
31
 * @class YAHOO.util.DataSourceBase
32
 * @constructor
33
 * @param oLiveData {HTMLElement}  Pointer to live data.
34
 * @param oConfigs {object} (optional) Object literal of configuration values.
35
 */
36
util.DataSourceBase = function(oLiveData, oConfigs) {
37
    if(oLiveData === null || oLiveData === undefined) {
38
        return;
39
    }
40
    
41
    this.liveData = oLiveData;
42
    this._oQueue = {interval:null, conn:null, requests:[]};
43
    this.responseSchema = {};   
44
45
    // Set any config params passed in to override defaults
46
    if(oConfigs && (oConfigs.constructor == Object)) {
47
        for(var sConfig in oConfigs) {
48
            if(sConfig) {
49
                this[sConfig] = oConfigs[sConfig];
50
            }
51
        }
52
    }
53
    
54
    // Validate and initialize public configs
55
    var maxCacheEntries = this.maxCacheEntries;
56
    if(!lang.isNumber(maxCacheEntries) || (maxCacheEntries < 0)) {
57
        maxCacheEntries = 0;
58
    }
59
60
    // Initialize interval tracker
61
    this._aIntervals = [];
62
63
    /////////////////////////////////////////////////////////////////////////////
64
    //
65
    // Custom Events
66
    //
67
    /////////////////////////////////////////////////////////////////////////////
68
69
    /**
70
     * Fired when a request is made to the local cache.
71
     *
72
     * @event cacheRequestEvent
73
     * @param oArgs.request {Object} The request object.
74
     * @param oArgs.callback {Object} The callback object.
75
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
76
     */
77
    this.createEvent("cacheRequestEvent");
78
79
    /**
80
     * Fired when data is retrieved from the local cache.
81
     *
82
     * @event cacheResponseEvent
83
     * @param oArgs.request {Object} The request object.
84
     * @param oArgs.response {Object} The response object.
85
     * @param oArgs.callback {Object} The callback object.
86
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
87
     */
88
    this.createEvent("cacheResponseEvent");
89
90
    /**
91
     * Fired when a request is sent to the live data source.
92
     *
93
     * @event requestEvent
94
     * @param oArgs.request {Object} The request object.
95
     * @param oArgs.callback {Object} The callback object.
96
     * @param oArgs.tId {Number} Transaction ID.     
97
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
98
     */
99
    this.createEvent("requestEvent");
100
101
    /**
102
     * Fired when live data source sends response.
103
     *
104
     * @event responseEvent
105
     * @param oArgs.request {Object} The request object.
106
     * @param oArgs.response {Object} The raw response object.
107
     * @param oArgs.callback {Object} The callback object.
108
     * @param oArgs.tId {Number} Transaction ID.     
109
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
110
     */
111
    this.createEvent("responseEvent");
112
113
    /**
114
     * Fired when response is parsed.
115
     *
116
     * @event responseParseEvent
117
     * @param oArgs.request {Object} The request object.
118
     * @param oArgs.response {Object} The parsed response object.
119
     * @param oArgs.callback {Object} The callback object.
120
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
121
     */
122
    this.createEvent("responseParseEvent");
123
124
    /**
125
     * Fired when response is cached.
126
     *
127
     * @event responseCacheEvent
128
     * @param oArgs.request {Object} The request object.
129
     * @param oArgs.response {Object} The parsed response object.
130
     * @param oArgs.callback {Object} The callback object.
131
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
132
     */
133
    this.createEvent("responseCacheEvent");
134
    /**
135
     * Fired when an error is encountered with the live data source.
136
     *
137
     * @event dataErrorEvent
138
     * @param oArgs.request {Object} The request object.
139
     * @param oArgs.response {String} The response object (if available).
140
     * @param oArgs.callback {Object} The callback object.
141
     * @param oArgs.caller {Object} (deprecated) Use callback.scope.
142
     * @param oArgs.message {String} The error message.
143
     */
144
    this.createEvent("dataErrorEvent");
145
146
    /**
147
     * Fired when the local cache is flushed.
148
     *
149
     * @event cacheFlushEvent
150
     */
151
    this.createEvent("cacheFlushEvent");
152
153
    var DS = util.DataSourceBase;
154
    this._sName = "DataSource instance" + DS._nIndex;
155
    DS._nIndex++;
156
};
157
158
var DS = util.DataSourceBase;
159
160
lang.augmentObject(DS, {
161
162
/////////////////////////////////////////////////////////////////////////////
163
//
164
// DataSourceBase public constants
165
//
166
/////////////////////////////////////////////////////////////////////////////
167
168
/**
169
 * Type is unknown.
170
 *
171
 * @property TYPE_UNKNOWN
172
 * @type Number
173
 * @final
174
 * @default -1
175
 */
176
TYPE_UNKNOWN : -1,
177
178
/**
179
 * Type is a JavaScript Array.
180
 *
181
 * @property TYPE_JSARRAY
182
 * @type Number
183
 * @final
184
 * @default 0
185
 */
186
TYPE_JSARRAY : 0,
187
188
/**
189
 * Type is a JavaScript Function.
190
 *
191
 * @property TYPE_JSFUNCTION
192
 * @type Number
193
 * @final
194
 * @default 1
195
 */
196
TYPE_JSFUNCTION : 1,
197
198
/**
199
 * Type is hosted on a server via an XHR connection.
200
 *
201
 * @property TYPE_XHR
202
 * @type Number
203
 * @final
204
 * @default 2
205
 */
206
TYPE_XHR : 2,
207
208
/**
209
 * Type is JSON.
210
 *
211
 * @property TYPE_JSON
212
 * @type Number
213
 * @final
214
 * @default 3
215
 */
216
TYPE_JSON : 3,
217
218
/**
219
 * Type is XML.
220
 *
221
 * @property TYPE_XML
222
 * @type Number
223
 * @final
224
 * @default 4
225
 */
226
TYPE_XML : 4,
227
228
/**
229
 * Type is plain text.
230
 *
231
 * @property TYPE_TEXT
232
 * @type Number
233
 * @final
234
 * @default 5
235
 */
236
TYPE_TEXT : 5,
237
238
/**
239
 * Type is an HTML TABLE element. Data is parsed out of TR elements from all TBODY elements.
240
 *
241
 * @property TYPE_HTMLTABLE
242
 * @type Number
243
 * @final
244
 * @default 6
245
 */
246
TYPE_HTMLTABLE : 6,
247
248
/**
249
 * Type is hosted on a server via a dynamic script node.
250
 *
251
 * @property TYPE_SCRIPTNODE
252
 * @type Number
253
 * @final
254
 * @default 7
255
 */
256
TYPE_SCRIPTNODE : 7,
257
258
/**
259
 * Type is local.
260
 *
261
 * @property TYPE_LOCAL
262
 * @type Number
263
 * @final
264
 * @default 8
265
 */
266
TYPE_LOCAL : 8,
267
268
/**
269
 * Error message for invalid dataresponses.
270
 *
271
 * @property ERROR_DATAINVALID
272
 * @type String
273
 * @final
274
 * @default "Invalid data"
275
 */
276
ERROR_DATAINVALID : "Invalid data",
277
278
/**
279
 * Error message for null data responses.
280
 *
281
 * @property ERROR_DATANULL
282
 * @type String
283
 * @final
284
 * @default "Null data"
285
 */
286
ERROR_DATANULL : "Null data",
287
288
/////////////////////////////////////////////////////////////////////////////
289
//
290
// DataSourceBase private static properties
291
//
292
/////////////////////////////////////////////////////////////////////////////
293
294
/**
295
 * Internal class variable to index multiple DataSource instances.
296
 *
297
 * @property DataSourceBase._nIndex
298
 * @type Number
299
 * @private
300
 * @static
301
 */
302
_nIndex : 0,
303
304
/**
305
 * Internal class variable to assign unique transaction IDs.
306
 *
307
 * @property DataSourceBase._nTransactionId
308
 * @type Number
309
 * @private
310
 * @static
311
 */
312
_nTransactionId : 0,
313
314
/////////////////////////////////////////////////////////////////////////////
315
//
316
// DataSourceBase private static methods
317
//
318
/////////////////////////////////////////////////////////////////////////////
319
320
/**
321
 * Get an XPath-specified value for a given field from an XML node or document.
322
 *
323
 * @method _getLocationValue
324
 * @param field {String | Object} Field definition.
325
 * @param context {Object} XML node or document to search within.
326
 * @return {Object} Data value or null.
327
 * @static
328
 * @private
329
 */
330
_getLocationValue: function(field, context) {
331
    var locator = field.locator || field.key || field,
332
        xmldoc = context.ownerDocument || context,
333
        result, res, value = null;
334
335
    try {
336
        // Standards mode
337
        if(!lang.isUndefined(xmldoc.evaluate)) {
338
            result = xmldoc.evaluate(locator, context, xmldoc.createNSResolver(!context.ownerDocument ? context.documentElement : context.ownerDocument.documentElement), 0, null);
339
            while(res = result.iterateNext()) {
340
                value = res.textContent;
341
            }
342
        }
343
        // IE mode
344
        else {
345
            xmldoc.setProperty("SelectionLanguage", "XPath");
346
            result = context.selectNodes(locator)[0];
347
            value = result.value || result.text || null;
348
        }
349
        return value;
350
351
    }
352
    catch(e) {
353
    }
354
},
355
356
/////////////////////////////////////////////////////////////////////////////
357
//
358
// DataSourceBase public static methods
359
//
360
/////////////////////////////////////////////////////////////////////////////
361
362
/**
363
 * Executes a configured callback.  For object literal callbacks, the third
364
 * param determines whether to execute the success handler or failure handler.
365
 *  
366
 * @method issueCallback
367
 * @param callback {Function|Object} the callback to execute
368
 * @param params {Array} params to be passed to the callback method
369
 * @param error {Boolean} whether an error occurred
370
 * @param scope {Object} the scope from which to execute the callback
371
 * (deprecated - use an object literal callback)
372
 * @static     
373
 */
374
issueCallback : function (callback,params,error,scope) {
375
    if (lang.isFunction(callback)) {
376
        callback.apply(scope, params);
377
    } else if (lang.isObject(callback)) {
378
        scope = callback.scope || scope || window;
379
        var callbackFunc = callback.success;
380
        if (error) {
381
            callbackFunc = callback.failure;
382
        }
383
        if (callbackFunc) {
384
            callbackFunc.apply(scope, params.concat([callback.argument]));
385
        }
386
    }
387
},
388
389
/**
390
 * Converts data to type String.
391
 *
392
 * @method DataSourceBase.parseString
393
 * @param oData {String | Number | Boolean | Date | Array | Object} Data to parse.
394
 * The special values null and undefined will return null.
395
 * @return {String} A string, or null.
396
 * @static
397
 */
398
parseString : function(oData) {
399
    // Special case null and undefined
400
    if(!lang.isValue(oData)) {
401
        return null;
402
    }
403
    
404
    //Convert to string
405
    var string = oData + "";
406
407
    // Validate
408
    if(lang.isString(string)) {
409
        return string;
410
    }
411
    else {
412
        return null;
413
    }
414
},
415
416
/**
417
 * Converts data to type Number.
418
 *
419
 * @method DataSourceBase.parseNumber
420
 * @param oData {String | Number | Boolean} Data to convert. Note, the following
421
 * values return as null: null, undefined, NaN, "". 
422
 * @return {Number} A number, or null.
423
 * @static
424
 */
425
parseNumber : function(oData) {
426
    if(!lang.isValue(oData) || (oData === "")) {
427
        return null;
428
    }
429
430
    //Convert to number
431
    var number = oData * 1;
432
    
433
    // Validate
434
    if(lang.isNumber(number)) {
435
        return number;
436
    }
437
    else {
438
        return null;
439
    }
440
},
441
// Backward compatibility
442
convertNumber : function(oData) {
443
    return DS.parseNumber(oData);
444
},
445
446
/**
447
 * Converts data to type Date.
448
 *
449
 * @method DataSourceBase.parseDate
450
 * @param oData {Date | String | Number} Data to convert.
451
 * @return {Date} A Date instance.
452
 * @static
453
 */
454
parseDate : function(oData) {
455
    var date = null;
456
    
457
    //Convert to date
458
    if(!(oData instanceof Date)) {
459
        date = new Date(oData);
460
    }
461
    else {
462
        return oData;
463
    }
464
    
465
    // Validate
466
    if(date instanceof Date) {
467
        return date;
468
    }
469
    else {
470
        return null;
471
    }
472
},
473
// Backward compatibility
474
convertDate : function(oData) {
475
    return DS.parseDate(oData);
476
}
477
478
});
479
480
// Done in separate step so referenced functions are defined.
481
/**
482
 * Data parsing functions.
483
 * @property DataSource.Parser
484
 * @type Object
485
 * @static
486
 */
487
DS.Parser = {
488
    string   : DS.parseString,
489
    number   : DS.parseNumber,
490
    date     : DS.parseDate
491
};
492
493
// Prototype properties and methods
494
DS.prototype = {
495
496
/////////////////////////////////////////////////////////////////////////////
497
//
498
// DataSourceBase private properties
499
//
500
/////////////////////////////////////////////////////////////////////////////
501
502
/**
503
 * Name of DataSource instance.
504
 *
505
 * @property _sName
506
 * @type String
507
 * @private
508
 */
509
_sName : null,
510
511
/**
512
 * Local cache of data result object literals indexed chronologically.
513
 *
514
 * @property _aCache
515
 * @type Object[]
516
 * @private
517
 */
518
_aCache : null,
519
520
/**
521
 * Local queue of request connections, enabled if queue needs to be managed.
522
 *
523
 * @property _oQueue
524
 * @type Object
525
 * @private
526
 */
527
_oQueue : null,
528
529
/**
530
 * Array of polling interval IDs that have been enabled, needed to clear all intervals.
531
 *
532
 * @property _aIntervals
533
 * @type Array
534
 * @private
535
 */
536
_aIntervals : null,
537
538
/////////////////////////////////////////////////////////////////////////////
539
//
540
// DataSourceBase public properties
541
//
542
/////////////////////////////////////////////////////////////////////////////
543
544
/**
545
 * Max size of the local cache.  Set to 0 to turn off caching.  Caching is
546
 * useful to reduce the number of server connections.  Recommended only for data
547
 * sources that return comprehensive results for queries or when stale data is
548
 * not an issue.
549
 *
550
 * @property maxCacheEntries
551
 * @type Number
552
 * @default 0
553
 */
554
maxCacheEntries : 0,
555
556
 /**
557
 * Pointer to live database.
558
 *
559
 * @property liveData
560
 * @type Object
561
 */
562
liveData : null,
563
564
/**
565
 * Where the live data is held:
566
 * 
567
 * <dl>  
568
 *    <dt>TYPE_UNKNOWN</dt>
569
 *    <dt>TYPE_LOCAL</dt>
570
 *    <dt>TYPE_XHR</dt>
571
 *    <dt>TYPE_SCRIPTNODE</dt>
572
 *    <dt>TYPE_JSFUNCTION</dt>
573
 * </dl> 
574
 *  
575
 * @property dataType
576
 * @type Number
577
 * @default YAHOO.util.DataSourceBase.TYPE_UNKNOWN
578
 *
579
 */
580
dataType : DS.TYPE_UNKNOWN,
581
582
/**
583
 * Format of response:
584
 *  
585
 * <dl>  
586
 *    <dt>TYPE_UNKNOWN</dt>
587
 *    <dt>TYPE_JSARRAY</dt>
588
 *    <dt>TYPE_JSON</dt>
589
 *    <dt>TYPE_XML</dt>
590
 *    <dt>TYPE_TEXT</dt>
591
 *    <dt>TYPE_HTMLTABLE</dt> 
592
 * </dl> 
593
 *
594
 * @property responseType
595
 * @type Number
596
 * @default YAHOO.util.DataSourceBase.TYPE_UNKNOWN
597
 */
598
responseType : DS.TYPE_UNKNOWN,
599
600
/**
601
 * Response schema object literal takes a combination of the following properties:
602
 *
603
 * <dl>
604
 * <dt>resultsList</dt> <dd>Pointer to array of tabular data</dd>
605
 * <dt>resultNode</dt> <dd>Pointer to node name of row data (XML data only)</dd>
606
 * <dt>recordDelim</dt> <dd>Record delimiter (text data only)</dd>
607
 * <dt>fieldDelim</dt> <dd>Field delimiter (text data only)</dd>
608
 * <dt>fields</dt> <dd>Array of field names (aka keys), or array of object literals
609
 * such as: {key:"fieldname",parser:YAHOO.util.DataSourceBase.parseDate}</dd>
610
 * <dt>metaFields</dt> <dd>Object literal of keys to include in the oParsedResponse.meta collection</dd>
611
 * <dt>metaNode</dt> <dd>Name of the node under which to search for meta information in XML response data</dd>
612
 * </dl>
613
 *
614
 * @property responseSchema
615
 * @type Object
616
 */
617
responseSchema : null,
618
619
/**
620
 * Additional arguments passed to the JSON parse routine.  The JSON string
621
 * is the assumed first argument (where applicable).  This property is not
622
 * set by default, but the parse methods will use it if present.
623
 *
624
 * @property parseJSONArgs
625
 * @type {MIXED|Array} If an Array, contents are used as individual arguments.
626
 *                     Otherwise, value is used as an additional argument.
627
 */
628
// property intentionally undefined
629
 
630
/**
631
 * When working with XML data, setting this property to true enables support for
632
 * XPath-syntaxed locators in schema definitions.
633
 *
634
 * @property useXPath
635
 * @type Boolean
636
 * @default false
637
 */
638
useXPath : false,
639
640
/////////////////////////////////////////////////////////////////////////////
641
//
642
// DataSourceBase public methods
643
//
644
/////////////////////////////////////////////////////////////////////////////
645
646
/**
647
 * Public accessor to the unique name of the DataSource instance.
648
 *
649
 * @method toString
650
 * @return {String} Unique name of the DataSource instance.
651
 */
652
toString : function() {
653
    return this._sName;
654
},
655
656
/**
657
 * Overridable method passes request to cache and returns cached response if any,
658
 * refreshing the hit in the cache as the newest item. Returns null if there is
659
 * no cache hit.
660
 *
661
 * @method getCachedResponse
662
 * @param oRequest {Object} Request object.
663
 * @param oCallback {Object} Callback object.
664
 * @param oCaller {Object} (deprecated) Use callback object.
665
 * @return {Object} Cached response object or null.
666
 */
667
getCachedResponse : function(oRequest, oCallback, oCaller) {
668
    var aCache = this._aCache;
669
670
    // If cache is enabled...
671
    if(this.maxCacheEntries > 0) {        
672
        // Initialize local cache
673
        if(!aCache) {
674
            this._aCache = [];
675
        }
676
        // Look in local cache
677
        else {
678
            var nCacheLength = aCache.length;
679
            if(nCacheLength > 0) {
680
                var oResponse = null;
681
                this.fireEvent("cacheRequestEvent", {request:oRequest,callback:oCallback,caller:oCaller});
682
        
683
                // Loop through each cached element
684
                for(var i = nCacheLength-1; i >= 0; i--) {
685
                    var oCacheElem = aCache[i];
686
        
687
                    // Defer cache hit logic to a public overridable method
688
                    if(this.isCacheHit(oRequest,oCacheElem.request)) {
689
                        // The cache returned a hit!
690
                        // Grab the cached response
691
                        oResponse = oCacheElem.response;
692
                        this.fireEvent("cacheResponseEvent", {request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});
693
                        
694
                        // Refresh the position of the cache hit
695
                        if(i < nCacheLength-1) {
696
                            // Remove element from its original location
697
                            aCache.splice(i,1);
698
                            // Add as newest
699
                            this.addToCache(oRequest, oResponse);
700
                        }
701
                        
702
                        // Add a cache flag
703
                        oResponse.cached = true;
704
                        break;
705
                    }
706
                }
707
                return oResponse;
708
            }
709
        }
710
    }
711
    else if(aCache) {
712
        this._aCache = null;
713
    }
714
    return null;
715
},
716
717
/**
718
 * Default overridable method matches given request to given cached request.
719
 * Returns true if is a hit, returns false otherwise.  Implementers should
720
 * override this method to customize the cache-matching algorithm.
721
 *
722
 * @method isCacheHit
723
 * @param oRequest {Object} Request object.
724
 * @param oCachedRequest {Object} Cached request object.
725
 * @return {Boolean} True if given request matches cached request, false otherwise.
726
 */
727
isCacheHit : function(oRequest, oCachedRequest) {
728
    return (oRequest === oCachedRequest);
729
},
730
731
/**
732
 * Adds a new item to the cache. If cache is full, evicts the stalest item
733
 * before adding the new item.
734
 *
735
 * @method addToCache
736
 * @param oRequest {Object} Request object.
737
 * @param oResponse {Object} Response object to cache.
738
 */
739
addToCache : function(oRequest, oResponse) {
740
    var aCache = this._aCache;
741
    if(!aCache) {
742
        return;
743
    }
744
745
    // If the cache is full, make room by removing stalest element (index=0)
746
    while(aCache.length >= this.maxCacheEntries) {
747
        aCache.shift();
748
    }
749
750
    // Add to cache in the newest position, at the end of the array
751
    var oCacheElem = {request:oRequest,response:oResponse};
752
    aCache[aCache.length] = oCacheElem;
753
    this.fireEvent("responseCacheEvent", {request:oRequest,response:oResponse});
754
},
755
756
/**
757
 * Flushes cache.
758
 *
759
 * @method flushCache
760
 */
761
flushCache : function() {
762
    if(this._aCache) {
763
        this._aCache = [];
764
        this.fireEvent("cacheFlushEvent");
765
    }
766
},
767
768
/**
769
 * Sets up a polling mechanism to send requests at set intervals and forward
770
 * responses to given callback.
771
 *
772
 * @method setInterval
773
 * @param nMsec {Number} Length of interval in milliseconds.
774
 * @param oRequest {Object} Request object.
775
 * @param oCallback {Function} Handler function to receive the response.
776
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
777
 * @return {Number} Interval ID.
778
 */
779
setInterval : function(nMsec, oRequest, oCallback, oCaller) {
780
    if(lang.isNumber(nMsec) && (nMsec >= 0)) {
781
        var oSelf = this;
782
        var nId = setInterval(function() {
783
            oSelf.makeConnection(oRequest, oCallback, oCaller);
784
        }, nMsec);
785
        this._aIntervals.push(nId);
786
        return nId;
787
    }
788
    else {
789
    }
790
},
791
792
/**
793
 * Disables polling mechanism associated with the given interval ID.
794
 *
795
 * @method clearInterval
796
 * @param nId {Number} Interval ID.
797
 */
798
clearInterval : function(nId) {
799
    // Remove from tracker if there
800
    var tracker = this._aIntervals || [];
801
    for(var i=tracker.length-1; i>-1; i--) {
802
        if(tracker[i] === nId) {
803
            tracker.splice(i,1);
804
            clearInterval(nId);
805
        }
806
    }
807
},
808
809
/**
810
 * Disables all known polling intervals.
811
 *
812
 * @method clearAllIntervals
813
 */
814
clearAllIntervals : function() {
815
    var tracker = this._aIntervals || [];
816
    for(var i=tracker.length-1; i>-1; i--) {
817
        clearInterval(tracker[i]);
818
    }
819
    tracker = [];
820
},
821
822
/**
823
 * First looks for cached response, then sends request to live data. The
824
 * following arguments are passed to the callback function:
825
 *     <dl>
826
 *     <dt><code>oRequest</code></dt>
827
 *     <dd>The same value that was passed in as the first argument to sendRequest.</dd>
828
 *     <dt><code>oParsedResponse</code></dt>
829
 *     <dd>An object literal containing the following properties:
830
 *         <dl>
831
 *         <dt><code>tId</code></dt>
832
 *         <dd>Unique transaction ID number.</dd>
833
 *         <dt><code>results</code></dt>
834
 *         <dd>Schema-parsed data results.</dd>
835
 *         <dt><code>error</code></dt>
836
 *         <dd>True in cases of data error.</dd>
837
 *         <dt><code>cached</code></dt>
838
 *         <dd>True when response is returned from DataSource cache.</dd> 
839
 *         <dt><code>meta</code></dt>
840
 *         <dd>Schema-parsed meta data.</dd>
841
 *         </dl>
842
 *     <dt><code>oPayload</code></dt>
843
 *     <dd>The same value as was passed in as <code>argument</code> in the oCallback object literal.</dd>
844
 *     </dl> 
845
 *
846
 * @method sendRequest
847
 * @param oRequest {Object} Request object.
848
 * @param oCallback {Object} An object literal with the following properties:
849
 *     <dl>
850
 *     <dt><code>success</code></dt>
851
 *     <dd>The function to call when the data is ready.</dd>
852
 *     <dt><code>failure</code></dt>
853
 *     <dd>The function to call upon a response failure condition.</dd>
854
 *     <dt><code>scope</code></dt>
855
 *     <dd>The object to serve as the scope for the success and failure handlers.</dd>
856
 *     <dt><code>argument</code></dt>
857
 *     <dd>Arbitrary data that will be passed back to the success and failure handlers.</dd>
858
 *     </dl> 
859
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
860
 * @return {Number} Transaction ID, or null if response found in cache.
861
 */
862
sendRequest : function(oRequest, oCallback, oCaller) {
863
    // First look in cache
864
    var oCachedResponse = this.getCachedResponse(oRequest, oCallback, oCaller);
865
    if(oCachedResponse) {
866
        DS.issueCallback(oCallback,[oRequest,oCachedResponse],false,oCaller);
867
        return null;
868
    }
869
870
871
    // Not in cache, so forward request to live data
872
    return this.makeConnection(oRequest, oCallback, oCaller);
873
},
874
875
/**
876
 * Overridable default method generates a unique transaction ID and passes 
877
 * the live data reference directly to the  handleResponse function. This
878
 * method should be implemented by subclasses to achieve more complex behavior
879
 * or to access remote data.          
880
 *
881
 * @method makeConnection
882
 * @param oRequest {Object} Request object.
883
 * @param oCallback {Object} Callback object literal.
884
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
885
 * @return {Number} Transaction ID.
886
 */
887
makeConnection : function(oRequest, oCallback, oCaller) {
888
    var tId = DS._nTransactionId++;
889
    this.fireEvent("requestEvent", {tId:tId, request:oRequest,callback:oCallback,caller:oCaller});
890
891
    /* accounts for the following cases:
892
    YAHOO.util.DataSourceBase.TYPE_UNKNOWN
893
    YAHOO.util.DataSourceBase.TYPE_JSARRAY
894
    YAHOO.util.DataSourceBase.TYPE_JSON
895
    YAHOO.util.DataSourceBase.TYPE_HTMLTABLE
896
    YAHOO.util.DataSourceBase.TYPE_XML
897
    YAHOO.util.DataSourceBase.TYPE_TEXT
898
    */
899
    var oRawResponse = this.liveData;
900
    
901
    this.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
902
    return tId;
903
},
904
905
/**
906
 * Receives raw data response and type converts to XML, JSON, etc as necessary.
907
 * Forwards oFullResponse to appropriate parsing function to get turned into
908
 * oParsedResponse. Calls doBeforeCallback() and adds oParsedResponse to 
909
 * the cache when appropriate before calling issueCallback().
910
 * 
911
 * The oParsedResponse object literal has the following properties:
912
 * <dl>
913
 *     <dd><dt>tId {Number}</dt> Unique transaction ID</dd>
914
 *     <dd><dt>results {Array}</dt> Array of parsed data results</dd>
915
 *     <dd><dt>meta {Object}</dt> Object literal of meta values</dd> 
916
 *     <dd><dt>error {Boolean}</dt> (optional) True if there was an error</dd>
917
 *     <dd><dt>cached {Boolean}</dt> (optional) True if response was cached</dd>
918
 * </dl>
919
 *
920
 * @method handleResponse
921
 * @param oRequest {Object} Request object
922
 * @param oRawResponse {Object} The raw response from the live database.
923
 * @param oCallback {Object} Callback object literal.
924
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
925
 * @param tId {Number} Transaction ID.
926
 */
927
handleResponse : function(oRequest, oRawResponse, oCallback, oCaller, tId) {
928
    this.fireEvent("responseEvent", {tId:tId, request:oRequest, response:oRawResponse,
929
            callback:oCallback, caller:oCaller});
930
    var xhr = (this.dataType == DS.TYPE_XHR) ? true : false;
931
    var oParsedResponse = null;
932
    var oFullResponse = oRawResponse;
933
    
934
    // Try to sniff data type if it has not been defined
935
    if(this.responseType === DS.TYPE_UNKNOWN) {
936
        var ctype = (oRawResponse && oRawResponse.getResponseHeader) ? oRawResponse.getResponseHeader["Content-Type"] : null;
937
        if(ctype) {
938
             // xml
939
            if(ctype.indexOf("text/xml") > -1) {
940
                this.responseType = DS.TYPE_XML;
941
            }
942
            else if(ctype.indexOf("application/json") > -1) { // json
943
                this.responseType = DS.TYPE_JSON;
944
            }
945
            else if(ctype.indexOf("text/plain") > -1) { // text
946
                this.responseType = DS.TYPE_TEXT;
947
            }
948
        }
949
        else {
950
            if(YAHOO.lang.isArray(oRawResponse)) { // array
951
                this.responseType = DS.TYPE_JSARRAY;
952
            }
953
             // xml
954
            else if(oRawResponse && oRawResponse.nodeType && (oRawResponse.nodeType === 9 || oRawResponse.nodeType === 1 || oRawResponse.nodeType === 11)) {
955
                this.responseType = DS.TYPE_XML;
956
            }
957
            else if(oRawResponse && oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
958
                this.responseType = DS.TYPE_HTMLTABLE;
959
            }    
960
            else if(YAHOO.lang.isObject(oRawResponse)) { // json
961
                this.responseType = DS.TYPE_JSON;
962
            }
963
            else if(YAHOO.lang.isString(oRawResponse)) { // text
964
                this.responseType = DS.TYPE_TEXT;
965
            }
966
        }
967
    }
968
969
    switch(this.responseType) {
970
        case DS.TYPE_JSARRAY:
971
            if(xhr && oRawResponse && oRawResponse.responseText) {
972
                oFullResponse = oRawResponse.responseText; 
973
            }
974
            try {
975
                // Convert to JS array if it's a string
976
                if(lang.isString(oFullResponse)) {
977
                    var parseArgs = [oFullResponse].concat(this.parseJSONArgs);
978
                    // Check for YUI JSON Util
979
                    if(lang.JSON) {
980
                        oFullResponse = lang.JSON.parse.apply(lang.JSON,parseArgs);
981
                    }
982
                    // Look for JSON parsers using an API similar to json2.js
983
                    else if(window.JSON && JSON.parse) {
984
                        oFullResponse = JSON.parse.apply(JSON,parseArgs);
985
                    }
986
                    // Look for JSON parsers using an API similar to json.js
987
                    else if(oFullResponse.parseJSON) {
988
                        oFullResponse = oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));
989
                    }
990
                    // No JSON lib found so parse the string
991
                    else {
992
                        // Trim leading spaces
993
                        while (oFullResponse.length > 0 &&
994
                                (oFullResponse.charAt(0) != "{") &&
995
                                (oFullResponse.charAt(0) != "[")) {
996
                            oFullResponse = oFullResponse.substring(1, oFullResponse.length);
997
                        }
998
999
                        if(oFullResponse.length > 0) {
1000
                            // Strip extraneous stuff at the end
1001
                            var arrayEnd =
1002
Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));
1003
                            oFullResponse = oFullResponse.substring(0,arrayEnd+1);
1004
1005
                            // Turn the string into an object literal...
1006
                            // ...eval is necessary here
1007
                            oFullResponse = eval("(" + oFullResponse + ")");
1008
1009
                        }
1010
                    }
1011
                }
1012
            }
1013
            catch(e1) {
1014
            }
1015
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1016
            oParsedResponse = this.parseArrayData(oRequest, oFullResponse);
1017
            break;
1018
        case DS.TYPE_JSON:
1019
            if(xhr && oRawResponse && oRawResponse.responseText) {
1020
                oFullResponse = oRawResponse.responseText;
1021
            }
1022
            try {
1023
                // Convert to JSON object if it's a string
1024
                if(lang.isString(oFullResponse)) {
1025
                    var parseArgs = [oFullResponse].concat(this.parseJSONArgs);
1026
                    // Check for YUI JSON Util
1027
                    if(lang.JSON) {
1028
                        oFullResponse = lang.JSON.parse.apply(lang.JSON,parseArgs);
1029
                    }
1030
                    // Look for JSON parsers using an API similar to json2.js
1031
                    else if(window.JSON && JSON.parse) {
1032
                        oFullResponse = JSON.parse.apply(JSON,parseArgs);
1033
                    }
1034
                    // Look for JSON parsers using an API similar to json.js
1035
                    else if(oFullResponse.parseJSON) {
1036
                        oFullResponse = oFullResponse.parseJSON.apply(oFullResponse,parseArgs.slice(1));
1037
                    }
1038
                    // No JSON lib found so parse the string
1039
                    else {
1040
                        // Trim leading spaces
1041
                        while (oFullResponse.length > 0 &&
1042
                                (oFullResponse.charAt(0) != "{") &&
1043
                                (oFullResponse.charAt(0) != "[")) {
1044
                            oFullResponse = oFullResponse.substring(1, oFullResponse.length);
1045
                        }
1046
    
1047
                        if(oFullResponse.length > 0) {
1048
                            // Strip extraneous stuff at the end
1049
                            var objEnd = Math.max(oFullResponse.lastIndexOf("]"),oFullResponse.lastIndexOf("}"));
1050
                            oFullResponse = oFullResponse.substring(0,objEnd+1);
1051
    
1052
                            // Turn the string into an object literal...
1053
                            // ...eval is necessary here
1054
                            oFullResponse = eval("(" + oFullResponse + ")");
1055
    
1056
                        }
1057
                    }
1058
                }
1059
            }
1060
            catch(e) {
1061
            }
1062
1063
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1064
            oParsedResponse = this.parseJSONData(oRequest, oFullResponse);
1065
            break;
1066
        case DS.TYPE_HTMLTABLE:
1067
            if(xhr && oRawResponse.responseText) {
1068
                var el = document.createElement('div');
1069
                el.innerHTML = oRawResponse.responseText;
1070
                oFullResponse = el.getElementsByTagName('table')[0];
1071
            }
1072
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1073
            oParsedResponse = this.parseHTMLTableData(oRequest, oFullResponse);
1074
            break;
1075
        case DS.TYPE_XML:
1076
            if(xhr && oRawResponse.responseXML) {
1077
                oFullResponse = oRawResponse.responseXML;
1078
            }
1079
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1080
            oParsedResponse = this.parseXMLData(oRequest, oFullResponse);
1081
            break;
1082
        case DS.TYPE_TEXT:
1083
            if(xhr && lang.isString(oRawResponse.responseText)) {
1084
                oFullResponse = oRawResponse.responseText;
1085
            }
1086
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1087
            oParsedResponse = this.parseTextData(oRequest, oFullResponse);
1088
            break;
1089
        default:
1090
            oFullResponse = this.doBeforeParseData(oRequest, oFullResponse, oCallback);
1091
            oParsedResponse = this.parseData(oRequest, oFullResponse);
1092
            break;
1093
    }
1094
1095
1096
    // Clean up for consistent signature
1097
    oParsedResponse = oParsedResponse || {};
1098
    if(!oParsedResponse.results) {
1099
        oParsedResponse.results = [];
1100
    }
1101
    if(!oParsedResponse.meta) {
1102
        oParsedResponse.meta = {};
1103
    }
1104
1105
    // Success
1106
    if(!oParsedResponse.error) {
1107
        // Last chance to touch the raw response or the parsed response
1108
        oParsedResponse = this.doBeforeCallback(oRequest, oFullResponse, oParsedResponse, oCallback);
1109
        this.fireEvent("responseParseEvent", {request:oRequest,
1110
                response:oParsedResponse, callback:oCallback, caller:oCaller});
1111
        // Cache the response
1112
        this.addToCache(oRequest, oParsedResponse);
1113
    }
1114
    // Error
1115
    else {
1116
        // Be sure the error flag is on
1117
        oParsedResponse.error = true;
1118
        this.fireEvent("dataErrorEvent", {request:oRequest, response: oRawResponse, callback:oCallback, 
1119
                caller:oCaller, message:DS.ERROR_DATANULL});
1120
    }
1121
1122
    // Send the response back to the caller
1123
    oParsedResponse.tId = tId;
1124
    DS.issueCallback(oCallback,[oRequest,oParsedResponse],oParsedResponse.error,oCaller);
1125
},
1126
1127
/**
1128
 * Overridable method gives implementers access to the original full response
1129
 * before the data gets parsed. Implementers should take care not to return an
1130
 * unparsable or otherwise invalid response.
1131
 *
1132
 * @method doBeforeParseData
1133
 * @param oRequest {Object} Request object.
1134
 * @param oFullResponse {Object} The full response from the live database.
1135
 * @param oCallback {Object} The callback object.  
1136
 * @return {Object} Full response for parsing.
1137
  
1138
 */
1139
doBeforeParseData : function(oRequest, oFullResponse, oCallback) {
1140
    return oFullResponse;
1141
},
1142
1143
/**
1144
 * Overridable method gives implementers access to the original full response and
1145
 * the parsed response (parsed against the given schema) before the data
1146
 * is added to the cache (if applicable) and then sent back to callback function.
1147
 * This is your chance to access the raw response and/or populate the parsed
1148
 * response with any custom data.
1149
 *
1150
 * @method doBeforeCallback
1151
 * @param oRequest {Object} Request object.
1152
 * @param oFullResponse {Object} The full response from the live database.
1153
 * @param oParsedResponse {Object} The parsed response to return to calling object.
1154
 * @param oCallback {Object} The callback object. 
1155
 * @return {Object} Parsed response object.
1156
 */
1157
doBeforeCallback : function(oRequest, oFullResponse, oParsedResponse, oCallback) {
1158
    return oParsedResponse;
1159
},
1160
1161
/**
1162
 * Overridable method parses data of generic RESPONSE_TYPE into a response object.
1163
 *
1164
 * @method parseData
1165
 * @param oRequest {Object} Request object.
1166
 * @param oFullResponse {Object} The full Array from the live database.
1167
 * @return {Object} Parsed response object with the following properties:<br>
1168
 *     - results {Array} Array of parsed data results<br>
1169
 *     - meta {Object} Object literal of meta values<br>
1170
 *     - error {Boolean} (optional) True if there was an error<br>
1171
 */
1172
parseData : function(oRequest, oFullResponse) {
1173
    if(lang.isValue(oFullResponse)) {
1174
        var oParsedResponse = {results:oFullResponse,meta:{}};
1175
        return oParsedResponse;
1176
1177
    }
1178
    return null;
1179
},
1180
1181
/**
1182
 * Overridable method parses Array data into a response object.
1183
 *
1184
 * @method parseArrayData
1185
 * @param oRequest {Object} Request object.
1186
 * @param oFullResponse {Object} The full Array from the live database.
1187
 * @return {Object} Parsed response object with the following properties:<br>
1188
 *     - results (Array) Array of parsed data results<br>
1189
 *     - error (Boolean) True if there was an error
1190
 */
1191
parseArrayData : function(oRequest, oFullResponse) {
1192
    if(lang.isArray(oFullResponse)) {
1193
        var results = [],
1194
            i, j,
1195
            rec, field, data;
1196
        
1197
        // Parse for fields
1198
        if(lang.isArray(this.responseSchema.fields)) {
1199
            var fields = this.responseSchema.fields;
1200
            for (i = fields.length - 1; i >= 0; --i) {
1201
                if (typeof fields[i] !== 'object') {
1202
                    fields[i] = { key : fields[i] };
1203
                }
1204
            }
1205
1206
            var parsers = {}, p;
1207
            for (i = fields.length - 1; i >= 0; --i) {
1208
                p = (typeof fields[i].parser === 'function' ?
1209
                          fields[i].parser :
1210
                          DS.Parser[fields[i].parser+'']) || fields[i].converter;
1211
                if (p) {
1212
                    parsers[fields[i].key] = p;
1213
                }
1214
            }
1215
1216
            var arrType = lang.isArray(oFullResponse[0]);
1217
            for(i=oFullResponse.length-1; i>-1; i--) {
1218
                var oResult = {};
1219
                rec = oFullResponse[i];
1220
                if (typeof rec === 'object') {
1221
                    for(j=fields.length-1; j>-1; j--) {
1222
                        field = fields[j];
1223
                        data = arrType ? rec[j] : rec[field.key];
1224
1225
                        if (parsers[field.key]) {
1226
                            data = parsers[field.key].call(this,data);
1227
                        }
1228
1229
                        // Safety measure
1230
                        if(data === undefined) {
1231
                            data = null;
1232
                        }
1233
1234
                        oResult[field.key] = data;
1235
                    }
1236
                }
1237
                else if (lang.isString(rec)) {
1238
                    for(j=fields.length-1; j>-1; j--) {
1239
                        field = fields[j];
1240
                        data = rec;
1241
1242
                        if (parsers[field.key]) {
1243
                            data = parsers[field.key].call(this,data);
1244
                        }
1245
1246
                        // Safety measure
1247
                        if(data === undefined) {
1248
                            data = null;
1249
                        }
1250
1251
                        oResult[field.key] = data;
1252
                    }                
1253
                }
1254
                results[i] = oResult;
1255
            }    
1256
        }
1257
        // Return entire data set
1258
        else {
1259
            results = oFullResponse;
1260
        }
1261
        var oParsedResponse = {results:results};
1262
        return oParsedResponse;
1263
1264
    }
1265
    return null;
1266
},
1267
1268
/**
1269
 * Overridable method parses plain text data into a response object.
1270
 *
1271
 * @method parseTextData
1272
 * @param oRequest {Object} Request object.
1273
 * @param oFullResponse {Object} The full text response from the live database.
1274
 * @return {Object} Parsed response object with the following properties:<br>
1275
 *     - results (Array) Array of parsed data results<br>
1276
 *     - error (Boolean) True if there was an error
1277
 */
1278
parseTextData : function(oRequest, oFullResponse) {
1279
    if(lang.isString(oFullResponse)) {
1280
        if(lang.isString(this.responseSchema.recordDelim) &&
1281
                lang.isString(this.responseSchema.fieldDelim)) {
1282
            var oParsedResponse = {results:[]};
1283
            var recDelim = this.responseSchema.recordDelim;
1284
            var fieldDelim = this.responseSchema.fieldDelim;
1285
            if(oFullResponse.length > 0) {
1286
                // Delete the last line delimiter at the end of the data if it exists
1287
                var newLength = oFullResponse.length-recDelim.length;
1288
                if(oFullResponse.substr(newLength) == recDelim) {
1289
                    oFullResponse = oFullResponse.substr(0, newLength);
1290
                }
1291
                if(oFullResponse.length > 0) {
1292
                    // Split along record delimiter to get an array of strings
1293
                    var recordsarray = oFullResponse.split(recDelim);
1294
                    // Cycle through each record
1295
                    for(var i = 0, len = recordsarray.length, recIdx = 0; i < len; ++i) {
1296
                        var bError = false,
1297
                            sRecord = recordsarray[i];
1298
                        if (lang.isString(sRecord) && (sRecord.length > 0)) {
1299
                            // Split each record along field delimiter to get data
1300
                            var fielddataarray = recordsarray[i].split(fieldDelim);
1301
                            var oResult = {};
1302
                            
1303
                            // Filter for fields data
1304
                            if(lang.isArray(this.responseSchema.fields)) {
1305
                                var fields = this.responseSchema.fields;
1306
                                for(var j=fields.length-1; j>-1; j--) {
1307
                                    try {
1308
                                        // Remove quotation marks from edges, if applicable
1309
                                        var data = fielddataarray[j];
1310
                                        if (lang.isString(data)) {
1311
                                            if(data.charAt(0) == "\"") {
1312
                                                data = data.substr(1);
1313
                                            }
1314
                                            if(data.charAt(data.length-1) == "\"") {
1315
                                                data = data.substr(0,data.length-1);
1316
                                            }
1317
                                            var field = fields[j];
1318
                                            var key = (lang.isValue(field.key)) ? field.key : field;
1319
                                            // Backward compatibility
1320
                                            if(!field.parser && field.converter) {
1321
                                                field.parser = field.converter;
1322
                                            }
1323
                                            var parser = (typeof field.parser === 'function') ?
1324
                                                field.parser :
1325
                                                DS.Parser[field.parser+''];
1326
                                            if(parser) {
1327
                                                data = parser.call(this, data);
1328
                                            }
1329
                                            // Safety measure
1330
                                            if(data === undefined) {
1331
                                                data = null;
1332
                                            }
1333
                                            oResult[key] = data;
1334
                                        }
1335
                                        else {
1336
                                            bError = true;
1337
                                        }
1338
                                    }
1339
                                    catch(e) {
1340
                                        bError = true;
1341
                                    }
1342
                                }
1343
                            }            
1344
                            // No fields defined so pass along all data as an array
1345
                            else {
1346
                                oResult = fielddataarray;
1347
                            }
1348
                            if(!bError) {
1349
                                oParsedResponse.results[recIdx++] = oResult;
1350
                            }
1351
                        }
1352
                    }
1353
                }
1354
            }
1355
            return oParsedResponse;
1356
        }
1357
    }
1358
    return null;
1359
            
1360
},
1361
1362
/**
1363
 * Overridable method parses XML data for one result into an object literal.
1364
 *
1365
 * @method parseXMLResult
1366
 * @param result {XML} XML for one result.
1367
 * @return {Object} Object literal of data for one result.
1368
 */
1369
parseXMLResult : function(result) {
1370
    var oResult = {},
1371
        schema = this.responseSchema;
1372
        
1373
    try {
1374
        // Loop through each data field in each result using the schema
1375
        for(var m = schema.fields.length-1; m >= 0 ; m--) {
1376
            var field = schema.fields[m];
1377
            var key = (lang.isValue(field.key)) ? field.key : field;
1378
            var data = null;
1379
1380
            if(this.useXPath) {
1381
                data = YAHOO.util.DataSource._getLocationValue(field, result);
1382
            }
1383
            else {
1384
                // Values may be held in an attribute...
1385
                var xmlAttr = result.attributes.getNamedItem(key);
1386
                if(xmlAttr) {
1387
                    data = xmlAttr.value;
1388
                }
1389
                // ...or in a node
1390
                else {
1391
                    var xmlNode = result.getElementsByTagName(key);
1392
                    if(xmlNode && xmlNode.item(0)) {
1393
                        var item = xmlNode.item(0);
1394
                        // For IE, then DOM...
1395
                        data = (item) ? ((item.text) ? item.text : (item.textContent) ? item.textContent : null) : null;
1396
                        // ...then fallback, but check for multiple child nodes
1397
                        if(!data) {
1398
                            var datapieces = [];
1399
                            for(var j=0, len=item.childNodes.length; j<len; j++) {
1400
                                if(item.childNodes[j].nodeValue) {
1401
                                    datapieces[datapieces.length] = item.childNodes[j].nodeValue;
1402
                                }
1403
                            }
1404
                            if(datapieces.length > 0) {
1405
                                data = datapieces.join("");
1406
                            }
1407
                        }
1408
                    }
1409
                }
1410
            }
1411
            
1412
            
1413
            // Safety net
1414
            if(data === null) {
1415
                   data = "";
1416
            }
1417
            // Backward compatibility
1418
            if(!field.parser && field.converter) {
1419
                field.parser = field.converter;
1420
            }
1421
            var parser = (typeof field.parser === 'function') ?
1422
                field.parser :
1423
                DS.Parser[field.parser+''];
1424
            if(parser) {
1425
                data = parser.call(this, data);
1426
            }
1427
            // Safety measure
1428
            if(data === undefined) {
1429
                data = null;
1430
            }
1431
            oResult[key] = data;
1432
        }
1433
    }
1434
    catch(e) {
1435
    }
1436
1437
    return oResult;
1438
},
1439
1440
1441
1442
/**
1443
 * Overridable method parses XML data into a response object.
1444
 *
1445
 * @method parseXMLData
1446
 * @param oRequest {Object} Request object.
1447
 * @param oFullResponse {Object} The full XML response from the live database.
1448
 * @return {Object} Parsed response object with the following properties<br>
1449
 *     - results (Array) Array of parsed data results<br>
1450
 *     - error (Boolean) True if there was an error
1451
 */
1452
parseXMLData : function(oRequest, oFullResponse) {
1453
    var bError = false,
1454
        schema = this.responseSchema,
1455
        oParsedResponse = {meta:{}},
1456
        xmlList = null,
1457
        metaNode      = schema.metaNode,
1458
        metaLocators  = schema.metaFields || {},
1459
        i,k,loc,v;
1460
1461
    // In case oFullResponse is something funky
1462
    try {
1463
        // Pull any meta identified
1464
        if(this.useXPath) {
1465
            for (k in metaLocators) {
1466
                oParsedResponse.meta[k] = YAHOO.util.DataSource._getLocationValue(metaLocators[k], oFullResponse);
1467
            }
1468
        }
1469
        else {
1470
            metaNode = metaNode ? oFullResponse.getElementsByTagName(metaNode)[0] :
1471
                       oFullResponse;
1472
1473
            if (metaNode) {
1474
                for (k in metaLocators) {
1475
                    if (lang.hasOwnProperty(metaLocators, k)) {
1476
                        loc = metaLocators[k];
1477
                        // Look for a node
1478
                        v = metaNode.getElementsByTagName(loc)[0];
1479
1480
                        if (v) {
1481
                            v = v.firstChild.nodeValue;
1482
                        } else {
1483
                            // Look for an attribute
1484
                            v = metaNode.attributes.getNamedItem(loc);
1485
                            if (v) {
1486
                                v = v.value;
1487
                            }
1488
                        }
1489
1490
                        if (lang.isValue(v)) {
1491
                            oParsedResponse.meta[k] = v;
1492
                        }
1493
                    }
1494
                }
1495
            }
1496
        }
1497
        
1498
        // For result data
1499
        xmlList = (schema.resultNode) ?
1500
            oFullResponse.getElementsByTagName(schema.resultNode) :
1501
            null;
1502
    }
1503
    catch(e) {
1504
    }
1505
    if(!xmlList || !lang.isArray(schema.fields)) {
1506
        bError = true;
1507
    }
1508
    // Loop through each result
1509
    else {
1510
        oParsedResponse.results = [];
1511
        for(i = xmlList.length-1; i >= 0 ; --i) {
1512
            var oResult = this.parseXMLResult(xmlList.item(i));
1513
            // Capture each array of values into an array of results
1514
            oParsedResponse.results[i] = oResult;
1515
        }
1516
    }
1517
    if(bError) {
1518
        oParsedResponse.error = true;
1519
    }
1520
    else {
1521
    }
1522
    return oParsedResponse;
1523
},
1524
1525
/**
1526
 * Overridable method parses JSON data into a response object.
1527
 *
1528
 * @method parseJSONData
1529
 * @param oRequest {Object} Request object.
1530
 * @param oFullResponse {Object} The full JSON from the live database.
1531
 * @return {Object} Parsed response object with the following properties<br>
1532
 *     - results (Array) Array of parsed data results<br>
1533
 *     - error (Boolean) True if there was an error
1534
 */
1535
parseJSONData : function(oRequest, oFullResponse) {
1536
    var oParsedResponse = {results:[],meta:{}};
1537
    
1538
    if(lang.isObject(oFullResponse) && this.responseSchema.resultsList) {
1539
        var schema = this.responseSchema,
1540
            fields          = schema.fields,
1541
            resultsList     = oFullResponse,
1542
            results         = [],
1543
            metaFields      = schema.metaFields || {},
1544
            fieldParsers    = [],
1545
            fieldPaths      = [],
1546
            simpleFields    = [],
1547
            bError          = false,
1548
            i,len,j,v,key,parser,path;
1549
1550
        // Function to convert the schema's fields into walk paths
1551
        var buildPath = function (needle) {
1552
            var path = null, keys = [], i = 0;
1553
            if (needle) {
1554
                // Strip the ["string keys"] and [1] array indexes
1555
                needle = needle.
1556
                    replace(/\[(['"])(.*?)\1\]/g,
1557
                    function (x,$1,$2) {keys[i]=$2;return '.@'+(i++);}).
1558
                    replace(/\[(\d+)\]/g,
1559
                    function (x,$1) {keys[i]=parseInt($1,10)|0;return '.@'+(i++);}).
1560
                    replace(/^\./,''); // remove leading dot
1561
1562
                // If the cleaned needle contains invalid characters, the
1563
                // path is invalid
1564
                if (!/[^\w\.\$@]/.test(needle)) {
1565
                    path = needle.split('.');
1566
                    for (i=path.length-1; i >= 0; --i) {
1567
                        if (path[i].charAt(0) === '@') {
1568
                            path[i] = keys[parseInt(path[i].substr(1),10)];
1569
                        }
1570
                    }
1571
                }
1572
                else {
1573
                }
1574
            }
1575
            return path;
1576
        };
1577
1578
1579
        // Function to walk a path and return the pot of gold
1580
        var walkPath = function (path, origin) {
1581
            var v=origin,i=0,len=path.length;
1582
            for (;i<len && v;++i) {
1583
                v = v[path[i]];
1584
            }
1585
            return v;
1586
        };
1587
1588
        // Parse the response
1589
        // Step 1. Pull the resultsList from oFullResponse (default assumes
1590
        // oFullResponse IS the resultsList)
1591
        path = buildPath(schema.resultsList);
1592
        if (path) {
1593
            resultsList = walkPath(path, oFullResponse);
1594
            if (resultsList === undefined) {
1595
                bError = true;
1596
            }
1597
        } else {
1598
            bError = true;
1599
        }
1600
        
1601
        if (!resultsList) {
1602
            resultsList = [];
1603
        }
1604
1605
        if (!lang.isArray(resultsList)) {
1606
            resultsList = [resultsList];
1607
        }
1608
1609
        if (!bError) {
1610
            // Step 2. Parse out field data if identified
1611
            if(schema.fields) {
1612
                var field;
1613
                // Build the field parser map and location paths
1614
                for (i=0, len=fields.length; i<len; i++) {
1615
                    field = fields[i];
1616
                    key    = field.key || field;
1617
                    parser = ((typeof field.parser === 'function') ?
1618
                        field.parser :
1619
                        DS.Parser[field.parser+'']) || field.converter;
1620
                    path   = buildPath(key);
1621
    
1622
                    if (parser) {
1623
                        fieldParsers[fieldParsers.length] = {key:key,parser:parser};
1624
                    }
1625
    
1626
                    if (path) {
1627
                        if (path.length > 1) {
1628
                            fieldPaths[fieldPaths.length] = {key:key,path:path};
1629
                        } else {
1630
                            simpleFields[simpleFields.length] = {key:key,path:path[0]};
1631
                        }
1632
                    } else {
1633
                    }
1634
                }
1635
1636
                // Process the results, flattening the records and/or applying parsers if needed
1637
                for (i = resultsList.length - 1; i >= 0; --i) {
1638
                    var r = resultsList[i], rec = {};
1639
                    if(r) {
1640
                        for (j = simpleFields.length - 1; j >= 0; --j) {
1641
                            // Bug 1777850: data might be held in an array
1642
                            rec[simpleFields[j].key] =
1643
                                    (r[simpleFields[j].path] !== undefined) ?
1644
                                    r[simpleFields[j].path] : r[j];
1645
                        }
1646
1647
                        for (j = fieldPaths.length - 1; j >= 0; --j) {
1648
                            rec[fieldPaths[j].key] = walkPath(fieldPaths[j].path,r);
1649
                        }
1650
1651
                        for (j = fieldParsers.length - 1; j >= 0; --j) {
1652
                            var p = fieldParsers[j].key;
1653
                            rec[p] = fieldParsers[j].parser(rec[p]);
1654
                            if (rec[p] === undefined) {
1655
                                rec[p] = null;
1656
                            }
1657
                        }
1658
                    }
1659
                    results[i] = rec;
1660
                }
1661
            }
1662
            else {
1663
                results = resultsList;
1664
            }
1665
1666
            for (key in metaFields) {
1667
                if (lang.hasOwnProperty(metaFields,key)) {
1668
                    path = buildPath(metaFields[key]);
1669
                    if (path) {
1670
                        v = walkPath(path, oFullResponse);
1671
                        oParsedResponse.meta[key] = v;
1672
                    }
1673
                }
1674
            }
1675
1676
        } else {
1677
1678
            oParsedResponse.error = true;
1679
        }
1680
1681
        oParsedResponse.results = results;
1682
    }
1683
    else {
1684
        oParsedResponse.error = true;
1685
    }
1686
1687
    return oParsedResponse;
1688
},
1689
1690
/**
1691
 * Overridable method parses an HTML TABLE element reference into a response object.
1692
 * Data is parsed out of TR elements from all TBODY elements. 
1693
 *
1694
 * @method parseHTMLTableData
1695
 * @param oRequest {Object} Request object.
1696
 * @param oFullResponse {Object} The full HTML element reference from the live database.
1697
 * @return {Object} Parsed response object with the following properties<br>
1698
 *     - results (Array) Array of parsed data results<br>
1699
 *     - error (Boolean) True if there was an error
1700
 */
1701
parseHTMLTableData : function(oRequest, oFullResponse) {
1702
    var bError = false;
1703
    var elTable = oFullResponse;
1704
    var fields = this.responseSchema.fields;
1705
    var oParsedResponse = {results:[]};
1706
1707
    if(lang.isArray(fields)) {
1708
        // Iterate through each TBODY
1709
        for(var i=0; i<elTable.tBodies.length; i++) {
1710
            var elTbody = elTable.tBodies[i];
1711
    
1712
            // Iterate through each TR
1713
            for(var j=elTbody.rows.length-1; j>-1; j--) {
1714
                var elRow = elTbody.rows[j];
1715
                var oResult = {};
1716
                
1717
                for(var k=fields.length-1; k>-1; k--) {
1718
                    var field = fields[k];
1719
                    var key = (lang.isValue(field.key)) ? field.key : field;
1720
                    var data = elRow.cells[k].innerHTML;
1721
    
1722
                    // Backward compatibility
1723
                    if(!field.parser && field.converter) {
1724
                        field.parser = field.converter;
1725
                    }
1726
                    var parser = (typeof field.parser === 'function') ?
1727
                        field.parser :
1728
                        DS.Parser[field.parser+''];
1729
                    if(parser) {
1730
                        data = parser.call(this, data);
1731
                    }
1732
                    // Safety measure
1733
                    if(data === undefined) {
1734
                        data = null;
1735
                    }
1736
                    oResult[key] = data;
1737
                }
1738
                oParsedResponse.results[j] = oResult;
1739
            }
1740
        }
1741
    }
1742
    else {
1743
        bError = true;
1744
    }
1745
1746
    if(bError) {
1747
        oParsedResponse.error = true;
1748
    }
1749
    else {
1750
    }
1751
    return oParsedResponse;
1752
}
1753
1754
};
1755
1756
// DataSourceBase uses EventProvider
1757
lang.augmentProto(DS, util.EventProvider);
1758
1759
1760
1761
/****************************************************************************/
1762
/****************************************************************************/
1763
/****************************************************************************/
1764
1765
/**
1766
 * LocalDataSource class for in-memory data structs including JavaScript arrays,
1767
 * JavaScript object literals (JSON), XML documents, and HTML tables.
1768
 *
1769
 * @namespace YAHOO.util
1770
 * @class YAHOO.util.LocalDataSource
1771
 * @extends YAHOO.util.DataSourceBase 
1772
 * @constructor
1773
 * @param oLiveData {HTMLElement}  Pointer to live data.
1774
 * @param oConfigs {object} (optional) Object literal of configuration values.
1775
 */
1776
util.LocalDataSource = function(oLiveData, oConfigs) {
1777
    this.dataType = DS.TYPE_LOCAL;
1778
    
1779
    if(oLiveData) {
1780
        if(YAHOO.lang.isArray(oLiveData)) { // array
1781
            this.responseType = DS.TYPE_JSARRAY;
1782
        }
1783
         // xml
1784
        else if(oLiveData.nodeType && oLiveData.nodeType == 9) {
1785
            this.responseType = DS.TYPE_XML;
1786
        }
1787
        else if(oLiveData.nodeName && (oLiveData.nodeName.toLowerCase() == "table")) { // table
1788
            this.responseType = DS.TYPE_HTMLTABLE;
1789
            oLiveData = oLiveData.cloneNode(true);
1790
        }    
1791
        else if(YAHOO.lang.isString(oLiveData)) { // text
1792
            this.responseType = DS.TYPE_TEXT;
1793
        }
1794
        else if(YAHOO.lang.isObject(oLiveData)) { // json
1795
            this.responseType = DS.TYPE_JSON;
1796
        }
1797
    }
1798
    else {
1799
        oLiveData = [];
1800
        this.responseType = DS.TYPE_JSARRAY;
1801
    }
1802
    
1803
    util.LocalDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
1804
};
1805
1806
// LocalDataSource extends DataSourceBase
1807
lang.extend(util.LocalDataSource, DS);
1808
1809
// Copy static members to LocalDataSource class
1810
lang.augmentObject(util.LocalDataSource, DS);
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
/****************************************************************************/
1825
/****************************************************************************/
1826
/****************************************************************************/
1827
1828
/**
1829
 * FunctionDataSource class for JavaScript functions.
1830
 *
1831
 * @namespace YAHOO.util
1832
 * @class YAHOO.util.FunctionDataSource
1833
 * @extends YAHOO.util.DataSourceBase  
1834
 * @constructor
1835
 * @param oLiveData {HTMLElement}  Pointer to live data.
1836
 * @param oConfigs {object} (optional) Object literal of configuration values.
1837
 */
1838
util.FunctionDataSource = function(oLiveData, oConfigs) {
1839
    this.dataType = DS.TYPE_JSFUNCTION;
1840
    oLiveData = oLiveData || function() {};
1841
    
1842
    util.FunctionDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
1843
};
1844
1845
// FunctionDataSource extends DataSourceBase
1846
lang.extend(util.FunctionDataSource, DS, {
1847
1848
/////////////////////////////////////////////////////////////////////////////
1849
//
1850
// FunctionDataSource public properties
1851
//
1852
/////////////////////////////////////////////////////////////////////////////
1853
1854
/**
1855
 * Context in which to execute the function. By default, is the DataSource
1856
 * instance itself. If set, the function will receive the DataSource instance
1857
 * as an additional argument. 
1858
 *
1859
 * @property scope
1860
 * @type Object
1861
 * @default null
1862
 */
1863
scope : null,
1864
1865
1866
/////////////////////////////////////////////////////////////////////////////
1867
//
1868
// FunctionDataSource public methods
1869
//
1870
/////////////////////////////////////////////////////////////////////////////
1871
1872
/**
1873
 * Overriding method passes query to a function. The returned response is then
1874
 * forwarded to the handleResponse function.
1875
 *
1876
 * @method makeConnection
1877
 * @param oRequest {Object} Request object.
1878
 * @param oCallback {Object} Callback object literal.
1879
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
1880
 * @return {Number} Transaction ID.
1881
 */
1882
makeConnection : function(oRequest, oCallback, oCaller) {
1883
    var tId = DS._nTransactionId++;
1884
    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
1885
1886
    // Pass the request in as a parameter and
1887
    // forward the return value to the handler
1888
    
1889
    
1890
    var oRawResponse = (this.scope) ? this.liveData.call(this.scope, oRequest, this) : this.liveData(oRequest);
1891
    
1892
    // Try to sniff data type if it has not been defined
1893
    if(this.responseType === DS.TYPE_UNKNOWN) {
1894
        if(YAHOO.lang.isArray(oRawResponse)) { // array
1895
            this.responseType = DS.TYPE_JSARRAY;
1896
        }
1897
         // xml
1898
        else if(oRawResponse && oRawResponse.nodeType && oRawResponse.nodeType == 9) {
1899
            this.responseType = DS.TYPE_XML;
1900
        }
1901
        else if(oRawResponse && oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
1902
            this.responseType = DS.TYPE_HTMLTABLE;
1903
        }    
1904
        else if(YAHOO.lang.isObject(oRawResponse)) { // json
1905
            this.responseType = DS.TYPE_JSON;
1906
        }
1907
        else if(YAHOO.lang.isString(oRawResponse)) { // text
1908
            this.responseType = DS.TYPE_TEXT;
1909
        }
1910
    }
1911
1912
    this.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
1913
    return tId;
1914
}
1915
1916
});
1917
1918
// Copy static members to FunctionDataSource class
1919
lang.augmentObject(util.FunctionDataSource, DS);
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
/****************************************************************************/
1934
/****************************************************************************/
1935
/****************************************************************************/
1936
1937
/**
1938
 * ScriptNodeDataSource class for accessing remote data via the YUI Get Utility. 
1939
 *
1940
 * @namespace YAHOO.util
1941
 * @class YAHOO.util.ScriptNodeDataSource
1942
 * @extends YAHOO.util.DataSourceBase  
1943
 * @constructor
1944
 * @param oLiveData {HTMLElement}  Pointer to live data.
1945
 * @param oConfigs {object} (optional) Object literal of configuration values.
1946
 */
1947
util.ScriptNodeDataSource = function(oLiveData, oConfigs) {
1948
    this.dataType = DS.TYPE_SCRIPTNODE;
1949
    oLiveData = oLiveData || "";
1950
    
1951
    util.ScriptNodeDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
1952
};
1953
1954
// ScriptNodeDataSource extends DataSourceBase
1955
lang.extend(util.ScriptNodeDataSource, DS, {
1956
1957
/////////////////////////////////////////////////////////////////////////////
1958
//
1959
// ScriptNodeDataSource public properties
1960
//
1961
/////////////////////////////////////////////////////////////////////////////
1962
1963
/**
1964
 * Alias to YUI Get Utility, to allow implementers to use a custom class.
1965
 *
1966
 * @property getUtility
1967
 * @type Object
1968
 * @default YAHOO.util.Get
1969
 */
1970
getUtility : util.Get,
1971
1972
/**
1973
 * Defines request/response management in the following manner:
1974
 * <dl>
1975
 *     <!--<dt>queueRequests</dt>
1976
 *     <dd>If a request is already in progress, wait until response is returned before sending the next request.</dd>
1977
 *     <dt>cancelStaleRequests</dt>
1978
 *     <dd>If a request is already in progress, cancel it before sending the next request.</dd>-->
1979
 *     <dt>ignoreStaleResponses</dt>
1980
 *     <dd>Send all requests, but handle only the response for the most recently sent request.</dd>
1981
 *     <dt>allowAll</dt>
1982
 *     <dd>Send all requests and handle all responses.</dd>
1983
 * </dl>
1984
 *
1985
 * @property asyncMode
1986
 * @type String
1987
 * @default "allowAll"
1988
 */
1989
asyncMode : "allowAll",
1990
1991
/**
1992
 * Callback string parameter name sent to the remote script. By default,
1993
 * requests are sent to
1994
 * &#60;URI&#62;?&#60;scriptCallbackParam&#62;=callbackFunction
1995
 *
1996
 * @property scriptCallbackParam
1997
 * @type String
1998
 * @default "callback"
1999
 */
2000
scriptCallbackParam : "callback",
2001
2002
2003
/////////////////////////////////////////////////////////////////////////////
2004
//
2005
// ScriptNodeDataSource public methods
2006
//
2007
/////////////////////////////////////////////////////////////////////////////
2008
2009
/**
2010
 * Creates a request callback that gets appended to the script URI. Implementers
2011
 * can customize this string to match their server's query syntax.
2012
 *
2013
 * @method generateRequestCallback
2014
 * @return {String} String fragment that gets appended to script URI that 
2015
 * specifies the callback function 
2016
 */
2017
generateRequestCallback : function(id) {
2018
    return "&" + this.scriptCallbackParam + "=YAHOO.util.ScriptNodeDataSource.callbacks["+id+"]" ;
2019
},
2020
2021
/**
2022
 * Overridable method gives implementers access to modify the URI before the dynamic
2023
 * script node gets inserted. Implementers should take care not to return an
2024
 * invalid URI.
2025
 *
2026
 * @method doBeforeGetScriptNode
2027
 * @param {String} URI to the script 
2028
 * @return {String} URI to the script
2029
 */
2030
doBeforeGetScriptNode : function(sUri) {
2031
    return sUri;
2032
},
2033
2034
/**
2035
 * Overriding method passes query to Get Utility. The returned
2036
 * response is then forwarded to the handleResponse function.
2037
 *
2038
 * @method makeConnection
2039
 * @param oRequest {Object} Request object.
2040
 * @param oCallback {Object} Callback object literal.
2041
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
2042
 * @return {Number} Transaction ID.
2043
 */
2044
makeConnection : function(oRequest, oCallback, oCaller) {
2045
    var tId = DS._nTransactionId++;
2046
    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
2047
    
2048
    // If there are no global pending requests, it is safe to purge global callback stack and global counter
2049
    if(util.ScriptNodeDataSource._nPending === 0) {
2050
        util.ScriptNodeDataSource.callbacks = [];
2051
        util.ScriptNodeDataSource._nId = 0;
2052
    }
2053
    
2054
    // ID for this request
2055
    var id = util.ScriptNodeDataSource._nId;
2056
    util.ScriptNodeDataSource._nId++;
2057
    
2058
    // Dynamically add handler function with a closure to the callback stack
2059
    var oSelf = this;
2060
    util.ScriptNodeDataSource.callbacks[id] = function(oRawResponse) {
2061
        if((oSelf.asyncMode !== "ignoreStaleResponses")||
2062
                (id === util.ScriptNodeDataSource.callbacks.length-1)) { // Must ignore stale responses
2063
                
2064
            // Try to sniff data type if it has not been defined
2065
            if(oSelf.responseType === DS.TYPE_UNKNOWN) {
2066
                if(YAHOO.lang.isArray(oRawResponse)) { // array
2067
                    oSelf.responseType = DS.TYPE_JSARRAY;
2068
                }
2069
                 // xml
2070
                else if(oRawResponse.nodeType && oRawResponse.nodeType == 9) {
2071
                    oSelf.responseType = DS.TYPE_XML;
2072
                }
2073
                else if(oRawResponse.nodeName && (oRawResponse.nodeName.toLowerCase() == "table")) { // table
2074
                    oSelf.responseType = DS.TYPE_HTMLTABLE;
2075
                }    
2076
                else if(YAHOO.lang.isObject(oRawResponse)) { // json
2077
                    oSelf.responseType = DS.TYPE_JSON;
2078
                }
2079
                else if(YAHOO.lang.isString(oRawResponse)) { // text
2080
                    oSelf.responseType = DS.TYPE_TEXT;
2081
                }
2082
            }
2083
2084
            oSelf.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
2085
        }
2086
        else {
2087
        }
2088
    
2089
        delete util.ScriptNodeDataSource.callbacks[id];
2090
    };
2091
    
2092
    // We are now creating a request
2093
    util.ScriptNodeDataSource._nPending++;
2094
    var sUri = this.liveData + oRequest + this.generateRequestCallback(id);
2095
    sUri = this.doBeforeGetScriptNode(sUri);
2096
    this.getUtility.script(sUri,
2097
            {autopurge: true,
2098
            onsuccess: util.ScriptNodeDataSource._bumpPendingDown,
2099
            onfail: util.ScriptNodeDataSource._bumpPendingDown});
2100
2101
    return tId;
2102
}
2103
2104
});
2105
2106
// Copy static members to ScriptNodeDataSource class
2107
lang.augmentObject(util.ScriptNodeDataSource, DS);
2108
2109
// Copy static members to ScriptNodeDataSource class
2110
lang.augmentObject(util.ScriptNodeDataSource,  {
2111
2112
/////////////////////////////////////////////////////////////////////////////
2113
//
2114
// ScriptNodeDataSource private static properties
2115
//
2116
/////////////////////////////////////////////////////////////////////////////
2117
2118
/**
2119
 * Unique ID to track requests.
2120
 *
2121
 * @property _nId
2122
 * @type Number
2123
 * @private
2124
 * @static
2125
 */
2126
_nId : 0,
2127
2128
/**
2129
 * Counter for pending requests. When this is 0, it is safe to purge callbacks
2130
 * array.
2131
 *
2132
 * @property _nPending
2133
 * @type Number
2134
 * @private
2135
 * @static
2136
 */
2137
_nPending : 0,
2138
2139
/**
2140
 * Global array of callback functions, one for each request sent.
2141
 *
2142
 * @property callbacks
2143
 * @type Function[]
2144
 * @static
2145
 */
2146
callbacks : []
2147
2148
});
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
/****************************************************************************/
2164
/****************************************************************************/
2165
/****************************************************************************/
2166
2167
/**
2168
 * XHRDataSource class for accessing remote data via the YUI Connection Manager
2169
 * Utility
2170
 *
2171
 * @namespace YAHOO.util
2172
 * @class YAHOO.util.XHRDataSource
2173
 * @extends YAHOO.util.DataSourceBase  
2174
 * @constructor
2175
 * @param oLiveData {HTMLElement}  Pointer to live data.
2176
 * @param oConfigs {object} (optional) Object literal of configuration values.
2177
 */
2178
util.XHRDataSource = function(oLiveData, oConfigs) {
2179
    this.dataType = DS.TYPE_XHR;
2180
    this.connMgr = this.connMgr || util.Connect;
2181
    oLiveData = oLiveData || "";
2182
    
2183
    util.XHRDataSource.superclass.constructor.call(this, oLiveData, oConfigs); 
2184
};
2185
2186
// XHRDataSource extends DataSourceBase
2187
lang.extend(util.XHRDataSource, DS, {
2188
2189
/////////////////////////////////////////////////////////////////////////////
2190
//
2191
// XHRDataSource public properties
2192
//
2193
/////////////////////////////////////////////////////////////////////////////
2194
2195
 /**
2196
 * Alias to YUI Connection Manager, to allow implementers to use a custom class.
2197
 *
2198
 * @property connMgr
2199
 * @type Object
2200
 * @default YAHOO.util.Connect
2201
 */
2202
connMgr: null,
2203
2204
 /**
2205
 * Defines request/response management in the following manner:
2206
 * <dl>
2207
 *     <dt>queueRequests</dt>
2208
 *     <dd>If a request is already in progress, wait until response is returned
2209
 *     before sending the next request.</dd>
2210
 *
2211
 *     <dt>cancelStaleRequests</dt>
2212
 *     <dd>If a request is already in progress, cancel it before sending the next
2213
 *     request.</dd>
2214
 *
2215
 *     <dt>ignoreStaleResponses</dt>
2216
 *     <dd>Send all requests, but handle only the response for the most recently
2217
 *     sent request.</dd>
2218
 *
2219
 *     <dt>allowAll</dt>
2220
 *     <dd>Send all requests and handle all responses.</dd>
2221
 *
2222
 * </dl>
2223
 *
2224
 * @property connXhrMode
2225
 * @type String
2226
 * @default "allowAll"
2227
 */
2228
connXhrMode: "allowAll",
2229
2230
 /**
2231
 * True if data is to be sent via POST. By default, data will be sent via GET.
2232
 *
2233
 * @property connMethodPost
2234
 * @type Boolean
2235
 * @default false
2236
 */
2237
connMethodPost: false,
2238
2239
 /**
2240
 * The connection timeout defines how many  milliseconds the XHR connection will
2241
 * wait for a server response. Any non-zero value will enable the Connection Manager's
2242
 * Auto-Abort feature.
2243
 *
2244
 * @property connTimeout
2245
 * @type Number
2246
 * @default 0
2247
 */
2248
connTimeout: 0,
2249
2250
/////////////////////////////////////////////////////////////////////////////
2251
//
2252
// XHRDataSource public methods
2253
//
2254
/////////////////////////////////////////////////////////////////////////////
2255
2256
/**
2257
 * Overriding method passes query to Connection Manager. The returned
2258
 * response is then forwarded to the handleResponse function.
2259
 *
2260
 * @method makeConnection
2261
 * @param oRequest {Object} Request object.
2262
 * @param oCallback {Object} Callback object literal.
2263
 * @param oCaller {Object} (deprecated) Use oCallback.scope.
2264
 * @return {Number} Transaction ID.
2265
 */
2266
makeConnection : function(oRequest, oCallback, oCaller) {
2267
2268
    var oRawResponse = null;
2269
    var tId = DS._nTransactionId++;
2270
    this.fireEvent("requestEvent", {tId:tId,request:oRequest,callback:oCallback,caller:oCaller});
2271
2272
    // Set up the callback object and
2273
    // pass the request in as a URL query and
2274
    // forward the response to the handler
2275
    var oSelf = this;
2276
    var oConnMgr = this.connMgr;
2277
    var oQueue = this._oQueue;
2278
2279
    /**
2280
     * Define Connection Manager success handler
2281
     *
2282
     * @method _xhrSuccess
2283
     * @param oResponse {Object} HTTPXMLRequest object
2284
     * @private
2285
     */
2286
    var _xhrSuccess = function(oResponse) {
2287
        // If response ID does not match last made request ID,
2288
        // silently fail and wait for the next response
2289
        if(oResponse && (this.connXhrMode == "ignoreStaleResponses") &&
2290
                (oResponse.tId != oQueue.conn.tId)) {
2291
            return null;
2292
        }
2293
        // Error if no response
2294
        else if(!oResponse) {
2295
            this.fireEvent("dataErrorEvent", {request:oRequest, response:null,
2296
                    callback:oCallback, caller:oCaller,
2297
                    message:DS.ERROR_DATANULL});
2298
2299
            // Send error response back to the caller with the error flag on
2300
            DS.issueCallback(oCallback,[oRequest, {error:true}], true, oCaller);
2301
2302
            return null;
2303
        }
2304
        // Forward to handler
2305
        else {
2306
            // Try to sniff data type if it has not been defined
2307
            if(this.responseType === DS.TYPE_UNKNOWN) {
2308
                var ctype = (oResponse.getResponseHeader) ? oResponse.getResponseHeader["Content-Type"] : null;
2309
                if(ctype) {
2310
                    // xml
2311
                    if(ctype.indexOf("text/xml") > -1) {
2312
                        this.responseType = DS.TYPE_XML;
2313
                    }
2314
                    else if(ctype.indexOf("application/json") > -1) { // json
2315
                        this.responseType = DS.TYPE_JSON;
2316
                    }
2317
                    else if(ctype.indexOf("text/plain") > -1) { // text
2318
                        this.responseType = DS.TYPE_TEXT;
2319
                    }
2320
                }
2321
            }
2322
            this.handleResponse(oRequest, oResponse, oCallback, oCaller, tId);
2323
        }
2324
    };
2325
2326
    /**
2327
     * Define Connection Manager failure handler
2328
     *
2329
     * @method _xhrFailure
2330
     * @param oResponse {Object} HTTPXMLRequest object
2331
     * @private
2332
     */
2333
    var _xhrFailure = function(oResponse) {
2334
        this.fireEvent("dataErrorEvent", {request:oRequest, response: oResponse,
2335
                callback:oCallback, caller:oCaller,
2336
                message:DS.ERROR_DATAINVALID});
2337
2338
        // Backward compatibility
2339
        if(lang.isString(this.liveData) && lang.isString(oRequest) &&
2340
            (this.liveData.lastIndexOf("?") !== this.liveData.length-1) &&
2341
            (oRequest.indexOf("?") !== 0)){
2342
        }
2343
2344
        // Send failure response back to the caller with the error flag on
2345
        oResponse = oResponse || {};
2346
        oResponse.error = true;
2347
        DS.issueCallback(oCallback,[oRequest,oResponse],true, oCaller);
2348
2349
        return null;
2350
    };
2351
2352
    /**
2353
     * Define Connection Manager callback object
2354
     *
2355
     * @property _xhrCallback
2356
     * @param oResponse {Object} HTTPXMLRequest object
2357
     * @private
2358
     */
2359
     var _xhrCallback = {
2360
        success:_xhrSuccess,
2361
        failure:_xhrFailure,
2362
        scope: this
2363
    };
2364
2365
    // Apply Connection Manager timeout
2366
    if(lang.isNumber(this.connTimeout)) {
2367
        _xhrCallback.timeout = this.connTimeout;
2368
    }
2369
2370
    // Cancel stale requests
2371
    if(this.connXhrMode == "cancelStaleRequests") {
2372
            // Look in queue for stale requests
2373
            if(oQueue.conn) {
2374
                if(oConnMgr.abort) {
2375
                    oConnMgr.abort(oQueue.conn);
2376
                    oQueue.conn = null;
2377
                }
2378
                else {
2379
                }
2380
            }
2381
    }
2382
2383
    // Get ready to send the request URL
2384
    if(oConnMgr && oConnMgr.asyncRequest) {
2385
        var sLiveData = this.liveData;
2386
        var isPost = this.connMethodPost;
2387
        var sMethod = (isPost) ? "POST" : "GET";
2388
        // Validate request
2389
        var sUri = (isPost || !lang.isValue(oRequest)) ? sLiveData : sLiveData+oRequest;
2390
        var sRequest = (isPost) ? oRequest : null;
2391
2392
        // Send the request right away
2393
        if(this.connXhrMode != "queueRequests") {
2394
            oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, _xhrCallback, sRequest);
2395
        }
2396
        // Queue up then send the request
2397
        else {
2398
            // Found a request already in progress
2399
            if(oQueue.conn) {
2400
                var allRequests = oQueue.requests;
2401
                // Add request to queue
2402
                allRequests.push({request:oRequest, callback:_xhrCallback});
2403
2404
                // Interval needs to be started
2405
                if(!oQueue.interval) {
2406
                    oQueue.interval = setInterval(function() {
2407
                        // Connection is in progress
2408
                        if(oConnMgr.isCallInProgress(oQueue.conn)) {
2409
                            return;
2410
                        }
2411
                        else {
2412
                            // Send next request
2413
                            if(allRequests.length > 0) {
2414
                                // Validate request
2415
                                sUri = (isPost || !lang.isValue(allRequests[0].request)) ? sLiveData : sLiveData+allRequests[0].request;
2416
                                sRequest = (isPost) ? allRequests[0].request : null;
2417
                                oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, allRequests[0].callback, sRequest);
2418
2419
                                // Remove request from queue
2420
                                allRequests.shift();
2421
                            }
2422
                            // No more requests
2423
                            else {
2424
                                clearInterval(oQueue.interval);
2425
                                oQueue.interval = null;
2426
                            }
2427
                        }
2428
                    }, 50);
2429
                }
2430
            }
2431
            // Nothing is in progress
2432
            else {
2433
                oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, _xhrCallback, sRequest);
2434
            }
2435
        }
2436
    }
2437
    else {
2438
        // Send null response back to the caller with the error flag on
2439
        DS.issueCallback(oCallback,[oRequest,{error:true}],true,oCaller);
2440
    }
2441
2442
    return tId;
2443
}
2444
2445
});
2446
2447
// Copy static members to XHRDataSource class
2448
lang.augmentObject(util.XHRDataSource, DS);
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
/****************************************************************************/
2463
/****************************************************************************/
2464
/****************************************************************************/
2465
2466
/**
2467
 * Factory class for creating a BaseDataSource subclass instance. The sublcass is
2468
 * determined by oLiveData's type, unless the dataType config is explicitly passed in.  
2469
 *
2470
 * @namespace YAHOO.util
2471
 * @class YAHOO.util.DataSource
2472
 * @constructor
2473
 * @param oLiveData {HTMLElement}  Pointer to live data.
2474
 * @param oConfigs {object} (optional) Object literal of configuration values.
2475
 */
2476
util.DataSource = function(oLiveData, oConfigs) {
2477
    oConfigs = oConfigs || {};
2478
    
2479
    // Point to one of the subclasses, first by dataType if given, then by sniffing oLiveData type.
2480
    var dataType = oConfigs.dataType;
2481
    if(dataType) {
2482
        if(dataType == DS.TYPE_LOCAL) {
2483
            lang.augmentObject(util.DataSource, util.LocalDataSource);
2484
            return new util.LocalDataSource(oLiveData, oConfigs);            
2485
        }
2486
        else if(dataType == DS.TYPE_XHR) {
2487
            lang.augmentObject(util.DataSource, util.XHRDataSource);
2488
            return new util.XHRDataSource(oLiveData, oConfigs);            
2489
        }
2490
        else if(dataType == DS.TYPE_SCRIPTNODE) {
2491
            lang.augmentObject(util.DataSource, util.ScriptNodeDataSource);
2492
            return new util.ScriptNodeDataSource(oLiveData, oConfigs);            
2493
        }
2494
        else if(dataType == DS.TYPE_JSFUNCTION) {
2495
            lang.augmentObject(util.DataSource, util.FunctionDataSource);
2496
            return new util.FunctionDataSource(oLiveData, oConfigs);            
2497
        }
2498
    }
2499
    
2500
    if(YAHOO.lang.isString(oLiveData)) { // strings default to xhr
2501
        lang.augmentObject(util.DataSource, util.XHRDataSource);
2502
        return new util.XHRDataSource(oLiveData, oConfigs);
2503
    }
2504
    else if(YAHOO.lang.isFunction(oLiveData)) {
2505
        lang.augmentObject(util.DataSource, util.FunctionDataSource);
2506
        return new util.FunctionDataSource(oLiveData, oConfigs);
2507
    }
2508
    else { // ultimate default is local
2509
        lang.augmentObject(util.DataSource, util.LocalDataSource);
2510
        return new util.LocalDataSource(oLiveData, oConfigs);
2511
    }
2512
};
2513
2514
// Copy static members to DataSource class
2515
lang.augmentObject(util.DataSource, DS);
2516
2517
})();
2518
2519
/****************************************************************************/
2520
/****************************************************************************/
2521
/****************************************************************************/
2522
2523
/**
2524
 * The static Number class provides helper functions to deal with data of type
2525
 * Number.
2526
 *
2527
 * @namespace YAHOO.util
2528
 * @requires yahoo
2529
 * @class Number
2530
 * @static
2531
 */
2532
 YAHOO.util.Number = {
2533
 
2534
     /**
2535
     * Takes a native JavaScript Number and formats to string for display to user.
2536
     *
2537
     * @method format
2538
     * @param nData {Number} Number.
2539
     * @param oConfig {Object} (Optional) Optional configuration values:
2540
     *  <dl>
2541
     *   <dt>prefix {String}</dd>
2542
     *   <dd>String prepended before each number, like a currency designator "$"</dd>
2543
     *   <dt>decimalPlaces {Number}</dd>
2544
     *   <dd>Number of decimal places to round.</dd>
2545
     *   <dt>decimalSeparator {String}</dd>
2546
     *   <dd>Decimal separator</dd>
2547
     *   <dt>thousandsSeparator {String}</dd>
2548
     *   <dd>Thousands separator</dd>
2549
     *   <dt>suffix {String}</dd>
2550
     *   <dd>String appended after each number, like " items" (note the space)</dd>
2551
     *   <dt>negativeFormat</dt>
2552
     *   <dd>String used as a guide for how to indicate negative numbers.  The first '#' character in the string will be replaced by the number.  Default '-#'.</dd>
2553
     *  </dl>
2554
     * @return {String} Formatted number for display. Note, the following values
2555
     * return as "": null, undefined, NaN, "".
2556
     */
2557
    format : function(n, cfg) {
2558
        if (!isFinite(+n)) {
2559
            return '';
2560
        }
2561
2562
        n   = !isFinite(+n) ? 0 : +n;
2563
        cfg = YAHOO.lang.merge(YAHOO.util.Number.format.defaults, (cfg || {}));
2564
2565
        var neg    = n < 0,        absN   = Math.abs(n),
2566
            places = cfg.decimalPlaces,
2567
            sep    = cfg.thousandsSeparator,
2568
            s, bits, i;
2569
2570
        if (places < 0) {
2571
            // Get rid of the decimal info
2572
            s = absN - (absN % 1) + '';
2573
            i = s.length + places;
2574
2575
            // avoid 123 vs decimalPlaces -4 (should return "0")
2576
            if (i > 0) {
2577
                    // leverage toFixed by making 123 => 0.123 for the rounding
2578
                    // operation, then add the appropriate number of zeros back on
2579
                s = Number('.' + s).toFixed(i).slice(2) +
2580
                    new Array(s.length - i + 1).join('0');
2581
            } else {
2582
                s = "0";
2583
            }
2584
        } else {        // There is a bug in IE's toFixed implementation:
2585
            // for n in {(-0.94, -0.5], [0.5, 0.94)} n.toFixed() returns 0
2586
            // instead of -1 and 1. Manually handle that case.
2587
            s = absN < 1 && absN >= 0.5 && !places ? '1' : absN.toFixed(places);
2588
        }
2589
2590
        if (absN > 1000) {
2591
            bits  = s.split(/\D/);
2592
            i  = bits[0].length % 3 || 3;
2593
2594
            bits[0] = bits[0].slice(0,i) +
2595
                      bits[0].slice(i).replace(/(\d{3})/g, sep + '$1');
2596
2597
            s = bits.join(cfg.decimalSeparator);
2598
        }
2599
2600
        s = cfg.prefix + s + cfg.suffix;
2601
2602
        return neg ? cfg.negativeFormat.replace(/#/,s) : s;
2603
    }
2604
};
2605
YAHOO.util.Number.format.defaults = {
2606
    decimalSeparator : '.',
2607
    decimalPlaces    : null,
2608
    thousandsSeparator : '',
2609
    prefix : '',
2610
    suffix : '',
2611
    negativeFormat : '-#'
2612
};
2613
2614
2615
/****************************************************************************/
2616
/****************************************************************************/
2617
/****************************************************************************/
2618
2619
(function () {
2620
2621
var xPad=function (x, pad, r)
2622
{
2623
    if(typeof r === 'undefined')
2624
    {
2625
        r=10;
2626
    }
2627
    for( ; parseInt(x, 10)<r && r>1; r/=10) {
2628
        x = pad.toString() + x;
2629
    }
2630
    return x.toString();
2631
};
2632
2633
2634
/**
2635
 * The static Date class provides helper functions to deal with data of type Date.
2636
 *
2637
 * @namespace YAHOO.util
2638
 * @requires yahoo
2639
 * @class Date
2640
 * @static
2641
 */
2642
 var Dt = {
2643
    formats: {
2644
        a: function (d, l) { return l.a[d.getDay()]; },
2645
        A: function (d, l) { return l.A[d.getDay()]; },
2646
        b: function (d, l) { return l.b[d.getMonth()]; },
2647
        B: function (d, l) { return l.B[d.getMonth()]; },
2648
        C: function (d) { return xPad(parseInt(d.getFullYear()/100, 10), 0); },
2649
        d: ['getDate', '0'],
2650
        e: ['getDate', ' '],
2651
        g: function (d) { return xPad(parseInt(Dt.formats.G(d)%100, 10), 0); },
2652
        G: function (d) {
2653
                var y = d.getFullYear();
2654
                var V = parseInt(Dt.formats.V(d), 10);
2655
                var W = parseInt(Dt.formats.W(d), 10);
2656
    
2657
                if(W > V) {
2658
                    y++;
2659
                } else if(W===0 && V>=52) {
2660
                    y--;
2661
                }
2662
    
2663
                return y;
2664
            },
2665
        H: ['getHours', '0'],
2666
        I: function (d) { var I=d.getHours()%12; return xPad(I===0?12:I, 0); },
2667
        j: function (d) {
2668
                var gmd_1 = new Date('' + d.getFullYear() + '/1/1 GMT');
2669
                var gmdate = new Date('' + d.getFullYear() + '/' + (d.getMonth()+1) + '/' + d.getDate() + ' GMT');
2670
                var ms = gmdate - gmd_1;
2671
                var doy = parseInt(ms/60000/60/24, 10)+1;
2672
                return xPad(doy, 0, 100);
2673
            },
2674
        k: ['getHours', ' '],
2675
        l: function (d) { var I=d.getHours()%12; return xPad(I===0?12:I, ' '); },
2676
        m: function (d) { return xPad(d.getMonth()+1, 0); },
2677
        M: ['getMinutes', '0'],
2678
        p: function (d, l) { return l.p[d.getHours() >= 12 ? 1 : 0 ]; },
2679
        P: function (d, l) { return l.P[d.getHours() >= 12 ? 1 : 0 ]; },
2680
        s: function (d, l) { return parseInt(d.getTime()/1000, 10); },
2681
        S: ['getSeconds', '0'],
2682
        u: function (d) { var dow = d.getDay(); return dow===0?7:dow; },
2683
        U: function (d) {
2684
                var doy = parseInt(Dt.formats.j(d), 10);
2685
                var rdow = 6-d.getDay();
2686
                var woy = parseInt((doy+rdow)/7, 10);
2687
                return xPad(woy, 0);
2688
            },
2689
        V: function (d) {
2690
                var woy = parseInt(Dt.formats.W(d), 10);
2691
                var dow1_1 = (new Date('' + d.getFullYear() + '/1/1')).getDay();
2692
                // First week is 01 and not 00 as in the case of %U and %W,
2693
                // so we add 1 to the final result except if day 1 of the year
2694
                // is a Monday (then %W returns 01).
2695
                // We also need to subtract 1 if the day 1 of the year is 
2696
                // Friday-Sunday, so the resulting equation becomes:
2697
                var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1);
2698
                if(idow === 53 && (new Date('' + d.getFullYear() + '/12/31')).getDay() < 4)
2699
                {
2700
                    idow = 1;
2701
                }
2702
                else if(idow === 0)
2703
                {
2704
                    idow = Dt.formats.V(new Date('' + (d.getFullYear()-1) + '/12/31'));
2705
                }
2706
    
2707
                return xPad(idow, 0);
2708
            },
2709
        w: 'getDay',
2710
        W: function (d) {
2711
                var doy = parseInt(Dt.formats.j(d), 10);
2712
                var rdow = 7-Dt.formats.u(d);
2713
                var woy = parseInt((doy+rdow)/7, 10);
2714
                return xPad(woy, 0, 10);
2715
            },
2716
        y: function (d) { return xPad(d.getFullYear()%100, 0); },
2717
        Y: 'getFullYear',
2718
        z: function (d) {
2719
                var o = d.getTimezoneOffset();
2720
                var H = xPad(parseInt(Math.abs(o/60), 10), 0);
2721
                var M = xPad(Math.abs(o%60), 0);
2722
                return (o>0?'-':'+') + H + M;
2723
            },
2724
        Z: function (d) {
2725
		var tz = d.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/, '$2').replace(/[a-z ]/g, '');
2726
		if(tz.length > 4) {
2727
			tz = Dt.formats.z(d);
2728
		}
2729
		return tz;
2730
	},
2731
        '%': function (d) { return '%'; }
2732
    },
2733
2734
    aggregates: {
2735
        c: 'locale',
2736
        D: '%m/%d/%y',
2737
        F: '%Y-%m-%d',
2738
        h: '%b',
2739
        n: '\n',
2740
        r: 'locale',
2741
        R: '%H:%M',
2742
        t: '\t',
2743
        T: '%H:%M:%S',
2744
        x: 'locale',
2745
        X: 'locale'
2746
        //'+': '%a %b %e %T %Z %Y'
2747
    },
2748
2749
     /**
2750
     * Takes a native JavaScript Date and formats to string for display to user.
2751
     *
2752
     * @method format
2753
     * @param oDate {Date} Date.
2754
     * @param oConfig {Object} (Optional) Object literal of configuration values:
2755
     *  <dl>
2756
     *   <dt>format &lt;String&gt;</dt>
2757
     *   <dd>
2758
     *   <p>
2759
     *   Any strftime string is supported, such as "%I:%M:%S %p". strftime has several format specifiers defined by the Open group at 
2760
     *   <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html">http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html</a>
2761
     *   </p>
2762
     *   <p>   
2763
     *   PHP added a few of its own, defined at <a href="http://www.php.net/strftime">http://www.php.net/strftime</a>
2764
     *   </p>
2765
     *   <p>
2766
     *   This javascript implementation supports all the PHP specifiers and a few more.  The full list is below:
2767
     *   </p>
2768
     *   <dl>
2769
     *    <dt>%a</dt> <dd>abbreviated weekday name according to the current locale</dd>
2770
     *    <dt>%A</dt> <dd>full weekday name according to the current locale</dd>
2771
     *    <dt>%b</dt> <dd>abbreviated month name according to the current locale</dd>
2772
     *    <dt>%B</dt> <dd>full month name according to the current locale</dd>
2773
     *    <dt>%c</dt> <dd>preferred date and time representation for the current locale</dd>
2774
     *    <dt>%C</dt> <dd>century number (the year divided by 100 and truncated to an integer, range 00 to 99)</dd>
2775
     *    <dt>%d</dt> <dd>day of the month as a decimal number (range 01 to 31)</dd>
2776
     *    <dt>%D</dt> <dd>same as %m/%d/%y</dd>
2777
     *    <dt>%e</dt> <dd>day of the month as a decimal number, a single digit is preceded by a space (range ' 1' to '31')</dd>
2778
     *    <dt>%F</dt> <dd>same as %Y-%m-%d (ISO 8601 date format)</dd>
2779
     *    <dt>%g</dt> <dd>like %G, but without the century</dd>
2780
     *    <dt>%G</dt> <dd>The 4-digit year corresponding to the ISO week number</dd>
2781
     *    <dt>%h</dt> <dd>same as %b</dd>
2782
     *    <dt>%H</dt> <dd>hour as a decimal number using a 24-hour clock (range 00 to 23)</dd>
2783
     *    <dt>%I</dt> <dd>hour as a decimal number using a 12-hour clock (range 01 to 12)</dd>
2784
     *    <dt>%j</dt> <dd>day of the year as a decimal number (range 001 to 366)</dd>
2785
     *    <dt>%k</dt> <dd>hour as a decimal number using a 24-hour clock (range 0 to 23); single digits are preceded by a blank. (See also %H.)</dd>
2786
     *    <dt>%l</dt> <dd>hour as a decimal number using a 12-hour clock (range 1 to 12); single digits are preceded by a blank. (See also %I.) </dd>
2787
     *    <dt>%m</dt> <dd>month as a decimal number (range 01 to 12)</dd>
2788
     *    <dt>%M</dt> <dd>minute as a decimal number</dd>
2789
     *    <dt>%n</dt> <dd>newline character</dd>
2790
     *    <dt>%p</dt> <dd>either `AM' or `PM' according to the given time value, or the corresponding strings for the current locale</dd>
2791
     *    <dt>%P</dt> <dd>like %p, but lower case</dd>
2792
     *    <dt>%r</dt> <dd>time in a.m. and p.m. notation equal to %I:%M:%S %p</dd>
2793
     *    <dt>%R</dt> <dd>time in 24 hour notation equal to %H:%M</dd>
2794
     *    <dt>%s</dt> <dd>number of seconds since the Epoch, ie, since 1970-01-01 00:00:00 UTC</dd>
2795
     *    <dt>%S</dt> <dd>second as a decimal number</dd>
2796
     *    <dt>%t</dt> <dd>tab character</dd>
2797
     *    <dt>%T</dt> <dd>current time, equal to %H:%M:%S</dd>
2798
     *    <dt>%u</dt> <dd>weekday as a decimal number [1,7], with 1 representing Monday</dd>
2799
     *    <dt>%U</dt> <dd>week number of the current year as a decimal number, starting with the
2800
     *            first Sunday as the first day of the first week</dd>
2801
     *    <dt>%V</dt> <dd>The ISO 8601:1988 week number of the current year as a decimal number,
2802
     *            range 01 to 53, where week 1 is the first week that has at least 4 days
2803
     *            in the current year, and with Monday as the first day of the week.</dd>
2804
     *    <dt>%w</dt> <dd>day of the week as a decimal, Sunday being 0</dd>
2805
     *    <dt>%W</dt> <dd>week number of the current year as a decimal number, starting with the
2806
     *            first Monday as the first day of the first week</dd>
2807
     *    <dt>%x</dt> <dd>preferred date representation for the current locale without the time</dd>
2808
     *    <dt>%X</dt> <dd>preferred time representation for the current locale without the date</dd>
2809
     *    <dt>%y</dt> <dd>year as a decimal number without a century (range 00 to 99)</dd>
2810
     *    <dt>%Y</dt> <dd>year as a decimal number including the century</dd>
2811
     *    <dt>%z</dt> <dd>numerical time zone representation</dd>
2812
     *    <dt>%Z</dt> <dd>time zone name or abbreviation</dd>
2813
     *    <dt>%%</dt> <dd>a literal `%' character</dd>
2814
     *   </dl>
2815
     *  </dd>
2816
     * </dl>
2817
     * @param sLocale {String} (Optional) The locale to use when displaying days of week,
2818
     *  months of the year, and other locale specific strings.  The following locales are
2819
     *  built in:
2820
     *  <dl>
2821
     *   <dt>en</dt>
2822
     *   <dd>English</dd>
2823
     *   <dt>en-US</dt>
2824
     *   <dd>US English</dd>
2825
     *   <dt>en-GB</dt>
2826
     *   <dd>British English</dd>
2827
     *   <dt>en-AU</dt>
2828
     *   <dd>Australian English (identical to British English)</dd>
2829
     *  </dl>
2830
     *  More locales may be added by subclassing of YAHOO.util.DateLocale.
2831
     *  See YAHOO.util.DateLocale for more information.
2832
     * @return {String} Formatted date for display.
2833
     * @sa YAHOO.util.DateLocale
2834
     */
2835
    format : function (oDate, oConfig, sLocale) {
2836
        oConfig = oConfig || {};
2837
        
2838
        if(!(oDate instanceof Date)) {
2839
            return YAHOO.lang.isValue(oDate) ? oDate : "";
2840
        }
2841
2842
        var format = oConfig.format || "%m/%d/%Y";
2843
2844
        // Be backwards compatible, support strings that are
2845
        // exactly equal to YYYY/MM/DD, DD/MM/YYYY and MM/DD/YYYY
2846
        if(format === 'YYYY/MM/DD') {
2847
            format = '%Y/%m/%d';
2848
        } else if(format === 'DD/MM/YYYY') {
2849
            format = '%d/%m/%Y';
2850
        } else if(format === 'MM/DD/YYYY') {
2851
            format = '%m/%d/%Y';
2852
        }
2853
        // end backwards compatibility block
2854
 
2855
        sLocale = sLocale || "en";
2856
2857
        // Make sure we have a definition for the requested locale, or default to en.
2858
        if(!(sLocale in YAHOO.util.DateLocale)) {
2859
            if(sLocale.replace(/-[a-zA-Z]+$/, '') in YAHOO.util.DateLocale) {
2860
                sLocale = sLocale.replace(/-[a-zA-Z]+$/, '');
2861
            } else {
2862
                sLocale = "en";
2863
            }
2864
        }
2865
2866
        var aLocale = YAHOO.util.DateLocale[sLocale];
2867
2868
        var replace_aggs = function (m0, m1) {
2869
            var f = Dt.aggregates[m1];
2870
            return (f === 'locale' ? aLocale[m1] : f);
2871
        };
2872
2873
        var replace_formats = function (m0, m1) {
2874
            var f = Dt.formats[m1];
2875
            if(typeof f === 'string') {             // string => built in date function
2876
                return oDate[f]();
2877
            } else if(typeof f === 'function') {    // function => our own function
2878
                return f.call(oDate, oDate, aLocale);
2879
            } else if(typeof f === 'object' && typeof f[0] === 'string') {  // built in function with padding
2880
                return xPad(oDate[f[0]](), f[1]);
2881
            } else {
2882
                return m1;
2883
            }
2884
        };
2885
2886
        // First replace aggregates (run in a loop because an agg may be made up of other aggs)
2887
        while(format.match(/%[cDFhnrRtTxX]/)) {
2888
            format = format.replace(/%([cDFhnrRtTxX])/g, replace_aggs);
2889
        }
2890
2891
        // Now replace formats (do not run in a loop otherwise %%a will be replace with the value of %a)
2892
        var str = format.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g, replace_formats);
2893
2894
        replace_aggs = replace_formats = undefined;
2895
2896
        return str;
2897
    }
2898
 };
2899
 
2900
 YAHOO.namespace("YAHOO.util");
2901
 YAHOO.util.Date = Dt;
2902
2903
/**
2904
 * The DateLocale class is a container and base class for all
2905
 * localised date strings used by YAHOO.util.Date. It is used
2906
 * internally, but may be extended to provide new date localisations.
2907
 *
2908
 * To create your own DateLocale, follow these steps:
2909
 * <ol>
2910
 *  <li>Find an existing locale that matches closely with your needs</li>
2911
 *  <li>Use this as your base class.  Use YAHOO.util.DateLocale if nothing
2912
 *   matches.</li>
2913
 *  <li>Create your own class as an extension of the base class using
2914
 *   YAHOO.lang.merge, and add your own localisations where needed.</li>
2915
 * </ol>
2916
 * See the YAHOO.util.DateLocale['en-US'] and YAHOO.util.DateLocale['en-GB']
2917
 * classes which extend YAHOO.util.DateLocale['en'].
2918
 *
2919
 * For example, to implement locales for French french and Canadian french,
2920
 * we would do the following:
2921
 * <ol>
2922
 *  <li>For French french, we have no existing similar locale, so use
2923
 *   YAHOO.util.DateLocale as the base, and extend it:
2924
 *   <pre>
2925
 *      YAHOO.util.DateLocale['fr'] = YAHOO.lang.merge(YAHOO.util.DateLocale, {
2926
 *          a: ['dim', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam'],
2927
 *          A: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
2928
 *          b: ['jan', 'f&eacute;v', 'mar', 'avr', 'mai', 'jun', 'jui', 'ao&ucirc;', 'sep', 'oct', 'nov', 'd&eacute;c'],
2929
 *          B: ['janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre'],
2930
 *          c: '%a %d %b %Y %T %Z',
2931
 *          p: ['', ''],
2932
 *          P: ['', ''],
2933
 *          x: '%d.%m.%Y',
2934
 *          X: '%T'
2935
 *      });
2936
 *   </pre>
2937
 *  </li>
2938
 *  <li>For Canadian french, we start with French french and change the meaning of \%x:
2939
 *   <pre>
2940
 *      YAHOO.util.DateLocale['fr-CA'] = YAHOO.lang.merge(YAHOO.util.DateLocale['fr'], {
2941
 *          x: '%Y-%m-%d'
2942
 *      });
2943
 *   </pre>
2944
 *  </li>
2945
 * </ol>
2946
 *
2947
 * With that, you can use your new locales:
2948
 * <pre>
2949
 *    var d = new Date("2008/04/22");
2950
 *    YAHOO.util.Date.format(d, {format: "%A, %d %B == %x"}, "fr");
2951
 * </pre>
2952
 * will return:
2953
 * <pre>
2954
 *    mardi, 22 avril == 22.04.2008
2955
 * </pre>
2956
 * And
2957
 * <pre>
2958
 *    YAHOO.util.Date.format(d, {format: "%A, %d %B == %x"}, "fr-CA");
2959
 * </pre>
2960
 * Will return:
2961
 * <pre>
2962
 *   mardi, 22 avril == 2008-04-22
2963
 * </pre>
2964
 * @namespace YAHOO.util
2965
 * @requires yahoo
2966
 * @class DateLocale
2967
 */
2968
 YAHOO.util.DateLocale = {
2969
        a: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
2970
        A: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
2971
        b: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
2972
        B: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
2973
        c: '%a %d %b %Y %T %Z',
2974
        p: ['AM', 'PM'],
2975
        P: ['am', 'pm'],
2976
        r: '%I:%M:%S %p',
2977
        x: '%d/%m/%y',
2978
        X: '%T'
2979
 };
2980
2981
 YAHOO.util.DateLocale['en'] = YAHOO.lang.merge(YAHOO.util.DateLocale, {});
2982
2983
 YAHOO.util.DateLocale['en-US'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en'], {
2984
        c: '%a %d %b %Y %I:%M:%S %p %Z',
2985
        x: '%m/%d/%Y',
2986
        X: '%I:%M:%S %p'
2987
 });
2988
2989
 YAHOO.util.DateLocale['en-GB'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en'], {
2990
        r: '%l:%M:%S %P %Z'
2991
 });
2992
 YAHOO.util.DateLocale['en-AU'] = YAHOO.lang.merge(YAHOO.util.DateLocale['en']);
2993
2994
})();
2995
2996
YAHOO.register("datasource", YAHOO.util.DataSource, {version: "2.8.0r4", build: "2449"});
2997
- 

Return to bug 7747