/*

  This file is an object library to encapsulate
  XMLHttpRequest IO and facilitate client side XSLT
  using similarly loaded XSL stylesheets.

  Provides two classes:
   - XMLData	Groups the XHR requests 
   				Manages a callback slot to
					kick off a provided user
					function when all data
					is loaded
				Provides a transform([label])
					function to apply a loaded
					XSL stylesheet to the data.

   - XML		Wrapper class over XHR/XMLDOM
   					requests (Browser agnostic)
				Parses and stores the returned
					XML on receipt.

  Author: Lucas Smith
  Version: 0.1
  Created: 7/24/2005

*/

// Set up an IO bridge to reconnect the
// objects issuing the requests with the server
// responses.
IOBridge = {
	"listeners": {},
	"registerListener":
		function(listener,label) {
			IOBridge.listeners[label] = listener;
		},
	"unregisterListener":
		function(label) {
			delete IOBridge.listeners[label];
		},
	"notify":
		function(name) {
			IOBridge.listeners[name].notify();
		}
};


// The data/XSLT collection class
function XMLData() {
	this.data		= null;
	this.XSLT		= {};
	this.output		= {};
	this.callback	= null;
	this.error		= null;

	// Define the data source url
	this.setDataSource = function(url) {
		this.data = new XML(url);
	};

	// Add an XSL template for the data
	this.addStylesheet = function(url,label) {
  		var url = arguments[0];
  		var label = arguments[1] || "default";
		if (!url) return false;

		this.XSLT[label] = new XML(url);
	};

	// Set the callback function to be executed when all the server
	// responses have come in.
	this.setCallback = function(func) {	this.callback = func; };

	// Send all requests to the server
	this.load = function() {
		if (!this.data) return false;

		// Register myself as a listener to each XML object
		this.data.registerListener(this);
		for (var label in this.XSLT)
			this.XSLT[label].registerListener(this);

		// Tell all XML objects to load
		this.data.load();
		for (var label in this.XSLT)
			this.XSLT[label].load();
	};

	// Transform the collected XML data using one of the loaded XSL files
	this.transform = function(label) {
  		label = arguments[0] || "default";

		// Make doubly sure everything is loaded
		if (!this.data.isLoaded()
  		||  !this.XSLT[label]
		||  !this.XSLT[label].isLoaded())
			return "";

  		// Check for pre-transformed output
  		if (!this.output[label]) {
    		var formatted = "";
    		if (window.XMLHttpRequest) { // W3C browsers
        		var xslt = new XSLTProcessor();
        		xslt.importStylesheet(this.XSLT[label].getXMLObject());
        		formatted = xslt.transformToFragment(this.data.getXMLObject(),document);
      		} else { // IE
        		formatted = this.data.transformNode(this.XSLT[label].getXMLObject());
    		}

    		this.output[label] = formatted;
  		}

  		return this.output[label];
	};

	// Handle the notifications from the XML objects that their
	// data is loaded.
	this.notify = function() {
		// Check for errors
		if (this.hasError())
			return;

		// Check that the data and any XSLTs are loaded
		if (!this.data.isLoaded()) return;
		for (var label in this.XSLT)
			if (!this.XSLT[label].isLoaded())
				return;

		// Everything is loaded, so issue the callback
		try {
			this.callback();
		}
		catch (e) {
			// Ignore errors
		}
	};

	this.notifyError = function(requested_url) {
		// The requested data returned a server code other than 200 (OK)
		this.error = "Unable to return data from "+request_url;
	}

	// Error handling
	this.hasError = function() { return this.error != null; };
	this.getError = function() { return this.error; };

}

function XML(url) {
	this.source		= url;
	this.data		= null;
	this.io			= null;
	this.loaded		= false;
	this.listeners	= [];

	this.registerListener = function(listener) {
		this.listeners.push(listener);
	}

	this.getXMLObject = function() {
		return this.data;
	}

	// Send the request to the server
	this.load = function() {
		// Build an IO bridge
		IOBridge.registerListener(this,this.source);

		// Create a local copy to pass to the onreadystatechange handler
		var src = this.source;

    	if (window.XMLHttpRequest) { // W3C browsers
      		this.io = new XMLHttpRequest();
      		this.io.onreadystatechange = function() { IOBridge.notify(src); };

      		// Send the request
      		this.io.open("GET",this.source,true);
      		this.io.send(null);
    	} else { // IE
      		this.io = new ActiveXObject("Microsoft.XMLHTTP");
      		this.io.onreadystatechange = function() { IOBridge.notify(src); };

      		// Send the request
      		this.io.open("GET",this.source,true);
      		this.io.send();
  		}
	};

	this.isLoaded = function() { return this.loaded; }

	// Receive the request from the server
	this.notify = function() {
		if (this.io.readyState == 4) {
			if (this.io.status == 200) {
				this.data = null;
				if (window.XMLHttpRequest) {  // W3C browsers
      				var parser = new DOMParser();
      				this.data = parser.parseFromString(this.io.responseText, "text/xml");
    			} else { // IE
      				this.data = new ActiveXObject("Microsoft.XMLDOM");
      				this.data.async = false;
      				this.data.loadXML(this.io.responseText);
  				}

				this.loaded = true;

				// Notify all my loyal fans that I'm done
				for (var i = 0; i < this.listeners.length; i++)
					this.listeners[i].notify();
			} else {
				// Notify the listeners that there was an error
				for (var i = 0; i < this.listeners.length; i++)
					this.listeners[i].notifyError(this.source);
			}

			IOBridge.unregisterListener[this.source];
		}
	};

}

