/*
// JaxScript 1.0
// JavaScript Algorithms for XML Web Services
// http://www.jaxcore.com/jaxscript/
//
// Copyright (c) 2010, Dan Steinman <info@jaxcore.com>
// JaxScript is a registered trademark of JaxCore (www.jaxcore.com).
// All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

	* Redistributions of source code must retain the above copyright notice, 
	  this list of conditions and the following disclaimer.
	* Redistributions in binary form must reproduce the above copyright notice, 
	  this list of conditions and the following disclaimer in the documentation 
	  and/or other materials provided with the distribution.
	* Neither the name of "JaxCore", nor "Dan Steinman" may be used to endorse 
	  or promote products derived from this software without specific prior 
	  written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

/*
About:
JaxScript is a JavaScript language extension that adds features commonly required in today's 
web applications.  JaxScript is written using simple, functional, stateless algorithms 
that contain no uses of prototype, classes, this, or new.  Visit JaxCore.com for 
documentation, demos, and tutorials.  JaxCore is developing a new commerical JavaScript 
class architecture and application framework that will elevate JaxScript's capabilities 
even further.  Sign up to our newsletter (http://www.jaxcore.com/newsletter/) to recieve 
more info as our projects progress.
*/

var jaxdoc = (function JaxDoc() {
	var library = {};
	
	function findParent(id) {
		var p = library;
		if (id.indexOf('.')) {
			var s = id.split('.');
			for (var i=0;i<s.length-1;i++) {
				var node = get(p,s[i]);
				if (node) p = node;
				else return null; // invalid id
			}
		}
		return p;
	}
	
	function get() {
		var l = arguments.length;
		if (l==0) return library;
		else if (l==1) {
			var a = arguments[0];
			if (a.indexOf('.')) {
				return get(findParent(a),a.split('.').pop());
			}
			else if (library[a]) {
				return library[a];
			}
		}
		else if (l==2) {
			var parent = arguments[0];
			var n = arguments[1];
			var t = ['class','function','property','method','package','literal','library'];
			for (var i in t) {
				var p = parent[t[i]];
				if (p) {
					for (var j=0;j<p.length;j++) {
						if (p[j].nodeName==n) return p[j];
					}
				}
			}
		}
	}
	
	function make(type) {
		return function(id,def,o) {
			var p = findParent(id);
			if (!p) {
				if (self.console&&self.console.error) self.console.error('jax.doc error: no parent node for '+id);
				return;
			}
			if (!p[type]) p[type] = [];
			var n = id.split('.');
			if (!o) o = {};
			o.nodeName = n.pop();
			o.nodeType = type;
			o.nodeId = id;
			o.definition = def;
			p[type].push(o);
		}
	}
	
	var addClass = make('class');
	var addFunction = make('function');
	var addProperty = make('property');
	var addMethod = make('method');
	var addLiteral = make('literal');
	var addLibrary = make('library');
	
	function addExample(id, ex) {
		var p = findParent(id);
		var c = id.split('.').pop();
	}
	
	addLibrary('jaxdoc','JavaScript Annotation and Documentation System',{
		title : 'JaxDoc',
		version : 1,
		author : 'Dan Steinman',
		date : new Date(2010,0,1,0,0,0).toGMTString(),
		url : 'http://www.jaxdoc.com',
		license : 'MIT'
	});
	
	return {
		get : get,
		addClass : addClass,
		addExample : addExample,
		addFunction : addFunction,
		addLiteral : addLiteral,
		addLibrary : addLibrary,
		addMethod : addMethod,
		addProperty : addProperty
	}
	
})();

function echo(o) {
	var s = inspect(o);
	var c = window.console;
	if (c && c.log) c.log(s);
}

function get(o,p) {
	if (!!o[p]) return o[p];
}

function set(o,p,v) {
	return o[p] = v;
}

function length(a) {
	return isArray(a)? a.length : 0;
}

function hasProperty(o,p) {
	if (isString(p)) {
		if (p.indexOf('.')>0) {
			var s = p.split('.');
			for (var i=0;i<s.length;i++) {
				o = o[s[i]];
				if (!o) return false;
			}
			return true;
		}
		else return typeof o[p]!='undefined';
	}
}

// exists(document);
// exists(document,'body')
// exists(document,'body.childNodes')
// exists(document,'body','location')
// exists(document,'body.childNodes','location.href')
// exists(document,['body','location'])
// exists(document,['body.childNodes','location.href'])
function exists(o,p) {
	if (!!o && !!p) {
		if (arguments.length>2) return existsArray(o,arguments,1);
		if (isString(p)) return hasProperty(o,p);
		if (isArray(p)) return existsArray(o,p);
	}
	return false;
}
function existsArray(o,a,start) {
	if (!a.length) return false;
	for (var i=start||0;i<a.length;i++)
		if (!exists(o,a[i])) return false;
	return true;
}


var type = {};

function enumerate(s) {
	var o = {};
	if (isString(s)) s = s.split(",");
	if (isArray(s)) {
		for (var i in s)
			o[s[i]] = parseInt(i);
		return o;
	}
}
jaxdoc.addFunction('enumerate','converts a comma separated string of words (or array of words) into an enumerable object',{
	param : [
		{types:type.String},
		{types:type.Array},
	],
	returns : type.Literal
});

function getFields(o) {
	var a = [];
	for (var i in o) {
		a[a.length] = i;
	}
	return a;
}
jaxdoc.addFunction('getFields','returns the fields of an object as an array of strings',{
	param : {obj : type.Object},
	returns : type.Array
});

function enumField(enm,field) {  // adds a field to an enumerable
	var i, c = 0;
	for (i in enm) {
		c = get(Math,'max')(c,enm[i]);
	}
	enm[field] = ++c;
	return c;
}

function isType(t,o) {
	var f = get(self,'is'+t);
	if (isFunction(f)) return f(o);
	return false;
}

function areType(t,a) {
	var l, fn, i;
	if (!!t && isArray(a)) {
		l = length(a);
		for (i=0;i<l;i++) {
			f = get(self,'is'+t);
			if (isFunction(f)) {
				if (!f(a[i])) return false;
			}
		}
		return true;
	}
	return false;
}

function defineType(t, fn, desc) {
	//t = ucfirst(t);
	if (typeof self['is'+t]=='undefined') {
		var v = enumField(type,t);
		self['is'+t] = fn;
		//echo('defined type.'+t+' ('+v+') as '+(desc||'[unknown]'));
	}
	else echo('addType() error: '+name+' is an invalid type');
}

// DISCRETE TYPES:

defineType('Null', function(o) {
	return o==undefined || typeof o=='undefined' || o==null || o=='';
},'an empty variable');

defineType('True', function(o) {
	return o===true;
},'true');

defineType('False', function(o) {
	return o===false;
},'false');

defineType('Boolean', function(o) {
	return isTrue(o) || isFalse(o);
},'true or false');

defineType('Integer', function(o) {
	return parseInt(o)==o;
},'a non-floating point number');

defineType('Float', function(o) {
	return parseFloat(o)==Number(o);
},'an integer or floating point number');

defineType('String', function(o) {
	return typeof o==='string';
},'JavaScript String object');

defineType('Object', function(o) {
	return typeof o==='object';
},'a JavaScript object of any type');

defineType('Array', function(o) {
	return Object.prototype.toString.apply(o) === '[object Array]';
},'a JavaScript Array object');

