/*
	Arthur Dent's Ajax Towel
	Another JS by Matt Evans
	Supplied under the MIT licence thingumy
*/


/*
------------------------------------------------------------------------------------------------------------------------------------------------------------
ArthurDent
------------------------------------------------------------------------------------------------------------------------------------------------------------

		The ArthurDent collection is provided by Matt Evans. Each deployment may feature slight differences depending on the project but 
		always follows the same script pattern and are usually compatible with one another under the ArthurDent namespace.
		
		You might be lucky enough to find a swiss army knife version with a full set of features on his home page: www.mattevans.info
		
		This particular sample of the ArthurDent namespace contains methods for	common services to make ajax and XML slicing nice and easy.
		
		If you have comments, go to Matt's homepage and find somewhere to put 'em (there should be a blog entry for the flavour (aka "towel") you're using)
		
		Name space:
		ArthurDent					- container, usually contains inits etc. for webapp implementations
		ArthurDent.utilities		- XML, DOM & string utilities
		ArthurDent.serviceDirectory	- templates to build common ajax calls (sinply helps you build common URLs, and handler, good when buidling web apps, otherwise ignore this one)
		ArthurDent.services			- keeps track of all services currently running, and provides a threaded state change handler
		
		Classes
		ArthurDent.service			- ajax service constructor
		
									This is the main one when creating a new ajax service
									Convention:
									Example - I want to get some html and write it to Dom element with ID "para_1":
									mytext = new ArthurDent.service("http://www.myurl.com/somepage.html");
									mytext.targetById = "para_1"; 
									mytext.targetProperty= "innerHTML"; 
									mytext.load();
									
									Or perhaps I have my text in a db, and a REST interface for it so I might have a template:
									mytext = new ArthurDent.service("textgrabber"); //note if I wanted to use a template I would
									mytext.text_id = 1; 
									mytext.targetById = "para_1"; 
									mytext.targetProperty= "innerHTML"; 
									mytext.load();
									
									Target doesn't have to be a DOM node, it can also be a function:
									mytext = new ArthurDent.service("http://www.myurl.com/somepage.html");
									... blah blah ... something like above ...
									mytext.target = funtion(thread){myFunctionToCall(thread.XMLRequest.responseText)}
									mytext.load();
									
*/

