]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/datasource/datasource.js
Release 6.2.0beta4
[Github/sugarcrm.git] / include / javascript / yui / build / datasource / datasource.js
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"});