defineType('Function', function(f) {
	return typeof f=='function';
},'a JavaScript function of any type');

defineType('Literal', function(o) {
	if (isObject(o)) {
		var c = 0, i, v;
		for (i in o.prototype) c++
		if (c==0) {
			for (i in o) {
				v = o[i];
				if (isObject(v) || isFunction(v)) return false;
			}
			return true;
		}
	}
	return false;
},'a JavaScript object containing no functions or objects');

defineType('Enum', function(o) {
	if (isLiteral(o)) {
		for (var i in o) {
			if (!isInteger(o[i])) return false;
		}
		return true;
	}
	return false;
},'a JavaScript literal containing only integers');

/*defineType('XML', function(o) {
	if (isString(o)) {
		o = trim(o);
		if (o.indexOf('<')>-1 && o.indexOf('>')>-1) return true; // is that enough?
	}
},'a string containing xml tags');*/


function isTag(o,t) {
	return isNode(o) && !!o.nodeName && o.nodeName==t;
}

function arrayContains(a,o) {x
	return arrayIndexOf(a,o)>-1;
};
jaxdoc.addFunction('arrayContains','returns true if the array contains the given object',{
	param : {
		arr : type.Array,
		obj : type.Object
	},
	returns : type.Array
});

function arrayIndexOf(a,o,s) {
	if (isFunction(a.indexOf)) return a.indexOf(o);
	for (var i=(s||0); i<a.length; i++)
		if (a[i] === o) return i;
	echo('arrayRemove() : object '+inspect(o)+' is not a member of Array('+inspect(a)+')');
	return -1;
};
jaxdoc.addFunction('arrayIndexOf','returns the index of the array which contains the given object',{
	param : {
		arr : type.Array,
		obj : type.Object,
		start : type.Integer
	},
	returns : type.Array
});

function arrayRemove(a,o) {
	if (isArray(a)) {
		var i = arrayIndexOf(a,o);
		if (i>-1) a.splice(i,1);
		return a;
	}
	echo('arrayRemove() : argument 0 is not an array: '+inspect(a));
}
jaxdoc.addFunction('arrayRemove','removes a member from an array',{
	param : {
		arr : 'Object[]',
		member : 'Object'
	},
	returns : type.Array
});

// dialog(window,'stuff',1,function(){},{});
// if you click cancel it won't show any more
function dialog() {
	if (self.noDialog) return;
	var o,s='';
	for (var i=0;i<arguments.length;i++) {
		o = arguments[i];
		if (isObject(o)) s += inspect(o);
		else if (s.toString) s += o.toString();
		if (i<arguments.length-1) s += "\n";
	}
	if (!confirm(s)) {
		if (confirm("Quit?")) self.noDialog = true;
	}
}

function isReserved(w) {
	// JavaScript Reserved Words:
	var r = 'abstract,as,boolean,break,byte,case,catch,char,class,continue,const,debugger,'+
	'default,delete,do,double,else,enum,export,extends,false,final,finally,float,for,function,'+
	'goto,if,implements,import,in,instanceof,int,interface,is,long,namespace,native,new,'+
	'null,package,private,protected,public,return,short,static,super,switch,synchronized,'+
	'this,throw,throws,transient,true,try,typeof,use,var,void,volatile,while,with';
	return arrayContains(r.split(','),w);
}
jaxdoc.addFunction('id','a shortcut for document.getElementById, but if arguments[0] is an object just return it, otherwise lookup using document.getElementById()',{
	param : {word : type.String},
	returns : type.Boolean
});

function getDocument(d) {
	if (!!d) return d;
	else return self.document;
}
jaxdoc.addFunction('getDocument','helper function to return self.document in a safe way if none was chosen',{
	param : [
		{'null' : null},
		{str : type.Date}
	],
	returns : type.Integer
});

function enumerate(s) {
	var o = {};
	if (isString(s)) s = s.split(s,",");
	if (isArray(s)) {
		for (var i in s)
			o[s[i]] = parseInt(i);
		return o;
	}
}
jaxdoc.addFunction('id','a shortcut for document.getElementById, but if arguments[0] is an object just return it, otherwise lookup using document.getElementById()',{
	param : {id : type.String},
	returns : type.Enum
});

function isEmpty(o) {
	var i,c = 0;
	for (i in o) c++;
	return c == 0;
}
jaxdoc.addFunction('isEmpty','returns true if the object has no properties or methods',{
	param : {obj : type.Object},
	returns : type.Boolean
});

function ucfirst(s) {
	return s.charAt(0).toUpperCase()+s.substring(1);
}
jaxdoc.addFunction('ucfirst','upper-cases the first character and returns the new string',{
	param : {str : type.String},
	returns : type.String
});

function getSeconds(d) {
	if (!d) d = new Date();
	return d.getTime()/1000;
}
jaxdoc.addFunction('getSeconds','returns the number of seconds from January 1, 1970, or from the given date',{
	param : [
		{'null' : null},
		{str : type.Date}
	],
	returns : type.Integer
});

function round(n,d) {
	n = parseFloat(n);
	if (n!=0) {
		n = parseFloat(n);
		var p;
		p = !d? 1 : Math.pow(10,d||2);
		return Math.round(n*p)/p;
	}
	return 0;
}
jaxdoc.addFunction('round','rounds a floating point number to the given number of decimal points (2 by default)',{
	param : [
		{'null' : null},
		{str : type.Date},
		{str : type.Date, decimals : type.Integer}
	],
	returns : type.Integer
});

function trim(s) {
	return isString(s)? s.replace(/^\s+|\s+$/g,'') : '';
};
jaxdoc.addFunction('trim','strips whitespace from the start and end of a string and returns the new string',{
	param : {str : type.String},
	returns : 'string'
});

function inspect(o,r) {
	if (isString(o)) return o;
	if (isObject(o)) {
		var s = '{\n',t;
		var b = (r==false)?'\t\t':'\t';
		var v;
		for (var i in o) {
			v = o[i];
			if (isString(v)) s += b+i+' : "'+v.replace('"','\"')+'"';
			else if (isBoolean(v)) s += b+i+' : '+(v?'true':'false');
			else if (isFloat(v)) s += b+i+' : '+o[i];
			else if (isFunction(v)) s += b+i+' : "[Function]"';
			else if (isArray(v))  s += b+i+' : "['+t+']"';
			else if (isObject(v)) s += b+i+' : "[Object]"';
			s += ',\n';
		}
		s = s.substring(0,s.length-2)+'\n';
		if (r==false) s+= '\t';
		s += '}';
		return s;
	}
};
jaxdoc.addFunction('inspect','returns an examination of an object in json, this is a quick way to take a snapshot of an unknown object for output to the log',{
	param : [
		{elementId:'string'},
		{subject:'Object'},
		{subject:'Object',recurse:'boolean'}
	],
	returns : 'string'
});

function copy(t,s,p) {
	if (!t) t = {};
	if (!!p) {
		var i;
		for (var j in p) {
			i = p[j];
			//if (!s[i]) echo('copy(1) : sourceObj property "'+i+'" is null'); // enable for debugging
			t[i] = s[i];
		}
	}
	else for (var i in s) {
		//if (!s[i]) echo('copy(2) : sourceObj property "'+i+'" is null'); // enable for debugging
		t[i] = s[i];
	}
	return t;
}
jaxdoc.addFunction('copy','copies properties from sourceObj to targetObj and returns targetObj',{
	param : [
		{targetObj:'Object'},
		{targetObj:'Object',sourceObj:'Object'},
		{targetObj:'Object',sourceObj:'Object',properties:'string[]'},
	],
	returns : 'Object'
});