var ArthurDent = {
			
	serviceDirectory: 
	{
		//Lookup properties for common ajax calls, URL, vars etc.
		//although we get standard vars here, the URL is only assembled using these when the final load request is made		
		media: 
		{
			URL: "/Media/someurl.aspx",
			presets: function(vars) 
			{
				vars.CreateNew= "true";
				vars.Media_id= 0;
				vars.cul= ArthurDent.getValue("Culture");
				return vars;
			}
		}
	},
	
	services: 
	{
		/*Ajax request tracker & default handler*/
		threads: Array(),
		threadHandler: function(ref)
		{
			var thread = ArthurDent.services.threads[ref]
			if (thread)
			{
				/*thread.XMLRequest.readyState values: 0: uninitialized 1: loading 2: loaded 3: interactive 4: complete*/
				if (thread.XMLRequest.readyState == 4 )
				{
					if (thread.XMLRequest.status==200 || thread.XMLRequest.status==304)
					{
						if (typeof(thread.target)=="function")
						{
							thread.target(thread);
						} else 
						if (thread.target)
						{
							thread.target[thread.targetProperty] = thread.XMLRequest.responseText;
						} else 
						if (thread.targetByID) {
							document.getElementById(thread.targetByID)[thread.targetProperty] = thread.XMLRequest.responseText;
						}
					}
				}
			}
		}
	},
	
	service: function(type)
	{
		/* ArthurDent Ajax service - create new to use */
		this.URL = "";	
		this.method = "GET";
		this.vars = new Object();
		this.async = true;
		this.target = null;
		this.targetById = null;
		this.targetProperty = "innerHTML";	
		
		//Set URL & get presets for standard calls
		if (!ArthurDent.serviceDirectory[type]) 
		{
			this.URL = (type?type:""); 
			this.type="www";
		} else {
			this.type	= type
			this.URL	= ArthurDent.serviceDirectory[this.type].URL;//present URL
			this.method = (ArthurDent.serviceDirectory[this.type].method ? ArthurDent.serviceDirectory[this.type].method  : "GET");
			this.vars = ArthurDent.serviceDirectory[this.type].presets(this.vars);//preset ArthurDent lookups
		};	
		if (this.URL.indexOf("www")==0) this.URL = "http://" + this.URL;
	    
	    
		this.XMLRequest = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Msxml2.XMLHTTP");
	    
		if(this.XMLRequest.timeout) this.XMLRequest.timeout = 30000;//not available to IE6
		
		this.XMLRequest.onreadystatechange = new Function("ArthurDent.services.threadHandler("+(ArthurDent.services.threads.length)+")");
		
		if (ArthurDent.serviceDirectory[this.type])
		{
			if (ArthurDent.serviceDirectory[this.type].customHandler)
			{
				this.XMLRequest.onreadystatechange = new Function("ArthurDent.serviceDirectory["+this.type+"].customhHandler("+(ArthurDent.services.threads.length)+")");
			}
		}
		
		ArthurDent.services.threads.push(this);
		
		//Load operation
		this.load = function ()
		{
			var params = "";
			for (var p in this.vars)
			{
				params += (params.length>0 ? "&" : "");
				params += p + "=" + this.vars[p];
			}
			try
			{
				if (this.method=="GET")
				{
					this.URL += (params.length>0 ? (this.URL.indexOf("?")!=-1 ? "&" : "?") + params : "");
					this.XMLRequest.open(this.method,this.URL,this.async);
					this.XMLRequest.send(params);
				} else {
					this.XMLRequest.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
					this.XMLRequest.open(this.method,this.URL,this.async);
					this.XMLRequest.send(params);					
				}
			} catch (E) {
				alert("ArthurDent service exception, check for denied cross domain calls...  "+E);
			}
		}
	},
	
	
	utilities:	
	{	
		/* Common Utilities - XML, Dom and common handlers. Like all ArthurDent functions, these are free to be used by any script */	

		/* XML */
		
		parseXML: function(str)
		{
			str = ArthurDent.utilities.trim(str);
			if (str=="" || str.length<=0)
			{
				return null;
			}
			try //Internet Explorer
			{
                var xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
                xmlDoc.async="false";
                xmlDoc.loadXML(str);
			}
			catch(e)
			{
                try //Firefox, Mozilla, Opera, etc.
                {
				  str = str.replace('<?xml version="1.0" encoding="utf-8"?>','');
				  str = str.replace(/\n/ig,'').replace(/  /g,' ').replace(/> *</g,'><');
                  var parser=new DOMParser();
				  var xmlDoc=parser.parseFromString(str,"text/xml");
                }
                catch(e)
                {
                return null;
                }
			}
			return ArthurDent.utilities.stripWhiteSpaceNodes(xmlDoc);
		},
				
		moveNodeAfter: function(obj,destination)
		{
				var newElement=obj.cloneNode(true);
				if (obj.parentNode)	obj.parentNode.removeChild(obj);
				ArthurDent.utilities.insertNodeAfter(newElement,destination);
				return newElement;
		},
	
		insertNodeAfter: function(node, referenceNode) 
		{
		  referenceNode.parentNode.insertBefore(node, referenceNode.nextSibling);
		},
		
		isWhiteSpaceNode: function(nod)
		{
		  return !(/[^\t\n\r ]/.test(nod.data));// Use ECMA-262 Edition 3 String and RegExp features
		},
		
		stripWhiteSpaceNodes: function(xmlDoc)
		{
			var elements = (xmlDoc.all)? xmlDoc.all : xmlDoc.getElementsByTagName("*");
		  for(var i=0; i<elements.length; i++)
		  {
		  	if (ArthurDent.utilities.isWhiteSpaceNode(elements[i]))
		  	{
		  		elements[i].parentNode.removeChild(elements[i]);
		  	};
		  }
		  return xmlDoc;
		},
		
		serializeXML: function(xmlObj)
		{
			//Serialize to String
			var str='';
			try
			{
				str = (new XMLSerializer()).serializeToString(xmlObj);
			}
			catch(E)
			{
				//IE doesn't support the serializer object
				str = xmlObj.xml;
			}
			return ArthurDent.utilities.trim(str);
		},
		
		serializeNodeContents: function(xmlObj)
		{
		    var val = ArthurDent.utilities.serializeXML(xmlObj);
		    val = val.substring(val.indexOf(">")+1,val.length);
		    val = val.substring(0,val.lastIndexOf("<"));
		    return val;
		},
		
		unescapeXML: function(str)
		{
			str = str.replace(/&lt;/g,"<");
			str = str.replace(/&gt;/g,">");
			str = str.replace(/&amp;lt;/g,"&lt;");
			str = str.replace(/&amp;gt;/g,"&gt;");
			return str;
		},
		
		escapeForXML: function(str)
		{
			var LT = new RegExp("<", "g");  
			var GT = new RegExp(">", "g"); 
			var TAB = new RegExp("\t", "g");
			var LTT = new RegExp("&amp;lt;", "g");   
			var GTT = new RegExp("&amp;gt;", "g");  
			return str.replace(LT, "&lt;").replace(GT, "&gt;").replace(TAB, "    ").replace(LTT, "&lt;").replace(GTT, "&gt;");  
		},
		
		getXPathAsString: function(ele)
		{
			return "/"+ArthurDent.utilities.getXPathAsArray(ele).join("/");
		},
		
		getXPathAsArray: function(node, path) 
		{
            path = path || [];
            if(node.parentNode) {
                path = ArthurDent.utilities.getXPathAsArray(node.parentNode, path);
            }
            var count = 1;
            if(node.previousSibling) {
                var sibling = node.previousSibling
                do {
                  if(sibling.nodeType == 1 && sibling.tagName == node.tagName) {count++;}
                  sibling = sibling.previousSibling;
                } while(sibling);
            } else if(node.nextSibling) {
                var sibling = node.nextSibling;
                do {
                  if(sibling.nodeType == 1 && sibling.tagName == node.tagName) {
                    sibling = null;
                  } else {
                    sibling = sibling.previousSibling;
                  }
                } while(sibling);
            }

            if(node.nodeType == 1) {
                //Note IE counts from Zero!
                var countVal = window.ActiveXObject ? count-1 : count==1?count=-1 : count;
                path.push(node.tagName + (node.id ? "[@id='"+node.id+"']" : count > -1 ? "["+countVal+"]" : ''));
            }
            return path;
        },
		
		insertNodeByXpath: function(xmlDoc, path)
		{
			var xmlDom = xmlDoc.ownerDocument ? xmlDoc.ownerDocument : xmlDoc;
			var pathComponents = path.split("/");
			var newChildName = pathComponents.pop();
			var parentPath = path.substr(0, path.length - newChildName.length - 1);
			
			// remove qualifier for node being added
			var qualifierLoc = newChildName.indexOf("[");
			if (qualifierLoc != -1) {
				newChildName = newChildName.substr(0, qualifierLoc);
			}			 
			var node = ArthurDent.utilities.getSingleNodeByXPath(xmlDoc,parentPath);
			var newChild = null;		
			
			if (window.ActiveXObject && node) {
				newChild = xmlDom.createElement(newChildName);
				node.appendChild(newChild);
			} else if ((!window.ActiveXObject) && node) {
				newChild = xmlDom.createElement(newChildName);
				node.appendChild(newChild);
			} else {
				// add the parent, then re-try to add this child
				var parentNode = ArthurDent.utilities.insertNodeByXpath(xmlDoc,parentPath);
				newChild = xmlDom.createElement(newChildName);
				parentNode.appendChild(newChild);
			}
			return newChild;
		},
		
		getSingleNodeByXPath: function(xmlDoc, path, create)
		{
            if (!xmlDoc) {
                return null;
            }
            if(window.ActiveXObject)
            {
              var node = xmlDoc.selectSingleNode(path);
            }
            else
            {
                var xpe = new XPathEvaluator();
                var nsResolver = xpe.createNSResolver( xmlDoc.ownerDocument == null ? xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement);
                //path = path.toLowerCase();
                var results = xpe.evaluate(path,xmlDoc,nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
                var node = results.singleNodeValue; 
            }
            if (!node && create)
            {
                //Make sure we have an XML document!
                if (xmlDoc)
                {
                node = ArthurDent.utilities.insertNodeByXpath(xmlDoc,path);
                }
            }
            return node;
		},
		
		setValueByXPath: function(xmlDoc,path,val)
		{
		    //Allow selection using attribute
		    attribute = path.indexOf("/@")!=-1 ?  path.substring(path.indexOf("@")+1,path.length) : false;
		    path = attribute ? path.substring(0,path.indexOf("/@")) : path;
		    
		    //Allow special function to be applied instead of the stanadrd text return
		    func = path.indexOf(".")>path.lastIndexOf("/") ? path.substring(path.indexOf(".")+1,path.length) : false;
		    path = func ? path.substring(0,path.indexOf(".")) : path;
		    
		    var node = ArthurDent.utilities.getSingleNodeByXPath(xmlDoc,path);
		    
			if (attribute && node)
			{
				node.setAttribute(attribute,ArthurDent.utilities.escapeForXML(val));
			}
			if (node && func)
			{
			    if (func = "childnodes")
			    {
			        //Create xml object
			        val = "<"+node.nodeName+">"+val+"</"+node.nodeName+">";
			        
			        //Some basic parsing protection from quotes amps and tabs
			        //Convert all &s not associated with a ; ex. &amp is invalid and we would get &amp;amp to make it validate
			        var amp = new RegExp('(&(?!([\S]*;)))', "g");
			        var quot = new RegExp('\"(?=[^<>]*<)', "g");
			        var tb = new RegExp("\t", "g");
			        
			        val = val.replace(amp,"&amp;")
			        val = val.replace(quot,"&quot;")
			        val = val.replace(tb,"     ")
			        
			        var xmlobj = ArthurDent.utilities.parseXML(val);
			        
			        if (xmlobj)
			        {
			            try
			            {
			                //Transplant the child nodes from this object to the destination node
			                node.parentNode.replaceChild(xmlobj.firstChild,node)
			            }catch(e)
			            {
			                alert("Parse Error\nYour HTML is has some errors. Please remember the following rules:\n1) Make sure your tags are correctly closed\n2) The < and > symbols are for use by tags only. If you want to use a < or > then use &lt; for < and &gt; for >");
			            }
			        } else {
			            alert("Parse Error\nYour HTML is has some errors. Please remember the following rules:\n1) Make sure your tags are correctly closed\n2) The < and > symbols are for use by tags only. If you want to use a < or > then use &lt; for < and &gt; for >");
			        }
			    }
			}
			if (node && !attribute && !func) {
			    //Assume we are dealing with a set of text nodes. It's ok to add HTML but it has to be checked to be accurate
			    //val = ArthurDent.utilities.escapeForXML(val);
			    try
			    {
			    node.textContent = val
			    } catch (e)
			    {
			    node.text = val
			    }
			}
		},
		
		getValueByXPath: function(xmlDoc,path)
		{
		    //Allow selection using attribute
		    attribute = path.indexOf("/@")!=-1 ?  path.substring(path.indexOf("@")+1,path.length) : false;
		    path = attribute ? path.substring(0,path.indexOf("/@")) : path;
		    
		    //Allow special function to be applied instead of the stanadrd text return
		    func = path.indexOf(".")>path.lastIndexOf("/") ? path.substring(path.indexOf(".")+1,path.length) : false;
		    path = func ? path.substring(0,path.indexOf(".")) : path;
		    
		    var node = ArthurDent.utilities.getSingleNodeByXPath(xmlDoc,path);
			
			var val = "";
			if (node && attribute)
			{
				val = node.getAttribute(attribute);
			}
			if (node && func)
			{
			    if (func = "childnodes")
			    {
			        //Returns the all node's child nodes
			        val = ArthurDent.utilities.serializeNodeContents(node);
			        //remove the current children as the returned value may be 100% different
			        while (node.hasChildren)
			        {
			            node.removeChild(node.firstChild());
			        }
			    }
			}
			if (node && !attribute && !func) {
				//get text node
				if (node.childNodes)
				{
					for (var i=0; i<node.childNodes.length; i++)
					{
						val += (node.childNodes[i].nodeType==3 ? node.childNodes[i].nodeValue : "");
					}
				}
			}
			return val;
		},
		
		setNodeTextContent: function(obj,val) 
		{
			if (val!=null && obj!=null)
			{
				try {
					obj.textContent = val
				} catch(E) {
					obj.text = val;
				}
			} 
			return obj ? (obj.text) ? obj.text : (obj.textContent) ? obj.textContent : "" : null;
		},
		
		/* DOM */
		getElementsByClassName: function(className, tag, elm) 
		{
			var testClass = new RegExp("(^|\\\\s)" + className + "(\\\\s|$)");
			var tag = tag || "*";
			var elm = elm || document;
			var elements = (tag == "*" && elm.all)? elm.all : elm.getElementsByTagName(tag);
			var returnElements = [];
			var current;
			var length = elements.length;
			for(var i=0; i<length; i++){
				current = elements[i];
				if(testClass.test(current.className)){
					returnElements.push(current);
				}
			}
			return returnElements;
		},
		
		getElementsByAttribute: function(elm, atr, tag, atrVal)
		{
			var tag = tag || "*";
			var elm = elm || document;
			var arrElements = (tag == "*" && elm.all)? elm.all : elm.getElementsByTagName(tag);
			var arrReturnElements = new Array();
			var oAtrVal = (typeof atrVal != "undefined")? new RegExp("(^|\\s)" + atrVal + "(\\s|$)") : null;
			var oCurrent;
			var oAtr;
			for(var i=0; i<arrElements.length; i++){
				oCurrent = arrElements[i];
				oAtr = oCurrent.getAttribute(atr);
				if(typeof oAtr == "string" && oAtr.length > 0){
					if(typeof atrVal == "undefined" || (oAtrVal && oAtrVal.test(oAtr))){
						arrReturnElements.push(oCurrent);
					}
				}
			}
			return arrReturnElements;
		},
		
		/* Page */
		getUrlValue: function(str)
		{
			hu = window.location.search.substring(1);
			gy = hu.split("&");
			for (i=0;i<gy.length;i++) {
				ft = gy[i].split("=");
				if (ft[0] == str) {
					return ft[1];
				}
			}
		},
		
		
		/* String */
		trim : function (str) 
		{
		    if (str) str = str.replace(/^\s+|\s+$/g,"");		    
		    return str;
		}
	}
}