MDL-11082 Improved groups upgrade performance 1.8x -> 1.9; thanks Eloy for telling...
[moodle-pu.git] / lib / yui / datasource / datasource-beta.js
blob1061f34310c3dd39d6b9d060564ce9899590661f
1 /*
2 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.3.0
6 */
7 /**
8 * The DataSource utility provides a common configurable interface for widgets
9 * to access a variety of data, from JavaScript arrays to online servers over
10 * XHR.
12 * @namespace YAHOO.util
13 * @module datasource
14 * @requires yahoo, event
15 * @optional connection
16 * @title DataSource Utility
17 * @beta
20 /****************************************************************************/
21 /****************************************************************************/
22 /****************************************************************************/
24 /**
25 * The DataSource class defines and manages a live set of data for widgets to
26 * interact with. Examples of live databases include in-memory
27 * local data such as a JavaScript array, a JavaScript function, or JSON, or
28 * remote data such as data retrieved through an XHR connection.
30 * @class DataSource
31 * @uses YAHOO.util.EventProvider
32 * @constructor
33 * @param oLiveData {Object} Pointer to live database.
34 * @param oConfigs {Object} (optional) Object literal of configuration values.
36 YAHOO.util.DataSource = function(oLiveData, oConfigs) {
37 // Set any config params passed in to override defaults
38 if(oConfigs && (oConfigs.constructor == Object)) {
39 for(var sConfig in oConfigs) {
40 if(sConfig) {
41 this[sConfig] = oConfigs[sConfig];
46 if(!oLiveData) {
47 return;
50 if(oLiveData.nodeType && oLiveData.nodeType == 9) {
51 this.dataType = YAHOO.util.DataSource.TYPE_XML;
53 else if(YAHOO.lang.isArray(oLiveData)) {
54 this.dataType = YAHOO.util.DataSource.TYPE_JSARRAY;
56 else if(YAHOO.lang.isString(oLiveData)) {
57 this.dataType = YAHOO.util.DataSource.TYPE_XHR;
59 else if(YAHOO.lang.isFunction(oLiveData)) {
60 this.dataType = YAHOO.util.DataSource.TYPE_JSFUNCTION;
62 else if(oLiveData.nodeName && (oLiveData.nodeName.toLowerCase() == "table")) {
63 this.dataType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
65 else if(YAHOO.lang.isObject(oLiveData)) {
66 this.dataType = YAHOO.util.DataSource.TYPE_JSON;
68 else {
69 this.dataType = YAHOO.util.DataSource.TYPE_UNKNOWN;
72 this.liveData = oLiveData;
73 this._oQueue = {interval:null, conn:null, requests:[]};
76 // Validate and initialize public configs
77 var maxCacheEntries = this.maxCacheEntries;
78 if(!YAHOO.lang.isNumber(maxCacheEntries) || (maxCacheEntries < 0)) {
79 maxCacheEntries = 0;
82 // Initialize local cache
83 if(maxCacheEntries > 0 && !this._aCache) {
84 this._aCache = [];
87 this._sName = "DataSource instance" + YAHOO.util.DataSource._nIndex;
88 YAHOO.util.DataSource._nIndex++;
91 /////////////////////////////////////////////////////////////////////////////
93 // Custom Events
95 /////////////////////////////////////////////////////////////////////////////
97 /**
98 * Fired when a request is made to the local cache.
100 * @event cacheRequestEvent
101 * @param oArgs.request {Object} The request object.
102 * @param oArgs.callback {Function} The callback function.
103 * @param oArgs.caller {Object} The parent object of the callback function.
105 this.createEvent("cacheRequestEvent");
108 * Fired when data is retrieved from the local cache.
110 * @event getCachedResponseEvent
111 * @param oArgs.request {Object} The request object.
112 * @param oArgs.response {Object} The response object.
113 * @param oArgs.callback {Function} The callback function.
114 * @param oArgs.caller {Object} The parent object of the callback function.
115 * @param oArgs.tId {Number} Transaction ID.
117 this.createEvent("cacheResponseEvent");
120 * Fired when a request is sent to the live data source.
122 * @event requestEvent
123 * @param oArgs.request {Object} The request object.
124 * @param oArgs.callback {Function} The callback function.
125 * @param oArgs.caller {Object} The parent object of the callback function.
127 this.createEvent("requestEvent");
130 * Fired when live data source sends response.
132 * @event responseEvent
133 * @param oArgs.request {Object} The request object.
134 * @param oArgs.response {Object} The raw response object.
135 * @param oArgs.callback {Function} The callback function.
136 * @param oArgs.caller {Object} The parent object of the callback function.
138 this.createEvent("responseEvent");
141 * Fired when response is parsed.
143 * @event responseParseEvent
144 * @param oArgs.request {Object} The request object.
145 * @param oArgs.response {Object} The parsed response object.
146 * @param oArgs.callback {Function} The callback function.
147 * @param oArgs.caller {Object} The parent object of the callback function.
149 this.createEvent("responseParseEvent");
152 * Fired when response is cached.
154 * @event responseCacheEvent
155 * @param oArgs.request {Object} The request object.
156 * @param oArgs.response {Object} The parsed response object.
157 * @param oArgs.callback {Function} The callback function.
158 * @param oArgs.caller {Object} The parent object of the callback function.
160 this.createEvent("responseCacheEvent");
162 * Fired when an error is encountered with the live data source.
164 * @event dataErrorEvent
165 * @param oArgs.request {Object} The request object.
166 * @param oArgs.callback {Function} The callback function.
167 * @param oArgs.caller {Object} The parent object of the callback function.
168 * @param oArgs.message {String} The error message.
170 this.createEvent("dataErrorEvent");
173 * Fired when the local cache is flushed.
175 * @event cacheFlushEvent
177 this.createEvent("cacheFlushEvent");
180 YAHOO.augment(YAHOO.util.DataSource, YAHOO.util.EventProvider);
182 /////////////////////////////////////////////////////////////////////////////
184 // Public constants
186 /////////////////////////////////////////////////////////////////////////////
189 * Type is unknown.
191 * @property TYPE_UNKNOWN
192 * @type Number
193 * @final
194 * @default -1
196 YAHOO.util.DataSource.TYPE_UNKNOWN = -1;
199 * Type is a JavaScript Array.
201 * @property TYPE_JSARRAY
202 * @type Number
203 * @final
204 * @default 0
206 YAHOO.util.DataSource.TYPE_JSARRAY = 0;
209 * Type is a JavaScript Function.
211 * @property TYPE_JSFUNCTION
212 * @type Number
213 * @final
214 * @default 1
216 YAHOO.util.DataSource.TYPE_JSFUNCTION = 1;
219 * Type is hosted on a server via an XHR connection.
221 * @property TYPE_XHR
222 * @type Number
223 * @final
224 * @default 2
226 YAHOO.util.DataSource.TYPE_XHR = 2;
229 * Type is JSON.
231 * @property TYPE_JSON
232 * @type Number
233 * @final
234 * @default 3
236 YAHOO.util.DataSource.TYPE_JSON = 3;
239 * Type is XML.
241 * @property TYPE_XML
242 * @type Number
243 * @final
244 * @default 4
246 YAHOO.util.DataSource.TYPE_XML = 4;
249 * Type is plain text.
251 * @property TYPE_TEXT
252 * @type Number
253 * @final
254 * @default 5
256 YAHOO.util.DataSource.TYPE_TEXT = 5;
259 * Type is an HTML TABLE element.
261 * @property TYPE_HTMLTABLE
262 * @type Number
263 * @final
264 * @default 6
266 YAHOO.util.DataSource.TYPE_HTMLTABLE = 6;
269 * Error message for invalid dataresponses.
271 * @property ERROR_DATAINVALID
272 * @type String
273 * @final
274 * @default "Invalid data"
276 YAHOO.util.DataSource.ERROR_DATAINVALID = "Invalid data";
279 * Error message for null data responses.
281 * @property ERROR_DATANULL
282 * @type String
283 * @final
284 * @default "Null data"
286 YAHOO.util.DataSource.ERROR_DATANULL = "Null data";
290 /////////////////////////////////////////////////////////////////////////////
292 // Private variables
294 /////////////////////////////////////////////////////////////////////////////
297 * Internal class variable to index multiple DataSource instances.
299 * @property DataSource._nIndex
300 * @type Number
301 * @private
302 * @static
304 YAHOO.util.DataSource._nIndex = 0;
307 * Internal class variable to assign unique transaction IDs.
309 * @property DataSource._nTransactionId
310 * @type Number
311 * @private
312 * @static
314 YAHOO.util.DataSource._nTransactionId = 0;
317 * Name of DataSource instance.
319 * @property _sName
320 * @type String
321 * @private
323 YAHOO.util.DataSource.prototype._sName = null;
326 * Local cache of data result object literals indexed chronologically.
328 * @property _aCache
329 * @type Object[]
330 * @private
332 YAHOO.util.DataSource.prototype._aCache = null;
335 * Local queue of request connections, enabled if queue needs to be managed.
337 * @property _oQueue
338 * @type Object
339 * @private
341 YAHOO.util.DataSource.prototype._oQueue = null;
343 /////////////////////////////////////////////////////////////////////////////
345 // Private methods
347 /////////////////////////////////////////////////////////////////////////////
351 /////////////////////////////////////////////////////////////////////////////
353 // Public member variables
355 /////////////////////////////////////////////////////////////////////////////
358 * Max size of the local cache. Set to 0 to turn off caching. Caching is
359 * useful to reduce the number of server connections. Recommended only for data
360 * sources that return comprehensive results for queries or when stale data is
361 * not an issue.
363 * @property maxCacheEntries
364 * @type Number
365 * @default 0
367 YAHOO.util.DataSource.prototype.maxCacheEntries = 0;
370 * Pointer to live database.
372 * @property liveData
373 * @type Object
375 YAHOO.util.DataSource.prototype.liveData = null;
378 * Where the live data is held.
380 * @property dataType
381 * @type Number
382 * @default YAHOO.util.DataSource.TYPE_UNKNOWN
385 YAHOO.util.DataSource.prototype.dataType = YAHOO.util.DataSource.TYPE_UNKNOWN;
388 * Format of response.
390 * @property responseType
391 * @type Number
392 * @default YAHOO.util.DataSource.TYPE_UNKNOWN
394 YAHOO.util.DataSource.prototype.responseType = YAHOO.util.DataSource.TYPE_UNKNOWN;
397 * Response schema object literal takes a combination of the following properties:
399 * <dl>
400 * <dt>resultsList</dt> <dd>Pointer to array of tabular data</dd>
401 * <dt>resultNode</dt> <dd>Pointer to node name of row data (XML data only)</dd>
402 * <dt>recordDelim</dt> <dd>Record delimiter (text data only)</dd>
403 * <dt>fieldDelim</dt> <dd>Field delimiter (text data only)</dd>
404 * <dt>fields</dt> <dd>Array of field names (aka keys), or array of object literals
405 * such as: {key:"fieldname",parser:YAHOO.util.DataSource.parseDate}</dd>
406 * </dl>
408 * @property responseSchema
409 * @type Object
411 YAHOO.util.DataSource.prototype.responseSchema = null;
414 * Alias to YUI Connection Manager. Allows implementers to specify their own
415 * subclasses of the YUI Connection Manager utility.
417 * @property connMgr
418 * @type Object
419 * @default YAHOO.util.Connect
421 YAHOO.util.DataSource.prototype.connMgr = null;
424 * If data is accessed over XHR via Connection Manager, this setting defines
425 * request/response management in the following manner:
426 * <dl>
427 * <dt>queueRequests</dt>
428 * <dd>If a request is already in progress, wait until response is returned
429 * before sending the next request.</dd>
431 * <dt>cancelStaleRequests</dt>
432 * <dd>If a request is already in progress, cancel it before sending the next
433 * request.</dd>
435 * <dt>ignoreStaleResponses</dt>
436 * <dd>Send all requests, but handle only the response for the most recently
437 * sent request.</dd>
439 * <dt>allowAll</dt>
440 * <dd>Send all requests and handle all responses.</dd>
442 * </dl>
444 * @property connXhrMode
445 * @type String
446 * @default "allowAll"
448 YAHOO.util.DataSource.prototype.connXhrMode = "allowAll";
451 * If data is accessed over XHR via Connection Manager, true if data should be
452 * sent via POST, otherwise data will be sent via GET.
454 * @property connMethodPost
455 * @type Boolean
456 * @default false
458 YAHOO.util.DataSource.prototype.connMethodPost = false;
461 * If data is accessed over XHR via Connection Manager, the connection timeout
462 * defines how many milliseconds the XHR connection will wait for a server
463 * response. Any non-zero value will enable the Connection utility's
464 * Auto-Abort feature.
466 * @property connTimeout
467 * @type Number
468 * @default 0
470 YAHOO.util.DataSource.prototype.connTimeout = 0;
472 /////////////////////////////////////////////////////////////////////////////
474 // Public static methods
476 /////////////////////////////////////////////////////////////////////////////
479 * Converts data to type String.
481 * @method DataSource.parseString
482 * @param oData {String | Number | Boolean | Date | Array | Object} Data to parse.
483 * The special values null and undefined will return null.
484 * @return {Number} A string, or null.
485 * @static
487 YAHOO.util.DataSource.parseString = function(oData) {
488 // Special case null and undefined
489 if(!YAHOO.lang.isValue(oData)) {
490 return null;
493 //Convert to string
494 var string = oData + "";
496 // Validate
497 if(YAHOO.lang.isString(string)) {
498 return string;
500 else {
501 return null;
506 * Converts data to type Number.
508 * @method DataSource.parseNumber
509 * @param oData {String | Number | Boolean | Null} Data to convert. Beware, null
510 * returns as 0.
511 * @return {Number} A number, or null if NaN.
512 * @static
514 YAHOO.util.DataSource.parseNumber = function(oData) {
515 //Convert to number
516 var number = oData * 1;
518 // Validate
519 if(YAHOO.lang.isNumber(number)) {
520 return number;
522 else {
523 return null;
526 // Backward compatibility
527 YAHOO.util.DataSource.convertNumber = function(oData) {
528 return YAHOO.util.DataSource.parseNumber(oData);
532 * Converts data to type Date.
534 * @method DataSource.parseDate
535 * @param oData {Date | String | Number} Data to convert.
536 * @return {Date} A Date instance.
537 * @static
539 YAHOO.util.DataSource.parseDate = function(oData) {
540 var date = null;
542 //Convert to date
543 if(!(oData instanceof Date)) {
544 date = new Date(oData);
546 else {
547 return oData;
550 // Validate
551 if(date instanceof Date) {
552 return date;
554 else {
555 return null;
558 // Backward compatibility
559 YAHOO.util.DataSource.convertDate = function(oData) {
560 return YAHOO.util.DataSource.parseDate(oData);
563 /////////////////////////////////////////////////////////////////////////////
565 // Public methods
567 /////////////////////////////////////////////////////////////////////////////
570 * Public accessor to the unique name of the DataSource instance.
572 * @method toString
573 * @return {String} Unique name of the DataSource instance.
575 YAHOO.util.DataSource.prototype.toString = function() {
576 return this._sName;
580 * Overridable method passes request to cache and returns cached response if any,
581 * refreshing the hit in the cache as the newest item. Returns null if there is
582 * no cache hit.
584 * @method getCachedResponse
585 * @param oRequest {Object} Request object.
586 * @param oCallback {Function} Handler function to receive the response.
587 * @param oCaller {Object} The Calling object that is making the request.
588 * @return {Object} Cached response object or null.
590 YAHOO.util.DataSource.prototype.getCachedResponse = function(oRequest, oCallback, oCaller) {
591 var aCache = this._aCache;
592 var nCacheLength = (aCache) ? aCache.length : 0;
593 var oResponse = null;
595 // If cache is enabled...
596 if((this.maxCacheEntries > 0) && aCache && (nCacheLength > 0)) {
597 this.fireEvent("cacheRequestEvent", {request:oRequest,callback:oCallback,caller:oCaller});
599 // Loop through each cached element
600 for(var i = nCacheLength-1; i >= 0; i--) {
601 var oCacheElem = aCache[i];
603 // Defer cache hit logic to a public overridable method
604 if(this.isCacheHit(oRequest,oCacheElem.request)) {
605 // Grab the cached response
606 oResponse = oCacheElem.response;
607 // The cache returned a hit!
608 // Remove element from its original location
609 aCache.splice(i,1);
610 // Add as newest
611 this.addToCache(oRequest, oResponse);
612 this.fireEvent("cacheResponseEvent", {request:oRequest,response:oResponse,callback:oCallback,caller:oCaller});
613 break;
617 return oResponse;
621 * Default overridable method matches given request to given cached request.
622 * Returns true if is a hit, returns false otherwise. Implementers should
623 * override this method to customize the cache-matching algorithm.
625 * @method isCacheHit
626 * @param oRequest {Object} Request object.
627 * @param oCachedRequest {Object} Cached request object.
628 * @return {Boolean} True if given request matches cached request, false otherwise.
630 YAHOO.util.DataSource.prototype.isCacheHit = function(oRequest, oCachedRequest) {
631 return (oRequest === oCachedRequest);
635 * Adds a new item to the cache. If cache is full, evicts the stalest item
636 * before adding the new item.
638 * @method addToCache
639 * @param oRequest {Object} Request object.
640 * @param oResponse {Object} Response object to cache.
642 YAHOO.util.DataSource.prototype.addToCache = function(oRequest, oResponse) {
643 var aCache = this._aCache;
644 if(!aCache) {
645 return;
648 //TODO: check for duplicate entries
650 // If the cache is full, make room by removing stalest element (index=0)
651 while(aCache.length >= this.maxCacheEntries) {
652 aCache.shift();
655 // Add to cache in the newest position, at the end of the array
656 var oCacheElem = {request:oRequest,response:oResponse};
657 aCache.push(oCacheElem);
658 this.fireEvent("responseCacheEvent", {request:oRequest,response:oResponse});
662 * Flushes cache.
664 * @method flushCache
666 YAHOO.util.DataSource.prototype.flushCache = function() {
667 if(this._aCache) {
668 this._aCache = [];
669 this.fireEvent("cacheFlushEvent");
674 * First looks for cached response, then sends request to live data.
676 * @method sendRequest
677 * @param oRequest {Object} Request object.
678 * @param oCallback {Function} Handler function to receive the response.
679 * @param oCaller {Object} The Calling object that is making the request.
680 * @return {Number} Transaction ID, or null if response found in cache.
682 YAHOO.util.DataSource.prototype.sendRequest = function(oRequest, oCallback, oCaller) {
683 // First look in cache
684 var oCachedResponse = this.getCachedResponse(oRequest, oCallback, oCaller);
685 if(oCachedResponse) {
686 oCallback.call(oCaller, oRequest, oCachedResponse);
687 return null;
690 // Not in cache, so forward request to live data
691 return this.makeConnection(oRequest, oCallback, oCaller);
695 * Overridable method provides default functionality to make a connection to
696 * live data in order to send request. The response coming back is then
697 * forwarded to the handleResponse function. This method should be customized
698 * to achieve more complex implementations.
700 * @method makeConnection
701 * @param oRequest {Object} Request object.
702 * @param oCallback {Function} Handler function to receive the response.
703 * @param oCaller {Object} The Calling object that is making the request.
704 * @return {Number} Transaction ID.
706 YAHOO.util.DataSource.prototype.makeConnection = function(oRequest, oCallback, oCaller) {
707 this.fireEvent("requestEvent", {request:oRequest,callback:oCallback,caller:oCaller});
708 var oRawResponse = null;
709 var tId = YAHOO.util.DataSource._nTransactionId++;
711 // How to make the connection depends on the type of data
712 switch(this.dataType) {
713 // If the live data is a JavaScript Function
714 // pass the request in as a parameter and
715 // forward the return value to the handler
716 case YAHOO.util.DataSource.TYPE_JSFUNCTION:
717 oRawResponse = this.liveData(oRequest);
718 this.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
719 break;
720 // If the live data is over Connection Manager
721 // set up the callback object and
722 // pass the request in as a URL query and
723 // forward the response to the handler
724 case YAHOO.util.DataSource.TYPE_XHR:
725 var oSelf = this;
726 var oConnMgr = this.connMgr || YAHOO.util.Connect;
727 var oQueue = this._oQueue;
730 * Define Connection Manager success handler
732 * @method _xhrSuccess
733 * @param oResponse {Object} HTTPXMLRequest object
734 * @private
736 var _xhrSuccess = function(oResponse) {
737 // If response ID does not match last made request ID,
738 // silently fail and wait for the next response
739 if(oResponse && (this.connXhrMode == "ignoreStaleResponses") &&
740 (oResponse.tId != oQueue.conn.tId)) {
741 return null;
743 // Error if no response
744 else if(!oResponse) {
745 this.fireEvent("dataErrorEvent", {request:oRequest,
746 callback:oCallback, caller:oCaller,
747 message:YAHOO.util.DataSource.ERROR_DATANULL});
749 // Send error response back to the caller with the error flag on
750 oCallback.call(oCaller, oRequest, oResponse, true);
752 return null;
754 // Forward to handler
755 else {
756 this.handleResponse(oRequest, oResponse, oCallback, oCaller, tId);
761 * Define Connection Manager failure handler
763 * @method _xhrFailure
764 * @param oResponse {Object} HTTPXMLRequest object
765 * @private
767 var _xhrFailure = function(oResponse) {
768 this.fireEvent("dataErrorEvent", {request:oRequest,
769 callback:oCallback, caller:oCaller,
770 message:YAHOO.util.DataSource.ERROR_DATAINVALID});
772 // Backward compatibility
773 if((this.liveData.lastIndexOf("?") !== this.liveData.length-1) &&
774 (oRequest.indexOf("?") !== 0)){
777 // Send failure response back to the caller with the error flag on
778 oCallback.call(oCaller, oRequest, oResponse, true);
779 return null;
783 * Define Connection Manager callback object
785 * @property _xhrCallback
786 * @param oResponse {Object} HTTPXMLRequest object
787 * @private
789 var _xhrCallback = {
790 success:_xhrSuccess,
791 failure:_xhrFailure,
792 scope: this
795 // Apply Connection Manager timeout
796 if(YAHOO.lang.isNumber(this.connTimeout)) {
797 _xhrCallback.timeout = this.connTimeout;
800 // Cancel stale requests
801 if(this.connXhrMode == "cancelStaleRequests") {
802 // Look in queue for stale requests
803 if(oQueue.conn) {
804 if(oConnMgr.abort) {
805 oConnMgr.abort(oQueue.conn);
806 oQueue.conn = null;
808 else {
813 // Get ready to send the request URL
814 if(oConnMgr && oConnMgr.asyncRequest) {
815 var sLiveData = this.liveData;
816 var isPost = this.connMethodPost;
817 var sMethod = (isPost) ? "POST" : "GET";
818 var sUri = (isPost) ? sLiveData : sLiveData+oRequest;
819 var sRequest = (isPost) ? oRequest : null;
821 // Send the request right away
822 if(this.connXhrMode != "queueRequests") {
823 oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, _xhrCallback, sRequest);
825 // Queue up then send the request
826 else {
827 // Found a request already in progress
828 if(oQueue.conn) {
829 // Add request to queue
830 oQueue.requests.push({request:oRequest, callback:_xhrCallback});
832 // Interval needs to be started
833 if(!oQueue.interval) {
834 oQueue.interval = setInterval(function() {
835 // Connection is in progress
836 if(oConnMgr.isCallInProgress(oQueue.conn)) {
837 return;
839 else {
840 // Send next request
841 if(oQueue.requests.length > 0) {
842 sUri = (isPost) ? sLiveData : sLiveData+oQueue.requests[0].request;
843 sRequest = (isPost) ? oQueue.requests[0].request : null;
844 oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, oQueue.requests[0].callback, sRequest);
846 // Remove request from queue
847 oQueue.requests.shift();
849 // No more requests
850 else {
851 clearInterval(oQueue.interval);
852 oQueue.interval = null;
855 }, 50);
858 // Nothing is in progress
859 else {
860 oQueue.conn = oConnMgr.asyncRequest(sMethod, sUri, _xhrCallback, sRequest);
864 else {
865 // Send null response back to the caller with the error flag on
866 oCallback.call(oCaller, oRequest, null, true);
869 break;
870 // Simply forward the entire data object to the handler
871 default:
872 /* accounts for the following cases:
873 YAHOO.util.DataSource.TYPE_UNKNOWN:
874 YAHOO.util.DataSource.TYPE_JSARRAY:
875 YAHOO.util.DataSource.TYPE_JSON:
876 YAHOO.util.DataSource.TYPE_HTMLTABLE:
877 YAHOO.util.DataSource.TYPE_XML:
879 oRawResponse = this.liveData;
880 this.handleResponse(oRequest, oRawResponse, oCallback, oCaller, tId);
881 break;
883 return tId;
887 * Handles raw data response from live data source. Sends a parsed response object
888 * to the callback function in this format:
890 * fnCallback(oRequest, oParsedResponse)
892 * where the oParsedResponse object literal with the following properties:
893 * <ul>
894 * <li>tId {Number} Unique transaction ID</li>
895 * <li>results {Array} Array of parsed data results</li>
896 * <li>error {Boolean} True if there was an error</li>
897 * </ul>
899 * @method handleResponse
900 * @param oRequest {Object} Request object
901 * @param oRawResponse {Object} The raw response from the live database.
902 * @param oCallback {Function} Handler function to receive the response.
903 * @param oCaller {Object} The calling object that is making the request.
904 * @param tId {Number} Transaction ID.
906 YAHOO.util.DataSource.prototype.handleResponse = function(oRequest, oRawResponse, oCallback, oCaller, tId) {
907 this.fireEvent("responseEvent", {request:oRequest, response:oRawResponse,
908 callback:oCallback, caller:oCaller, tId: tId});
909 var xhr = (this.dataType == YAHOO.util.DataSource.TYPE_XHR) ? true : false;
910 var oParsedResponse = null;
911 var bError = false;
913 // Access to the raw response before it gets parsed
914 oRawResponse = this.doBeforeParseData(oRequest, oRawResponse);
916 switch(this.responseType) {
917 case YAHOO.util.DataSource.TYPE_JSARRAY:
918 if(xhr && oRawResponse.responseText) {
919 oRawResponse = oRawResponse.responseText;
921 oParsedResponse = this.parseArrayData(oRequest, oRawResponse);
922 break;
923 case YAHOO.util.DataSource.TYPE_JSON:
924 if(xhr && oRawResponse.responseText) {
925 oRawResponse = oRawResponse.responseText;
927 oParsedResponse = this.parseJSONData(oRequest, oRawResponse);
928 break;
929 case YAHOO.util.DataSource.TYPE_HTMLTABLE:
930 if(xhr && oRawResponse.responseText) {
931 oRawResponse = oRawResponse.responseText;
933 oParsedResponse = this.parseHTMLTableData(oRequest, oRawResponse);
934 break;
935 case YAHOO.util.DataSource.TYPE_XML:
936 if(xhr && oRawResponse.responseXML) {
937 oRawResponse = oRawResponse.responseXML;
939 oParsedResponse = this.parseXMLData(oRequest, oRawResponse);
940 break;
941 case YAHOO.util.DataSource.TYPE_TEXT:
942 if(xhr && oRawResponse.responseText) {
943 oRawResponse = oRawResponse.responseText;
945 oParsedResponse = this.parseTextData(oRequest, oRawResponse);
946 break;
947 default:
948 //var contentType = oRawResponse.getResponseHeader["Content-Type"];
949 break;
953 if(oParsedResponse) {
954 // Last chance to touch the raw response or the parsed response
955 oParsedResponse.tId = tId;
956 oParsedResponse = this.doBeforeCallback(oRequest, oRawResponse, oParsedResponse);
957 this.fireEvent("responseParseEvent", {request:oRequest,
958 response:oParsedResponse, callback:oCallback, caller:oCaller});
959 // Cache the response
960 this.addToCache(oRequest, oParsedResponse);
962 else {
963 this.fireEvent("dataErrorEvent", {request:oRequest, callback:oCallback,
964 caller:oCaller, message:YAHOO.util.DataSource.ERROR_DATANULL});
966 // Send response back to the caller with the error flag on
967 oParsedResponse = {error:true};
970 // Send the response back to the caller
971 oCallback.call(oCaller, oRequest, oParsedResponse);
975 * Overridable method gives implementers access to the original raw response
976 * before the data gets parsed. Implementers should take care not to return an
977 * unparsable or otherwise invalid raw response.
979 * @method doBeforeParseData
980 * @param oRequest {Object} Request object.
981 * @param oRawResponse {Object} The raw response from the live database.
982 * @return {Object} Raw response for parsing.
984 YAHOO.util.DataSource.prototype.doBeforeParseData = function(oRequest, oRawResponse) {
985 return oRawResponse;
989 * Overridable method gives implementers access to the original raw response and
990 * the parsed response (parsed against the given schema) before the data
991 * is added to the cache (if applicable) and then sent back to callback function.
992 * This is your chance to access the raw response and/or populate the parsed
993 * response with any custom data.
995 * @method doBeforeCallback
996 * @param oRequest {Object} Request object.
997 * @param oRawResponse {Object} The raw response from the live database.
998 * @param oParsedResponse {Object} The parsed response to return to calling object.
999 * @return {Object} Parsed response object.
1001 YAHOO.util.DataSource.prototype.doBeforeCallback = function(oRequest, oRawResponse, oParsedResponse) {
1002 return oParsedResponse;
1006 * Overridable method parses raw array data into a response object.
1008 * @method parseArrayData
1009 * @param oRequest {Object} Request object.
1010 * @param oRawResponse {Object} The raw response from the live database.
1011 * @return {Object} Parsed response object.
1013 YAHOO.util.DataSource.prototype.parseArrayData = function(oRequest, oRawResponse) {
1014 if(YAHOO.lang.isArray(oRawResponse) && YAHOO.lang.isArray(this.responseSchema.fields)) {
1015 var oParsedResponse = {results:[]};
1016 var fields = this.responseSchema.fields;
1017 for(var i=oRawResponse.length-1; i>-1; i--) {
1018 var oResult = {};
1019 for(var j=fields.length-1; j>-1; j--) {
1020 var field = fields[j];
1021 var key = (YAHOO.lang.isValue(field.key)) ? field.key : field;
1022 var data = (YAHOO.lang.isValue(oRawResponse[i][j])) ? oRawResponse[i][j] : oRawResponse[i][key];
1023 // Backward compatibility
1024 if(!field.parser && field.converter) {
1025 field.parser = field.converter;
1027 if(field.parser) {
1028 data = field.parser.call(this, data);
1030 // Safety measure
1031 if(data === undefined) {
1032 data = null;
1034 oResult[key] = data;
1036 oParsedResponse.results.unshift(oResult);
1038 return oParsedResponse;
1040 else {
1041 return null;
1046 * Overridable method parses raw plain text data into a response object.
1048 * @method parseTextData
1049 * @param oRequest {Object} Request object.
1050 * @param oRawResponse {Object} The raw response from the live database.
1051 * @return {Object} Parsed response object.
1053 YAHOO.util.DataSource.prototype.parseTextData = function(oRequest, oRawResponse) {
1054 var oParsedResponse = {};
1055 if(YAHOO.lang.isString(oRawResponse) &&
1056 YAHOO.lang.isArray(this.responseSchema.fields) &&
1057 YAHOO.lang.isString(this.responseSchema.recordDelim) &&
1058 YAHOO.lang.isString(this.responseSchema.fieldDelim)) {
1059 oParsedResponse.results = [];
1060 var recDelim = this.responseSchema.recordDelim;
1061 var fieldDelim = this.responseSchema.fieldDelim;
1062 var fields = this.responseSchema.fields;
1063 if(oRawResponse.length > 0) {
1064 // Delete the last line delimiter at the end of the data if it exists
1065 var newLength = oRawResponse.length-recDelim.length;
1066 if(oRawResponse.substr(newLength) == recDelim) {
1067 oRawResponse = oRawResponse.substr(0, newLength);
1069 // Split along record delimiter to get an array of strings
1070 var recordsarray = oRawResponse.split(recDelim);
1071 // Cycle through each record, except the first which contains header info
1072 for(var i = recordsarray.length-1; i>-1; i--) {
1073 var oResult = {};
1074 for(var j=fields.length-1; j>-1; j--) {
1075 // Split along field delimter to get each data value
1076 var fielddataarray = recordsarray[i].split(fieldDelim);
1078 // Remove quotation marks from edges, if applicable
1079 var data = fielddataarray[j];
1080 if(data.charAt(0) == "\"") {
1081 data = data.substr(1);
1083 if(data.charAt(data.length-1) == "\"") {
1084 data = data.substr(0,data.length-1);
1086 var field = fields[j];
1087 var key = (YAHOO.lang.isValue(field.key)) ? field.key : field;
1088 // Backward compatibility
1089 if(!field.parser && field.converter) {
1090 field.parser = field.converter;
1092 if(field.parser) {
1093 data = field.parser.call(this, data);
1095 // Safety measure
1096 if(data === undefined) {
1097 data = null;
1099 oResult[key] = data;
1101 oParsedResponse.results.unshift(oResult);
1105 else {
1106 oParsedResponse.error = true;
1108 return oParsedResponse;
1112 * Overridable method parses raw XML data into a response object.
1114 * @method parseXMLData
1115 * @param oRequest {Object} Request object.
1116 * @param oRawResponse {Object} The raw response from the live database.
1117 * @return {Object} Parsed response object.
1119 YAHOO.util.DataSource.prototype.parseXMLData = function(oRequest, oRawResponse) {
1120 var bError = false;
1121 var oParsedResponse = {};
1122 var xmlList = (this.responseSchema.resultNode) ?
1123 oRawResponse.getElementsByTagName(this.responseSchema.resultNode) :
1124 null;
1125 if(!xmlList || !YAHOO.lang.isArray(this.responseSchema.fields)) {
1126 bError = true;
1128 // Loop through each result
1129 else {
1130 oParsedResponse.results = [];
1131 for(var k = xmlList.length-1; k >= 0 ; k--) {
1132 var result = xmlList.item(k);
1133 var oResult = {};
1134 // Loop through each data field in each result using the schema
1135 for(var m = this.responseSchema.fields.length-1; m >= 0 ; m--) {
1136 var field = this.responseSchema.fields[m];
1137 var key = (YAHOO.lang.isValue(field.key)) ? field.key : field;
1138 var data = null;
1139 // Values may be held in an attribute...
1140 var xmlAttr = result.attributes.getNamedItem(key);
1141 if(xmlAttr) {
1142 data = xmlAttr.value;
1144 // ...or in a node
1145 else {
1146 var xmlNode = result.getElementsByTagName(key);
1147 if(xmlNode && xmlNode.item(0) && xmlNode.item(0).firstChild) {
1148 data = xmlNode.item(0).firstChild.nodeValue;
1150 else {
1151 data = "";
1154 // Backward compatibility
1155 if(!field.parser && field.converter) {
1156 field.parser = field.converter;
1158 if(field.parser) {
1159 data = field.parser.call(this, data);
1161 // Safety measure
1162 if(data === undefined) {
1163 data = null;
1165 oResult[key] = data;
1167 // Capture each array of values into an array of results
1168 oParsedResponse.results.unshift(oResult);
1171 if(bError) {
1172 oParsedResponse.error = true;
1174 else {
1176 return oParsedResponse;
1180 * Overridable method parses raw JSON data into a response object.
1182 * @method parseJSONData
1183 * @param oRequest {Object} Request object.
1184 * @param oRawResponse {Object} The raw response from the live database.
1185 * @return {Object} Parsed response object.
1187 YAHOO.util.DataSource.prototype.parseJSONData = function(oRequest, oRawResponse) {
1188 var oParsedResponse = {};
1189 if(oRawResponse && YAHOO.lang.isArray(this.responseSchema.fields)) {
1190 var fields = this.responseSchema.fields;
1191 var bError = false;
1192 oParsedResponse.results = [];
1193 var jsonObj,jsonList;
1195 // Parse JSON object out if it's a string
1196 if(YAHOO.lang.isString(oRawResponse)) {
1197 // Check for latest JSON lib but divert KHTML clients
1198 var isNotMac = (navigator.userAgent.toLowerCase().indexOf('khtml')== -1);
1199 if(oRawResponse.parseJSON && isNotMac) {
1200 // Use the new JSON utility if available
1201 jsonObj = oRawResponse.parseJSON();
1202 if(!jsonObj) {
1203 bError = true;
1206 // Check for older JSON lib but divert KHTML clients
1207 else if(window.JSON && JSON.parse && isNotMac) {
1208 // Use the JSON utility if available
1209 jsonObj = JSON.parse(oRawResponse);
1210 if(!jsonObj) {
1211 bError = true;
1214 // No JSON lib found so parse the string
1215 else {
1216 try {
1217 // Trim leading spaces
1218 while (oRawResponse.length > 0 &&
1219 (oRawResponse.charAt(0) != "{") &&
1220 (oRawResponse.charAt(0) != "[")) {
1221 oRawResponse = oRawResponse.substring(1, oRawResponse.length);
1224 if(oRawResponse.length > 0) {
1225 // Strip extraneous stuff at the end
1226 var objEnd = Math.max(oRawResponse.lastIndexOf("]"),oRawResponse.lastIndexOf("}"));
1227 oRawResponse = oRawResponse.substring(0,objEnd+1);
1229 // Turn the string into an object literal...
1230 // ...eval is necessary here
1231 jsonObj = eval("(" + oRawResponse + ")");
1232 if(!jsonObj) {
1233 bError = true;
1237 else {
1238 jsonObj = null;
1239 bError = true;
1242 catch(e) {
1243 bError = true;
1247 // Response must already be a JSON object
1248 else if(oRawResponse.constructor == Object) {
1249 jsonObj = oRawResponse;
1251 // Not a string or an object
1252 else {
1253 bError = true;
1255 // Now that we have a JSON object, parse a jsonList out of it
1256 if(jsonObj && jsonObj.constructor == Object) {
1257 try {
1258 // eval is necessary here since schema can be of unknown depth
1259 jsonList = eval("jsonObj." + this.responseSchema.resultsList);
1261 catch(e) {
1262 bError = true;
1266 if(bError || !jsonList) {
1267 oParsedResponse.error = true;
1269 if(jsonList && !YAHOO.lang.isArray(jsonList)) {
1270 jsonList = [jsonList];
1272 else if(!jsonList) {
1273 jsonList = [];
1276 // Loop through the array of all responses...
1277 for(var i = jsonList.length-1; i >= 0 ; i--) {
1278 var oResult = {};
1279 var jsonResult = jsonList[i];
1280 // ...and loop through each data field value of each response
1281 for(var j = fields.length-1; j >= 0 ; j--) {
1282 var field = fields[j];
1283 var key = (YAHOO.lang.isValue(field.key)) ? field.key : field;
1284 // ...and capture data into an array mapped according to the schema...
1285 // eval is necessary here since schema can be of unknown depth
1286 var data = eval("jsonResult." + key);
1288 // Backward compatibility
1289 if(!field.parser && field.converter) {
1290 field.parser = field.converter;
1292 if(field.parser) {
1293 data = field.parser.call(this, data);
1295 // Safety measure
1296 if(data === undefined) {
1297 data = null;
1299 oResult[key] = data;
1301 // Capture the array of data field values in an array of results
1302 oParsedResponse.results.unshift(oResult);
1305 else {
1306 oParsedResponse.error = true;
1308 return oParsedResponse;
1312 * Overridable method parses raw HTML TABLE element data into a response object.
1314 * @method parseHTMLTableData
1315 * @param oRequest {Object} Request object.
1316 * @param oRawResponse {Object} The raw response from the live database.
1317 * @return {Object} Parsed response object.
1319 YAHOO.util.DataSource.prototype.parseHTMLTableData = function(oRequest, oRawResponse) {
1320 var bError = false;
1321 var elTable = oRawResponse;
1322 var fields = this.responseSchema.fields;
1323 var oParsedResponse = {};
1324 oParsedResponse.results = [];
1326 // Iterate through each TBODY
1327 for(var i=0; i<elTable.tBodies.length; i++) {
1328 var elTbody = elTable.tBodies[i];
1330 // Iterate through each TR
1331 for(var j=elTbody.rows.length-1; j>-1; j--) {
1332 var elRow = elTbody.rows[j];
1333 var oResult = {};
1335 for(var k=fields.length-1; k>-1; k--) {
1336 var field = fields[k];
1337 var key = (YAHOO.lang.isValue(field.key)) ? field.key : field;
1338 var data = elRow.cells[k].innerHTML;
1340 // Backward compatibility
1341 if(!field.parser && field.converter) {
1342 field.parser = field.converter;
1344 if(field.parser) {
1345 data = field.parser.call(this, data);
1347 // Safety measure
1348 if(data === undefined) {
1349 data = null;
1351 oResult[key] = data;
1353 oParsedResponse.results.unshift(oResult);
1357 if(bError) {
1358 oParsedResponse.error = true;
1360 else {
1362 return oParsedResponse;
1365 YAHOO.register("datasource", YAHOO.util.DataSource, {version: "2.3.0", build: "442"});