var jaxscript = (function() {

	var start = getSeconds();
	var runs = [];
	var busy = true;
	var loaded = false;
	var supported = false;
	var version = 0.1;
	
	jaxdoc.addLibrary('jaxscript','JavaScript Algorithms for XML Web Services',{
		title : 'JaxScript',
		version : version,
		date : new Date(2010,0,1,0,0,0).toGMTString(),
		author : 'Dan Steinman',
		url : 'http://www.jaxcore.com/jaxscript',
		license : 'MIT'
	});
	
	function main() {
		busy = false;
		loaded = true;
		for (var i=0;i<runs.length;i++) {
			runs[i]();
		}
		echo('jaxscript types are '+getFields(type).join(', '));
		echo('jaxscript client is '+client.getInfo());
		echo('jaxscript server is '+server.getHost());
		echo('jaxscript '+version+' loaded in '+round(getSeconds()-start,3)+' seconds');
		
	};
	
	var server = (function() {
		
		function getRoot() {
			return getProtocol()+'://'+getHost();
		}
		
		function getProtocol() {
			return location.protocol;
		}
		
		function getHost() {
			return location.host;
		}
		
		function getURI() {
			return location.href;
		}
		
		function getParams() {
			//return transform.param2obj(location.search);
		}
		
		var staticHost = getHost();
		
		function getStaticHost() {
			return staticHost;
		}
		
		function setStaticHost(h) {
			staticHost = h;
		}
		
		return {
			getRoot : getRoot,
			getProtocol : getProtocol,
			getHost : getHost,
			getURI : getURI,
			getParams : getParams,
			getStaticHost : getStaticHost,
			setStaticHost : setStaticHost
		};
		
	})();
	
	var client = (function() {
		jaxdoc.addLibrary('client','library containing functions related to identification of the client');

		var features = {};
		
		function getInfo() {
			return (typeof navigator=='object')? navigator.userAgent : '';
		}
		
		function getPlugin(s) {
			var a = navigator.plugins;
			if (a.length>0)
				for (i=0;i<a.length;i++)
					if (a[i].name.indexOf(s) > -1)
						return a[i];
		}
		
		jaxdoc.addFunction('client.getPlugin','returns a web browser plugin by the given name',{
			param : {name : type.String},
			returns : 'Object'
		});
		jaxdoc.addExample('client.getPlugin',
			'var flash = jaxscript.getPlugin("Flash");\n'+
			'var version = flash.description;  // contains "10.0 \n'+
			'if (flash) alert("You are using Flash version "+version);');
		
		function addFeature(id,name,test) {
			features[id] = {
				name : name,
				test : test
			};
		}
		jaxdoc.addFunction('client.addFeature','adds a feature checking function to the client library',{
			param : {
				id : type.String, 
				name : type.String,
				test : type.Function
			},
			returns : 'Object'
		});
		
		function supports(id,v) {
			if (features[id]) return features[id].test(v);
			return false;
		}
		
		addFeature('activex','ActiveX',function() {
			return typeof ActiveXObject=='object' || typeof ActiveXObject=='function';
		});
		
		addFeature('fixed','CSS-P Fixed Position extension',function(v) {
			// TO DO: Internet Explorer supports css fixed only if a !DOCTYPE is specified
			return !supports('activex');
		});
		addFeature('cookies','Browser cookies',function(v) {
			document.cookie = "1";
			return document.cookie.indexOf("1")>-1;
		});
		addFeature('dom','Document Object Model',function(v) {
			if (!v) v = 1;
			var cv = 0;
			// )
			if (
				( typeof document.addEventListener=='function' && 
				(typeof DOMParser=='function'||typeof DOMParser=='object') && 
				(typeof XMLSerializer=='function'||typeof XMLSerializer=='object') && 
				(typeof XSLTProcessor=='function'||typeof XSLTProcessor=='object') ) || 
				(supports('activex') && typeof self.attachEvent=='object' && typeof document.getElementById=='object')
				) cv = 2;
			return cv >= v;
		});
		addFeature('ecma','ECMAScript',function(v) {
			if (!v) v = 1;
			var cv = 0;
			if (typeof [].pop=='function' && typeof parseFloat=='function' && typeof decodeURIComponent=='function')
				cv = 3;
			return cv >= v;
		});
		addFeature('flash','Adobe Shockwave Flash',function(v) {
			return !!getPlugin("Shockwave Flash");
			// to do : versions
		});
		addFeature('gecko','Gecko HTML Rendering Engine',function() {
			return getInfo().toLowerCase().indexOf("gecko")>-1 && !supports("webkit");  // webkit pretends to be gecko
		});
		addFeature('webkit','WebKit HTML Rendering Engine',function() {
			return getInfo().toLowerCase().indexOf("webkit")>-1;
		});
		addFeature('safari','Safari version of WebKit',function() {
			return supports("webkit") && getInfo().toLowerCase().indexOf("safari")>-1 && !supports("chrome");  // chrome pretends to be safari
		});
		addFeature('chrome','Chrome version of WebKit',function() {
			return supports("webkit") && getInfo().indexOf("Chrome")>-1;
		});
		addFeature('iphone','Apple iPhone/iPod',function() {
			return supports("webkit") && getInfo().indexOf("iPhone")>-1 || getInfo().indexOf("iPod")>-1;
		});
		addFeature('java','Sun Microsystems Java',function() {
			return typeof java=='object' && !!getPlugin("Java");
		});
		addFeature('js','JavaScript',function(v) {
			if (!v) v = 1;
			var cv = 1.5; // assume client supports 1.5
			if (typeof [].indexOf=='function' && typeof [].forEach=='function')
				cv = 1.6;
			return cv > v;
		});
		addFeature('xhr','XMLHTTPRequest communication',function() {
			return supports('activex') || typeof XMLHttpRequest=='function' || typeof XMLHttpRequest=='object';
		});
	
		return {
			getPlugin : getPlugin,
			getInfo : getInfo,
			supports : supports,
			addFeature : addFeature
		};
		
		//mousewheel : false,
		//DOMMouseScroll : false
	})();
	
	//alert(client.supports('dom',2) +' '+ client.supports('xhr') +' '+ client.supports('ecma',3)+' '+typeof document.attachEvent);
	
	if (client.supports('dom',2) && client.supports('xhr') && client.supports('ecma',3)) {
		if (typeof document.onreadystatechange=='object') { // for IE 5/6 support
			supported = true;
			document.onreadystatechange = function() {
				if (document.readyState == "complete") {
					main();
				}
			};
		}
		else if (typeof document.addEventListener=='function') {
			supported = true;
			document.addEventListener("DOMContentLoaded", main, true);  // ff, opera, safari
		}
	}
	
	if (!isSupported()) unsupported();
	
	function isBusy() {
		return busy;
	}
	jaxdoc.addFunction('jaxscript.isBusy','returns true before the DOM has been initialized and during XHR requests',{
		returns : type.Boolean
	});
	
	function isLoaded() {
		return !busy && loaded;
	}
	jaxdoc.addFunction('jaxscript.isLoaded','returns whether the DOM has been initialized and all required JaxScript libraries are loaded',{
		returns : type.Boolean
	});
	
	function isSupported() {
		return supported;
	}
	jaxdoc.addFunction('jaxscript.isSupported','returns whether the web browser is supported by JaxScript',{
		returns : type.Boolean
	});
	
	function unsupported() {
		if (confirm('Sorry. Your web browser is unsupported.\n\nWould you like to upgrade?'))
			top.location = 'http://www.jaxcore.com/upgrade/';
	};
	jaxdoc.addFunction('unsupported','this function is called if the web browser is not supported by JaxScript, it is intended that you overwrite this function with your desired handling');
	
	function run(f) {
		runs.push(f);
	};
	jaxdoc.addFunction('jaxscript.run','defines a handler function to be executed after the DOM is initialized and all required JaxScript libraries are loaded',{
		param : {handler : type.Function}
	});
	
	function request(o) {
		if (!client.supports('xhr')) return unsupported();
		o = o||{};
		if (!o.method) o.method = "get";
		if (o.async==null) o.async = true;
		if (o.cache===false) url += url.indexOf('?')>0?'&':'?'+'nocache='+Math.random().toString().substring(2);
		var r = (typeof XMLHttpRequest=='function')? new XMLHttpRequest():new ActiveXObject("Microsoft.XMLHTTP");
		r.open(o.method,o.url,o.async);
		r.onreadystatechange = function(){
			if (r.readyState==4) {
				if (r.status == 200 && typeof o.handler=='function') return o.handler(r);
				if (typeof o.errorHandler=='function') o.errorHandler(r);
			}
		};
		r.send('');
		return r;		
	};
	jaxdoc.addFunction('jaxscript.request',{
		definition : 'this is JaxScript\'s XMLHttpRequest function, options are {url,method,async,data,handler,errorHandler}, returns the XMLHttpRequest result object',
		param : {options:type.Literal},
		returns : type.Object
	});
	
	return {
		client : client,
		server : server,
		isBusy : isBusy,
		isLoaded : isLoaded,
		isSupported : isSupported,
		unsupported : unsupported,
		run : run,
		request : request
	};

})();

// DOM Types:

defineType('Node', function(n,t) {
	var b = (isObject(n) && exists(n,['nodeType','nodeName']));
	if (b && isString(t))
		return n.nodeName.toUpperCase()==t.toUpperCase();
	return b;
},'a DOM Node');

defineType('Document', function(o) {
	return isNode(o) && o.nodeType==1;
},'a DOM Document');

defineType('Element', function(o) {
	return isNode(o) && isChild(o,document.body);
},'a DOM Node that is a child of document.body');

defineType('Event', function(e) {
	if (!!self.event) return e===self.event;
	else return (isObject(e) && !!e.type);  // o.src should maybe make a better test
},'a DOM Event object');

// DOM FUNCTIONS:

/* because id() and dimensions() are so commonly used I prefer them to be outside the dom. library */

function id(s,d) {
	if (isObject(s)) return s;
	if (isString(s)) {
		d = getDocument(d);
		if (exists(d,'getElementById')) {
			var o = d.getElementById(s);
			if (!!o) return o;
			//echo('id() : element not found using document.getElementById("'+s+'")');
		}
	}
};
jaxdoc.addFunction('id','a shortcut for document.getElementById, but if arguments[0] is an object just return it, otherwise lookup using document.getElementById()',{
	param : [
		{id : type.String},
		{node : type.Node}
	],
	returns : type.Node
});

function dimensions(o,frame) {
	var x=y=w=h=0;
	var f = !!frame? frame : window;
	var d = f.document;
	var b = d.body;
	var de = d.documentElement;
	if (o==document) {
		// document size
		if (exists(f,'innerWidth','innerHeight','scrollMaxY','scrollMaxY')) { // ff
			w = f.innerWidth + f.scrollMaxX;
			h = f.innerHeight + f.scrollMaxY;
		}
		else if (exists(b,'scrollWidth','offsetWidth','scrollHeight','offsetHeight')) { // dom
			w = (b.scrollWidth > b.offsetWidth)? b.scrollWidth : b.offsetWidth;
			h = (b.scrollHeight > b.offsetHeight)? b.scrollHeight : b.offsetHeight;
		}
		else if (exists(f,'innerHeight','innerHeight')) { // ie
			w = f.innerWidth,
			h = f.innerHeight
		}
		else if (exists(b,'offsetWidth','offsetHeight')) {
			echo('dimensions(document) : falling back to offsetWidth/Height');
			w = b.offsetWidth;
			h = b.offsetHeight;
		}
		
		// document scroll
		if (exists(b,'scrollLeft','scrollTop')) {
			x = b.scrollLeft;
			y = b.scrollTop;
		}
		if (x==0 && y==0 && exists(de,'scrollLeft','scrollTop')) {
			x = de.scrollLeft;
			y = de.scrollTop;
		}
		
		//echo('dimensions(document) = w:'+w+', h:'+h+', scroll x:'+x+', y:'+y);
	}
	else if (o==window) {
		// inner width/height of the window
		if (exists(f,'innerWidth','innerHeight')) {
			w = f.innerWidth;
			h = f.innerHeight;
		}
		else if (exists(b,'clientWidth','clientHeight')) {
			w = b.clientWidth;
			h = b.clientHeight;
		}
		else if (exists(de,'clientWidth','clientHeight')) {
			w = de.clientWidth;
			h = de.clientHeight;
		}
		
		// window position
		x = (jaxscript.client.supports('activex'))? f.screenLeft : f.screenX;
		y = (jaxscript.client.supports('activex'))? f.screenTop : f.screenY;
		
		//echo('dimensions(window) = x:'+x+', y:'+y+', w:'+w+', h:'+h,'i');
	}
	else {
		o = id(o);
		if (!!o) {
			var sx = 0;
			if (b && b.scrollLeft) sx = b.scrollLeft;
			if (de && de.scrollLeft) sx = de.scrollLeft;
			var sy = 0;
			if (b && b.scrollTop) sy = b.scrollTop;
			if (de && de.scrollTop) sy = de.scrollTop;
			
			var r;
			if (o.getBoundingClientRect) { // CSS3
				r = o.getBoundingClientRect();
				x = r.left + sx;
				y = r.top + sy;
				w = r.right - r.left;
				h = r.bottom - r.top;
			}
			else if (d.getBoxObjectFor) { // FF
				r = d.getBoxObjectFor(o);
				x = r.x;
				y = r.y;
				w = r.width;
				h = r.height;
			}
			else {  // the old fashioned way
				return offsetDimensions(o);
			}
		}
		//echo('dimensions('+(exists(o,'nodeName','id')?o.nodeName+'#'+o.id:'element')+') = x:'+x+', y:'+y+', w:'+w+', h:'+h);
	}
	return {x:x,y:y,w:w,h:h};
};
function offsetDimensions(o) {
	var x=y=0;
	var w = o.offsetWidth;
	var h = o.offsetHeight;
	while (o.offsetParent) {
		x += o.offsetLeft;
		y += o.offsetTop;
		o = o.offsetParent;
	}
	var d = document;
	x += (d.body.scrollLeft || d.documentElement.scrollLeft || 0);
	y += (d.body.scrollTop || d.documentElement.scrollTop || 0);
	return {x:x,y:y,w:w,h:h};
};
jaxdoc.addFunction('dimensions','returns {x,y,w,h} containing the absolute position (x=left,y=top) and size (w=width,h=height) of the element, also includes window and document (document scroll position is available in the {x,y} properties)',{
	param : [
		{node:'Node'},
		{win:'Window'}
	],
	returns : 'JSON'
});

function createConsole() {
	jaxscript.run(function() {
		var c = jaxscript.console = document.createElement('div');
		c.id = "jaxconsole";
		style.set(c,{position:'absolute',overflow:'scroll',right:0,bottom:0,width:'350px',height:'200px',background:'#fff',border:'1px solid grey',fontSize:'8pt'});
		dom.append(c);
	});
}

function print(s,d) {
	if (jaxscript.isLoaded()) dom.append(s);
	else {
		d = getDocument(d);
		if (d) d.write(s);
	}
}
jaxdoc.addFunction('print','prints text to the document, this can be called before or after the DOM initialized',{
	param : [
		{str : type.String},
		{str : type.String, doc : type.DOMDocument}
	]
});

function println(s,d) {
	if (!jaxscript.isBusy() && jaxscript.isLoaded()) {
		var e = document.createElement('div');
		e.className = "println";
		e.innerHTML = s;
		dom.append(e); 
	}
	else {
		d = getDocument(d);
		if (d) d.write('<div class="println">'+s+'<\/div>');
	}
}
jaxdoc.addFunction('println','like print(), but prints a new line of text to the document by wrapping the text in a DIV element, this can be called before or after the DOM initialized',{
	param : [
		{str : type.String},
		{str : type.String, doc : type.DOMDocument}
	]

});

// DOM LIBRARY:

var dom = jaxscript.dom = (function() {

	jaxdoc.addLibrary('dom','DOM-related functions and server communication');
	
	function addClass(n,c) { // needs testing
		n = id(n);
		if (n.nodeType==1) {
			if (!hasClass(n,c)) {
				if (n.className.indexOf(' ')) n.className = " "+c;
				else n.className = c;
			}
		}
		return n;
	};
	jaxdoc.addFunction('dom.addClass','adds a class name to an element',{
		param : [
			{node:type.Node, className:type.String},
			{nodeId:type.String, className:type.String}
		]
	});
	
	function children(n) {
		if (n && n.childNodes && n.childNodes.length>0) return n.childNodes;
		else return [];
	}
	
	function findParent(n,tag) {
		n = id(n);
		while (n.parentNode) {
			if (n.parentNode.tagName.toUpperCase()==tag.toUpperCase())
				return n.parentNode;
			n = n.parentNode;
		}
	};
	jaxdoc.addFunction('dom.findParent','travels up the DOM tree and returns the node that matches the given tag name',{
		param : [
			{node:type.Node, tagName:type.String},
			{nodeId:type.String, tagName:type.String}
		],
		returns : type.Node
	});
	
	function findClass(node, tagAndOrClass) {
		node = id(node);
		var r = [];
		if (!node) return r;
		var n;
		var dot = tagAndOrClass.indexOf('.');
		if (dot>=0) {  // handle tag + className which is more efficient than searching just by className
			var tagname = tagAndOrClass.substring(0,dot);
			var className = tagAndOrClass.substring(dot+1);
			var nodes = node.getElementsByTagName(tagname);
			for (var i=0;i<nodes.length;i++) {
				nd = nodes[i];
				if (hasClass(nd,className)) r.push(nd);
			}
		}
		else {  // walk through the entire dom tree (try to avoid this when possible)
			walkDOM(node, function(nd) {
				if (hasClass(nd,cname)) r.push(nd);
			});
		}
		return r;
	};
	jaxdoc.addFunction('dom.findClass','examines an element and returns an array of DOM nodes that match the given class name or tag + class name ("className" or "tagName.className")',{
		param : [
			{node:type.Node, tagAndOrClass:type.String},
			{elementId:type.String, tagAndOrClass:type.String}
		],
		returns : 'Node[]'
	});
	
	function hasClass(node,cname) {
		return !!node.className && (node.className==cname || arrayIndexOf(node.className.split(' '),cname)>-1);
	};
	jaxdoc.addFunction('dom.hasClass',{
		definition : 'returns whether the node has a given class name',
		param : [
			{node:type.Node, className:type.String},
			{nodeId:type.String, className:type.String}
		]
	});
	
	function insertTag(o) {
		var s = document.createElement(o.tagName);
		for (var i in o.attributes) {
			s.setAttribute(i,o.attributes[i]);
		}
		// if (o.insertAfter) insertAfter(s, o.insertAfter)
		// else if (o.insertBefore) insertBefore(s, o.insertBefore)
		// else if (o.insertFirst) insertFirst(s, o.insertFirst)
		// else if (o.insertLast) insertLast(s, o.insertLast)
		// will replace the following
		if (o.elementAppend) o.elementAppend.appendChild(s);
		else if (o.elementBefore) o.elementBefore.insertBefore(s,o.elementBrother);
		else document.body.appendChild(s);  // appends to body by default
		
		
		var h = o.handler;
		if (typeof h=='function') {
			if (typeof s.onreadystatechange=='Object') {
				s.onreadystatechange = function() {
					if (s.readyState=='loaded') h();
				};
				return;
			}
			if (/webkit/i.test(ua)) { 
				// script.onload is not available in Safari but document readyState is useful
				var t = setInterval(function() {
					if (document.readyState=='complete') {
						clearInterval(t);
						h();
					}
				},10);
			}
			else s.onload = h;
		}
		return s;
	};
	jaxdoc.addFunction('dom.insertTag',{
		definition : 'inserts an element into the document before or after an existing element (appends to body by default) and returns the node, \
		options are: {tag,handler,attributes,elementAppend,addBefore,elementBrother}',
		param : {options : type.Literal},
		returns : type.Node
	});
	
	function after(n, s) {
		//if (exists(s,'parentNode.insertBefore')) 
		s.parentNode.insertBefore(id(n), s.nextSibling);
	};
	jaxdoc.addFunction('dom.after',{
		definition : 'inserts a new node as a sibling after another node',
		param : [
			{node:type.Node, sibling:type.Node},
			{nodeId:type.String, siblingId:type.String},
		]
	});

	function before(n, s) {
		//if (exists(s,'parentNode.insertBefore')) 
		s.parentNode.insertBefore(id(n), s);
	}
	jaxdoc.addFunction('dom.before',{
		definition : 'inserts a new node as a sibling after another node',
		param : [
			{node:type.Node, sibling:type.Node},
			{nodeId:type.String, siblingId:type.String},
		]
	});
	
	function hasChildren(o) {
		return isNode(o) && o.childNodes.length>0;
	}
	
	function prepend(n, p) {
		if (!p && exists(document,'body')) p = document.body;
		if (hasChildren(n)) before(n, p.childNodes[0]);
		else append(n, p);
		return n;
	}
	jaxdoc.addFunction('dom.insertFirst','inserts a new node as the first child of another node',{
		param : [
			{node:type.Node, sibling:type.Node},
			{nodeId:type.String, siblingId:type.String}
		],
		returns : type.Node
	});
	
	function append(n, p) {
		if (jaxscript.isLoaded()) {
			if (!p && exists(document,'body')) {
				p = document.body;
			}
			if (!!p && !!p.appendChild) p.appendChild(id(n));
		}
		else echo("Error: append() cannot be called before the DOM is initialized");
		return ;
	}
	jaxdoc.addFunction('dom.append',{
		definition : 'inserts a new node as the last child of another node',
		param : [
			{node:type.Node, sibling:type.Node},
			{nodeId:type.String, siblingId:type.String},
		],
		returns : type.Node
	});

	function isChild(n,p) {
		return isParent(p,n);
	};
	jaxdoc.addFunction('dom.isChild',{
		definition : 'returns whether elementA is a child of elementB',
		param : [
			{elementA:type.Node, elementB:type.Node},
			{elementIdA:type.String, elementIdB:type.Node},
			{elementA:type.Node, elementBid:type.String},
			{elementIdA:type.String, elementIdB:type.String},
		],
		returns : type.Boolean
	});
	
	function isParent(p,n) {
		p = id(p);
		n = id(n);
		if (jaxscript.client.supports('activex') && typeof p.contains == 'function' && n.nodeType == 1) {  // this is not tested
			return p == n || p.contains(n);
		}
		while (n) {
			if (n===p) return true;
			if (!!n.parentNode) n = n.parentNode;
			else return false;
		}
		return false;
	};
	jaxdoc.addFunction('dom.isParent',{
		definition : 'returns whether elementA is the parent of elementB',
		param : [
			{elementA:type.Node, elementB:type.Node},
			{elementIdA:type.String, elementIdB:type.Node},
			{elementA:type.Node, elementBid:type.String},
			{elementIdA:type.String, elementIdB:type.String},
		],
		returns : type.Boolean
	});
	
	function isSibling(n,s) { // needs testing
		s = id(s);
		var c = id(n).parentNode.childNodes;
		for (var i=0;i<c.length;i++)
			if (c[i]==s) return true;
		return false;
	};
	jaxdoc.addFunction('dom.isSibling',{
		definition : 'returns whether elementA is a sibling of elementB',
		param : [
			{elementA:type.Node, elementB:type.Node},
			{elementIdA:type.String, elementIdB:type.Node},
			{elementA:type.Node, elementBid:type.String},
			{elementIdA:type.String, elementIdB:type.String},
		],
		returns : type.Boolean
	});
	
	function loadCSS(s,fn) {
		/*
		if (!jaxscript.isLoaded()) document.write('<link rel="stylesheet" type="text/css" src="'+js+'"><\/script>');
		*/
		return insertTag({
			tagName : 'link',
			handler : fn,
			attributes : {
				href : s,
				type : 'text/css',
				rel : 'stylesheet'
			},
			elementAppend : document.getElementsByTagName('head')[0]
		});
	};
	jaxdoc.addFunction('dom.loadCSS',{
		definition : 'inserts a script into the head of the document and returns the link node, calls handler function when the file is loaded',
		param : {
			url : type.String,
			handler : type.Function
		},
		returns : type.Node
	});
	
	function loadJS(s,fn) {
		/*
		if (!jaxscript.isLoaded()) {
			document.write('<script type="text/javascript" src="'+js+'"><\/script>');
			if (isFunction(fn)) fn();
		}
		*/
		return insertTag({
			tagName : 'script',
			handler : fn,
			attributes : {
				src : s,
				type : 'text/javascript'
			},
			elementAppend : document.getElementsByTagName('head')[0]
		});
	};
	jaxdoc.addFunction('dom.includeJS',{
		definition : 'appends a javascript file to the head of the document and returns the script node, calls handler function when the file is loaded, and returns the script tag',
		param : {url:type.String,handler:type.Function},
		returns : type.Node
	});

	function loadDOM(url) {
		// loadDOM does not have a handler because it is a synchronous request
		var r = jax.request({
			url : url,
			method : "get",
			async : false
		});
		return r.responseXML;
	};
	jaxdoc.addFunction('dom.loadDOM',{
		definition : 'loads an XML file synchronously and returns the DOM',
		param : {xmlURL:type.String},
		returns : type.DOMDocument
	});
	
	function replaceClass(n,className,newClassName) {
		n = id(n);
		if (hasClass(n,className)) n.className = n.className.replace(className,newClassName);
		return n;
	}; 
	jaxdoc.addFunction('dom.replaceClass',{
		definition : 'replaces an element\'s class name with a new one, and returns the node',
		param : [
			{node:type.Node, className:type.String, newClassName:type.String},
			{nodeId:type.String, className:type.String, newClassName:type.String}
		],
		returns : type.Node
	});
	
	function removeClass(n,c) {
		if (hasClass(n,c)) n.className = trim(n.className.replace(c,'').replace('  ',''));
	};
	jaxdoc.addFunction('dom.removeClass',{
		definition : 'removes a given class name from an element',
		param : [
			{node:type.Node, className:type.String},
			{nodeId:type.String, className:type.String}
		]
	});

	function walk(n, fn) {
		if (n.nodeName && n.childNodes) {
			fn(n);
			n = n.firstChild;
			while (n) {
				walk(n,f);
				n = n.nextSibling;
			}
		}
		else if (isArray(n)) { // also handle an array of nodes
			for (var i in n) {
				fn(n[i]);
			}
		}
	};
	jaxdoc.addFunction('dom.walk',{
		definition : 'examines a DOM node (or array of nodes) and recursively passes each child as a parameter for a handler function',
		param : {
			node : type.Node,
			handler : type.Function
		}
	});
	
	function addEvent(n,e,h,p) {
		n = id(n);
		if (n) {
			if (n.addEventListener) n.addEventListener(e,h,(p==null)?false:p);
			else if (n.attachEvent) n.attachEvent("on"+e,h);
		}
		else echo('addEvent node does not exists');
	}
	jaxdoc.addFunction('dom.addEvent',{
		definition : 'adds an event handler &><\'" to an element',
		param : [
			{element:type.Element,eventName:type.String,handler:type.Function,propagateEvent:type.Boolean},
			{elementId:type.String,eventName:type.String,handler:type.Function,propagateEvent:type.Boolean}
		]
	});
	
	function removeEvent(n,e,h,p) {
		n = id(n);
		if (n) {
			if (n.removeEventListener) n.removeEventListener(e,h,(p==null)?false:p);
			else if (n.detachEvent) n.detachEvent("on"+e,h);
		}
	}
	jaxdoc.addFunction('dom.removeEvent',{
		definition : 'removes an event handler from a given node',
		param : [
			{element:type.Node,eventName:type.String,handler:type.Function,propagateEvent:type.Boolean},
			{elementId:type.String,eventName:type.String,handler:type.Function,propagateEvent:type.Boolean}
		]
	});
	
	function cancelEvent(e) {
		e.cancelBubble = true;
		e.returnValue = false;
		if (e.stopPropagation) e.stopPropagation();
		if (e.preventDefault) e.preventDefault();
		return false;
	};
	jaxdoc.addFunction('dom.cancelEvent',{
		definition : 'cancels event propagation and bubbling',
		param : {e : type.Event},
		returns : type.Boolean
	});
	
	function eventPosition(e) {
		if (window.event) {
			e = window.event;
			var b = window.document.body;
			return {
				x : e.clientX + b.scrollLeft,
				y : e.clientY + b.scrollTop
			};
		}
		else if (exists(e,'pageX','pageY')) return {x:e.pageX,y:e.pageY};
		else return null;
	};
	jaxdoc.addFunction('dom.eventPosition',{
		definition : 'returns the absolute mouse position (including scroll position) as {x,y}',
		param : {e : type.Event},
		returns : type.Literal
	});
	
	function relatedTarget(e) {
		var r = e.relatedTarget;
		if (r) {
			try {
				r.nodeName;
			}
			catch (e) {
				if (r.nodeType==3) return r.parentNode; // skip text nodes
				echo('error: relatedTarget() had an invalid node');
				return null;
			}
			return r;
		}
		if (window.event) {
			if (e.type=="mouseover" && window.event.fromElement) return window.event.fromElement;
			if (e.type=="mouseout" && window.event.toElement) return window.event.toElement;
		}
	}
	jaxdoc.addFunction('dom.relatedTarget',{
		definition : 'returns the element the mouse came "from" during a mouseover event, or the element the mouse went "to" during a mouseout event',
		param : {e : type.Event},
		returns : type.Node
	});
	
	function eventTarget(e) {
		var t = (e && e.target)? e.target : window.event.srcElement;
		return (t.nodeType == 3)? t.parentNode:t; // skip text nodes
	};
	jaxdoc.addFunction('dom.eventTarget',{
		definition : 'returns the element the mouse moved "to" during a mouseout event',
		param : {e : type.Event},
		returns : type.Node
	});
	
	return {
		addClass : addClass,
		children : children,
		findParent : findParent,
		findClass : findClass,
		hasClass : hasClass,
		insertTag : insertTag,
		
		append : append,
		prepend : prepend,
		after : after,
		before : before,

		isChild : isChild,
		isParent : isParent,
		isSibling : isSibling,
		loadCSS : loadCSS,
		loadJS : loadJS,
		loadDOM : loadDOM,
		removeClass : removeClass,
		replaceClass : replaceClass,
		walk : walk,
		
		addEvent : addEvent,
		removeEvent : removeEvent,
		cancelEvent : cancelEvent,
		eventPosition : eventPosition,
		relatedTarget : relatedTarget,
		eventTarget : eventTarget
	};
	
})();

// STYLE TYPES:

defineType('CSSDelcaration', function(o) {
	if (isString(o)) return o.indexOf(':')>1;
},'a CSS declaration (the stuff inside CSS brackets)');

defineType('CSSRule', function(o) {
	if (isString(o)) return o.indexOf(':')>1;
},'a CSS Rule');

defineType('Style', isLiteral, 'a JavaScript literal representing CSS declarations');

// STYLE FUNCTIONS:

var style = jaxscript.style = (function() {
	
	function clip(n,i) {
		return set(n,{
			clip : (i&&i.length==4)? 'rect('+i[0]+'px '+i[1]+'px '+i[2]+'px '+i[3]+'px)' : 'auto'
		});
	};
	jaxdoc.addFunction('dom.clip','sets "clip" style using a 4-element array [top,right,bottom,left]',{
		param : [
			{node:type.Node,clipArray:type.Array},
			{nodeId:type.String,clipArray:type.Array}
		],
		returns : type.Node
	});
	jaxdoc.addExample('dom.clip("MyDiv",[0,100,70,0]);  // clips #MyDiv to width 100 and height 70;');
	
	function display(n,b) {
		return set(n,{
			display : b? 'block' : 'none'
		});
	};
	jaxdoc.addFunction('dom.display','sets the "display" style of an element to either "block" (true) or "none" (false)',{
		param : [
			{node:type.Node,displayed:type.Boolean},
			{nodeId:type.String,displayed:type.Boolean}
		],
		returns : type.Node
	});
	jaxdoc.addExample('var mydiv = dom.id("MyDiv");\n'+
		'dom.display("MyDiv",false); \n'+
		'alert(mydiv.style.display); // returns "none"\n'+
		'dom.display("MyDiv",true);\n'+
		'alert(mydiv.style.display); // returns "block"');
	
	function getClip(n) {
		n = id(n);
		var c = n.style.clip;
		if (c && c.indexOf('rect(') == 0) {
			c = c.replace("rect(","");
			c = c.replace(")","");
			var v = c.split(" ");
			for (var i in v) v[i] = parseInt(v[i]);
			return v;
		}
		else return [0, n.offsetWidth, n.offsetHeight, 0];
	};
	jaxdoc.addFunction('dom.getClip','returns "clip" style as 4-element array [top,right,bottom,left]',{
		param : {node:type.Node},
		returns : type.Array
	});
	jaxdoc.addExample('dom.setClip("MyDiv",[0,100,70,0]);\n'+
		'alert(dom.getClip("MyDiv"));   // returns [0,100,70,0]');
	
	function getOpacity(n) {
		n = id(n);
		return isFloat(n.style.opacity)? parseFloat(n.style.opacity) : 1;
	};
	jaxdoc.addFunction('dom.getOpacity','reads the _opacity property set in dom.opacity() if not available, otherwise returns 1',{ 
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Integer
	});
	jaxdoc.addExample('alert(dom.getOpacity("MyDiv")); // returns the opacity of MyDiv');
	
	function get(n,s) {
		n = id(n);
		if (isString(s)) {
			if (s=="opacity") return isFloat(n.style.opacity)? n.style.opacity : 1;
			if (!!document.defaultView) {
				var v = document.defaultView.getComputedStyle(n,"").getPropertyValue(s);
				return v;
			}
			else if (n.currentStyle) return n.currentStyle[s];
			else if (n.style[s]) return n.style[s];
			else if (isNode(n)) echo('error: dom.get() could not obtain style for node '+node.nodeName+'#'+n.id);
		}
	};
	jaxdoc.addFunction('dom.get','a shortcut for "element.style.property"',{
		param : [
			{node:type.Node,style:type.String},
			{nodeId:type.String,style:type.String}
		],
		returns : 'Object'
	});
	jaxdoc.addExample('var mydiv = dom.id("MyDiv");\n'+
		'mydiv.style.backgroundColor = "red"\n'+
		'var bg = dom.get(mydiv,"backgroundColor");\n'+
		'alert(bg):  // returns "red" or "#ff0000"');
	
	function getXY(n) {
		n = id(n);
		return {
			x : parseInt(get(n,'left')),
			y : parseInt(get(n,'top'))
		}
	};
	jaxdoc.addFunction('dom.getXY','returns the left and top position of an element as a JSON set {x,y}',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Literal
	});
	jaxdoc.addExample('// consider : <div id="MyDiv" style="width:500px; height:400px;"></div>\n'+
		'var position = dom.getXY("MyDiv");\n'+
		'alert(position.x+" "+position.y); // returns "500,400"');
	
	var _z = 5000; // assume this is high enough
	function maxZ(n) {
		return set(n,{zIndex:++_z});
	};
	jaxdoc.addFunction('dom.maxZ','sets the z-index of an element to be the top most layer',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Node
	});
	jaxdoc.addExample('// consider : <div id="MyDiv" style="width:500px; height:400px;"></div>\n'+
		'var position = dom.getXY("MyDiv");\n'+
		'alert(position.x+" "+position.y); // returns "500,400"');
	
	function getSize(n) {
		return {
			w : parseInt(get(n,'width')),
			h : parseInt(get(n,'height'))
		}
	};
	jaxdoc.addFunction('dom.getSize','returns an element\'s width and height styles as an integer set {w,h}',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Literal
	});
	jaxdoc.addExample('var mydiv = dom.id("MyDiv");\n'+
		'alert(dom.size(mydiv,500,400).style.width);  // returns "500px"');
	
	function left(n,x) {
		return set(n,{left:px(x)});
	};
	jaxdoc.addFunction('dom.left','sets the "left" style of an element as an integer',{
		param : [
			{node : type.Node, left : type.Integer},
			{nodeId : type.String, left : type.Integer}
		],
		returns : type.Node
	});
	
	function top(n,y) {
		return set(n,{top:px(y)});
	};
	jaxdoc.addFunction('dom.left','sets the "top" style of an element as an integer',{
		param : [
			{node : type.Node, top : type.Integer},
			{nodeId : type.String, top : type.Integer}
		],
		returns : type.Node
	});
	
	function right(n,x) {
		return set(n,{right:px(x)});
	};
	jaxdoc.addFunction('dom.right','sets the "right" style of an element as an integer',{
		param : [
			{node : type.Node, right : type.Integer},
			{nodeId : type.String, right : type.Integer}
		],
		returns : type.Node
	});
	
	function bottom(n,x) {
		return set(n,{bottom:px(x)});
	};
	jaxdoc.addFunction('dom.bottom','sets the "bottom" style of an element as an integer',{
		param : [
			{node : type.Node, bottom : type.Integer},
			{nodeId : type.String, bottom : type.Integer}
		],
		returns : type.Node
	});
	
	function width(n,w) {
		return set(n,{width:px(w)});
	};
	jaxdoc.addFunction('dom.width','sets the "width" style of an element as an integer',{
		param : [
			{node : type.Node, visible : type.Boolean},
			{nodeId : type.String, visible : type.Boolean}
		],
		returns : type.Node
	});
	
	function height(n,h) {
		return set(n,{height:px(h)});
	};
	jaxdoc.addFunction('dom.width','sets the "height" style of an element as an integer',{
		param : [
			{node : type.Node, height : type.Integer},
			{nodeId : type.String, height : type.Integer}
		],
		returns : type.Node
	});
	
	function move(n,x,y) {
		return set(n,{
			left:px(x),
			top:px(y)
		});
	};
	jaxdoc.addFunction('dom.move','sets the (left,top) position of an element',{
		param : [
			{node : type.Node, left : type.Integer , top:type.Integer},
			{nodeId : type.String, left : type.Integer , top:type.Integer}
		],
		returns : type.Node
	});
	jaxdoc.addExample('var mydiv = dom.id("MyDiv");\n'+
		'dom.move(mydiv,400,"500px");\n'+
		'alert(mydiv.style.width+","+mydiv.style.height); // returns "400,500"');

	function opacity(n,f) {
		n = id(n);
		f = parseFloat(f);
		if (f<0) f = 0;
		if (f>1) f = 1;
		n.style.opacity = f;
		if (jaxscript.client.supports('activex')) n.style.filter = 'alpha(opacity='+f*100+')';
		//echo('dom.opacity(): '+n.nodeName+((n.id)?'#'+n.id:'')+' opacity = '+f);
		return n;
	};
	jaxdoc.addFunction('dom.opacity','sets the opacity of an element from 0 (transparent) to 1 (opaque), and adds a _opacity property to the element for reading back',{
		param : [
			{node : type.Node , opacityPercent:'float'},
			{nodeId : type.String , opacityPercent:'float'}
		],
		returns : type.Literal
	});
	jaxdoc.addExample('var mydiv = dom.id("MyDiv");\n'+
		'dom.opacity(mydiv,0.3);\n'+
		'alert(mydiv._opacity);  // returns 0.3');
	
	function px(i) {
		return Math.round(parseFloat(i))+'px';
	};
	jaxdoc.addFunction('dom.px','parses a value to an integer then adds "px" for use as a css style',{
		param : {intval : type.Integer},
		returns : type.Node
	});
	
	function size(n,w,h) {
		return set(n,{
			width:px(w),
			height:px(h)
		});
	};
	jaxdoc.addFunction('dom.size','sets an element\'s size as (width,height) in pixels and returns the element object',{
		param : [
			{node : type.Node , width:type.Integer , height:type.Integer},
			{nodeId : type.String , width:type.Integer , height:type.Integer}
		],
		returns : type.Node
	});
	jaxdoc.addExample('var mydiv = dom.id("MyDiv");\n'+
		'alert(dom.size(mydiv,500,400).style.width);  // returns "500px"');
	
	function set(o,s) {
		var n = id(o);
		if (!!n) {
			if (isArray(n)) {  // handle an array of elements
				for (var i=0;i<n.length;i++)
					copy(n[i].style,s);
			}
			else if (!!n.style) {
				copy(n.style,s);
				return n;
			}
		}
		echo('erro: dom.set() : element '+(isString(o)?o:'')+' does not exist, properties were '+inspect(s));
	};
	jaxdoc.addFunction('dom.style','a shortcut for "element.style.property = value" allowing multiple styles to be set in a single command',{
		param : [
			{node : type.Node, style : type.Literal},
			{nodeId : type.String, style : type.Literal},
			{node : type.Array, style : type.Literal}
		],
		returns : type.Node
	});
	jaxdoc.addExample('dom.set("MyDiv",{color:"red",border:"5px solid black"});\n'+
		'// same result as:\n'+
		'var mydiv = document.getElementById("MyDiv");\n'+
		'mydiv.style.color = "red";\n'+
		'mydiv.style.border = "5px solid black";');
	
	function swap(a,b) {
		display(a,0);
		display(b,1);
	};
	jaxdoc.addFunction('dom.swap','sets nodeA.style.display to "none" and sets nodeB.style.display to "block"',{
		param : [
			{nodeA : type.Node, nodeB : type.Node},
			{nodeIdA : type.String, nodeB : type.Node},
			{nodeA : type.Node, nodeIdB : type.String},
			{nodeIdA : type.String, nodeIdB : type.String}
		]
	});
	jaxdoc.addExample('var divB = dom.id("divB");\n'+
		'dom.swap("divA",divB);  // removes #divA shows #divB');
	
	function show(o) {
		return visible(o,true);
	}
	jaxdoc.addFunction('dom.hide','sets the "visibility" style of an element to "visible"',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Node
	});
	
	function hide(o) {
		return visible(o,false);
	}
	jaxdoc.addFunction('dom.hide','sets the "visibility" style of an element to "hidden"',{
		param : [
			{node:type.Node},
			{nodeId:type.String}
		],
		returns : type.Node
	});
	
	function visible(n,b) {
		return set(n,{visibility:b?'visible':'hidden'});
	};
	jaxdoc.addFunction('dom.visible','sets the "visibility" style of an element true (="visible"), false (="hidden")',{
		param : [
			{node : type.Node, visible : type.Boolean},
			{nodeId : type.String, visible : type.Boolean}
		],
		returns : type.Node
	});
	jaxdoc.addExample('dom.visible("MyDiv",0);  // makes #MyDiv invisible');
	
	return {
		clip : clip,
		display : display,
		getClip : getClip,
		getOpacity : getOpacity,
		getSize : getSize,
		get : get,
		getXY : getXY,
		maxZ : maxZ,
		move : move,
		opacity : opacity,
		size : size,
		set : set,
		visible : visible,
		left : left,
		top : top,
		right : right,
		bottom : bottom,
		width : width,
		height : height,
		show : show,
		hide : hide
	}

})();