(function(page) { var private = {};	
	
	var active = false;
	page.setactive = function(bool) {
		active = bool;		
	}	
	page.checkactive = function() {
		return active;
	}
	page.cancelaction = function(actionname) {
		if (typeof active[actionname].abort == 'function') {
			active[actionname].abort();
		}
	}
	page.debug = function(set) {
		var static = arguments.callee;
		if (typeof static.debug == 'undefined') { static.debug = false; }
		if (typeof set != 'undefined') { static.debug = set; return; }
		return static.debug;
	}
	page.debugupdate = function(set) {
		var static = arguments.callee;
		if (typeof static.debug == 'undefined') { static.debug = false; }
		if (typeof set != 'undefined') { static.debug = set; return; }
		return static.debug;
	}
	// public functions
	page.actionSetup = function(options) {
		// Version 2009 07 09
		// --
		// use current function as static object
		var static = arguments.callee;
		// define default options
		if (typeof static.defaults == 'undefined') static.options = static.defaults = {
			actionhandler:function(data,actionname) { page.updatepage(data, actionname); },
			rewriteurl:function(url) { return url; },
			rewriteparams:function(data) { return data; },
			passpageparams:true
		}
		// if an options argument is passed - fill in it's blanks with default values and overwrite static.options
		if (typeof options != "undefined") {
			// fill in the blanks with default values
			for(index in static.defaults) { if (typeof options[index] == 'undefined') { options[index] = static.defaults[index]; }}			
			// overwrite stored options with the argument options
			static.options = options;
		}
		// return stored options
		return static.options;
	}
	page.action = function(actionname) {
		try{ 
			// Version 2009 08 07
			// --
			// Usage: page.action(actionname[,(object)data][,(function)handler])
			// An action is an ajax request from a page to itself
			// while passing the parameter 'action=actionname'. By default
			// all the GET parameters that were passed to the page are
			// passed to the action. Also by default, the page will update
			// its self using the return data.
			// --
			// arguments for this function
			var args = arguments;
			// function that checks if action is ready
			var isreadyfunc = function(){ return !page.checkactive(); }
			// check function every this number of milliseconds
			var checkevery = 500; // miliseconds
			// when ready
			private.when(isreadyfunc, function(){ 
				// get setup options
				var options = page.actionSetup();
				// define default handler
				var handler = function(data) { options.actionhandler(data, actionname); };
				// define default post data
				var data = {};
				// loop through parameters after actionname (starting with second parameter)
				for(var i=1; i<args.length;i++) {
					// get this argument
					var arg = args[i];
					// if this argument is a function, set it as the handler function
					if (i<=2 && typeof arg == 'function') { handler = arg; continue; }
					// if this argument is an object, set it as post data object
					else if (i<=2 && typeof arg == 'object') { data = arg; continue; }
					// if we get this far, the bad arguments have been passed
					alert('Bad arguments for page.action(actionname[,(object)data][,(function)handler])');
				}
				// rewrite parameters
				data = options.rewriteparams( data );
				// set action parameter
				data.action = actionname;
				//
				// // *** THIS FIXES THE 'do_logPr' PROBLEM ***
				// // put something else at the end in case of firefox corruption
				// data.fixpr = 'anything';
				// // this code is commented out because it fixes a problem that only happens
				// // when firefox is actually broken. really we should not be supporting that
				// // situation. i suspect that upgrading firefox will fix the problem. also,
				// // because this code is deep at the core of the LTK I am worried that it could
				// // have negative side effects. Equally, there may still exist some situations
				// // in which this problem causes bugs.
				// // *** THIS FIXES THE 'do_logPr' PROBLEM ***
				//
				// ajax options that modify how the request works
				var ajaxoptions = (typeof data['options']=='object') ? data['options'] : {};
				// flatten data
				data = private.flattenforpost(data);
				// rewrite url
				var url = options.rewriteurl(window.location.href);
				// add action as url parameter (important!)
				url = private.makeurl(url, {action:actionname});
				// define successfunction
				var successfunc = function(data,textStatus) { 
					// handle response
					handler(data); 
				}
				// redirect if debug
				if (page.debug()) private.formredirect({
					url:url,
					data:data
				});
				// run ajax request for this action
				return active[actionname] = $.ajax(private.merge({
					// callbacks
					success:successfunc,
					// ajax params
					url:url,
					async:true,
					type:'post',
					data:data,
					dataType:'html'
				},ajaxoptions));
			},checkevery); // check every
		} catch(err) {
			if (typeof window.handlerror == 'function') {
				delete err['stack'];
				err.action = actionname;
				err.actiondata = data;
				var msg = "action error - "+get_r(err);
				window.handlerror(msg);
			}
		}
	}
	private.when = function(conditionfunc,func,checkdelay) {
		if (conditionfunc()) {
			func();
		} else {
			var self = arguments.callee;
			var timeoutfunc = function(){ 
				self(conditionfunc,func,checkdelay); 
			}
			setTimeout(timeoutfunc, checkdelay);
		}
	}
	page.actionupload = function(actionname) {
		// Version 2009 08 07
		// --
		// Usage: page.action(actionname[,(object)data][,(function)handler])
		// An action is an ajax request from a page to itself
		// while passing the parameter 'action=actionname'. By default
		// all the GET parameters that were passed to the page are
		// passed to the action. Also by default, the page will update
		// its self using the return data.
		// --
		// arguments for this function
		var args = arguments;
		// function that checks if action is ready
		var isreadyfunc = function(){ return !page.checkactive(); }
		// check function every this number of milliseconds
		var checkevery = 500; // miliseconds
		// when ready
		private.when(isreadyfunc, function(){ 
			// get setup options
			var options = page.actionSetup();
			// define default handler
			var handler = function(data) { options.actionhandler(data, actionname); };
			// define default post data
			var data = {};
			// loop through parameters after actionname (starting with second parameter)
			for(var i=1; i<args.length;i++) {
				// get this argument
				var arg = args[i];
				// if this argument is a function, set it as the handler function
				if (i<=2 && typeof arg == 'function') { handler = arg; continue; }
				// if this argument is an object, set it as post data object
				else if (i<=2 && typeof arg == 'object') { data = arg; continue; }
				// if we get this far, the bad arguments have been passed
				alert('Bad arguments for page.action(actionname[,(object)data][,(function)handler])');
			}
			// rewrite parameters
			data = options.rewriteparams( data );
			// set action parameter
			data.action = actionname;
			// flatten data
			data = private.flattenforpost(data);
			// rewrite url
			var url = options.rewriteurl(window.location.href);
			// add action as url parameter (important!)
			url = private.makeurl(url, {action:actionname});
			// define successfunction
			var successfunc = function(data,textStatus) { handler(data); }
			// debug redirect
			if (page.debug()) private.formredirect({
				data:data,
				url:url
			});
			// create dynamic form with the passed data and file inputs
			var formobj = private.createform(data);
			// run iframe call this action
			private.iframecall(formobj,{
				beforeSend:function() { page.showpleasewait(); },
				complete:function(html) { page.hidepleasewait(); },
				success:function(html) { page.updatepage(html); },
				url:url,
				type:'post'
			})
			// cleanup form - return file inputs to their original locations
			private.cleanupform(formobj);
			// action
		},checkevery); // check every
	}
	private.createform = function(data,action) {
		// IE needs the form to be created with the enctype or uploads won't work
		try { var formobj = document.createElement('<form enctype="multipart/form-data">'); } 
		// an error is thrown for other browsers, so we create the form and set enctype normally
		catch(err) { var formobj = document.createElement('form'); formobj.enctype="multipart/form-data"; }				
		// make the form invisible
		formobj.style.visibility = 'hidden';
		// set form action now so that it doesn't get mad at us for using the name 'action' later
		if (action) formobj.action = action;
		// create form elements from the data argument
		if (data) for(key in data) {
			// get value for this key
			var value = data[key]
			// if value is null or undefined, continue
			if (typeof value == 'undefined' || value==null) { continue; }
			// detect if value is a file upload input
			var iselem = (typeof value.tagName=='string');
			var isinput = (iselem) ? (value.tagName.toLowerCase()=='input') && (typeof value.type=='string') : false;
			var isfile = (isinput) ? (value.type.toLowerCase()=='file') : false;
			// if value is a file upload input, attach the input to this form (you can't just copy it or it's value), and remember where it was
			if (isfile) {
				var fileinput = value;
				var parent = fileinput.parentNode;
				// make placeholder to put where this input was
				var placeholder = document.createElement('input');
				// give the placeholder the name of the file input as it will be overwritten
				placeholder.name = fileinput.name;
				// give the fileinput a pointer to the placeholder (it's original location)
				fileinput.original_location = placeholder;
				// attach the placeholder right before the file input
				parent.insertBefore(placeholder,fileinput);
				// set name of file input to the key name
				fileinput.name = u(key);
				// attach file input to this form
				formobj.appendChild(fileinput);
			} else {
				// create a new input of type text
				var textarea = document.createElement('textarea');
				// input.type = 'text';
				textarea.style.visibility = 'hidden';
				textarea.style.overflow = 'hidden';
				// set input name to the key name
				textarea.name = key;
				// set input value to the value
				textarea.value = value;
				// attach the new input to the form
				formobj.appendChild(textarea);					
			}
		}
		// attach the form object to the page body
		document.body.appendChild(formobj);
		// return the form object
		return formobj;
	}
	private.cleanupform = function(formobj) {
		for(var i=0;i<formobj.elements.length;i++) {
			// get the current element
			var element = formobj.elements[i];
			// detect if value is a file upload input
			var iselem = (typeof element.tagName=='string');
			var isinput = (iselem) ? (element.tagName.toLowerCase()=='input') && (typeof element.type=='string') : false;
			var isfile = (isinput) ? (element.type.toLowerCase()=='file') : false;
			// if this is a file input - move it back to it's original location
			if (isfile) {
				var fileinput = element;
				// get the placeholder
				var placeholder = fileinput.original_location;
				var parent = placeholder.parentNode;
				// retrieve name from placehokder
				fileinput.name = placeholder.name;
				// put fileinput back where placeholder is
				parent.insertBefore(fileinput,placeholder);
				parent.removeChild(placeholder);
				// delete original_location parameter
				try{ delete fileinput.original_location; } catch(ex) { }
			}
		}
		// remove the whole form from the page
		formobj.parentNode.removeChild(formobj);
		// delete the form
		delete formobj;
	}
	page.updatepage = function(html) {
		try {
			// Version: 2009 07 21
			// --
			// This function takes html text and uses it to update the DOM.
			// First, this function converts the html string into html elements - then
			// for each root html or text element the function will do one of the following:
			// 1. If the element is an html node with an id, and there is already a node with 
			// the same id on the page, the contents of the old element will be replaced.
			// 2. If the element is an html node with no matching id, the element will be
			// appended to the top of page body element (so you can see the mistake)
			// 3. If the element is a script tag, the contents of the script tag will be
			// run through the eval() function.
			// 4. If the element is just text, it will be alerted.		
			// 5. If the element behaves unpredictably, it will be skipped.
			// --
			// debug flag
			var debug = page.debugupdate();
			// create a new div element to use as a container for the html
			var elements = document.createElement("div");
			// insert html parameter into innerHTML of new element so javascript will parse it into objects
			// ie will break if there is only one script tag in the html - so we add div
			elements.innerHTML = "<div>&nbsp;</div>"+html; 
			// remove the first div that was put there for ie
			elements.removeChild(elements.firstChild);
			// create and style a fieldset (has nice border and legend) to use as contained for unmatched elements
			var appendcontainer = document.createElement('FIELDSET');
			appendcontainer.style.background = 'white';
			// create a container that will hold debug elements
			if (debug) var debugcontainer = document.createElement('FIELDSET');
			if (debug) debugcontainer.style.padding = '20px';
			if (debug) debugcontainer.style.background = '#eee';
			if (debug) debugcontainer.appendChild( document.createElement('H3') );
			if (debug) debugcontainer.firstChild.style.color = 'black';
			if (debug) debugcontainer.firstChild.style.paddingBottom = '10px';
			if (debug) debugcontainer.firstChild.innerHTML = 'debug';
			// add response debug container
			if (debug) var debugtitle = document.createElement('div');
			if (debug) debugtitle.innerHTML = 'response html';
			if (debug) debugtitle.style.color = 'white';
			if (debug) debugtitle.style.background = 'black';
			if (debug) debugtitle.style.padding = '5px';
			if (debug) debugcontainer.appendChild(debugtitle);			
			if (debug) var debugchild = document.createElement('PRE');			
			if (debug) debugchild.style.background = 'white';
			if (debug) debugchild.style.padding = '10px';
			if (debug) debugchild.style.margin = '0px';
			if (debug) debugchild.style.whiteSpace = 'pre';
			if (debug) var inserthtml = html;
			if (debug) while( inserthtml != (inserthtml=inserthtml.replace('<','&lt;')) ) {}
			if (debug) while( inserthtml != (inserthtml=inserthtml.replace('>','&gt;')) ) {}
			if (debug) while( inserthtml != (inserthtml=inserthtml.replace('\r\n','\n')) ) {}
			if (debug) while( inserthtml != (inserthtml=inserthtml.replace('\n','<br/>')) ) {}
			if (debug) debugchild.innerHTML = inserthtml;
			if (debug) debugcontainer.appendChild(debugchild);
			// add inserted html to the debug container
			if (debug) var debugtitle = document.createElement('div');
			if (debug) debugtitle.innerHTML = 'inserted html';
			if (debug) debugtitle.style.color = 'white';
			if (debug) debugtitle.style.background = 'black';
			if (debug) debugtitle.style.padding = '5px';
			if (debug) debugcontainer.appendChild(debugtitle);			
			if (debug) var debugchild = document.createElement('FIELDSET');			
			if (debug) debugchild.style.background = 'white';
			if (debug) debugchild.style.padding = '10px';
			if (debug) debugchild.style.margin = '0px';
			if (debug) var outerhtml = elements.innerHTML;
			if (debug) while( outerhtml != (outerhtml=outerhtml.replace('<','&lt;')) ) {}
			if (debug) debugchild.innerHTML = outerhtml;
			if (debug) debugcontainer.appendChild(debugchild);
			// create a div to use a container for script tags (they won't get evaluate because they aren't attached to the dom)
			var scriptcontainer = document.createElement('DIV');	
			// flag which is set to true if any unmatched element is found.	
			var doappend = false;
			// string used to store text to be alerted - all text will be alerted at once
			var alerttext = '';
			// return the elements
			while (elements.childNodes.length>0) {
				// get this child
				var child = elements.firstChild;
				// detect information about this child
				var isnull = (child==null);
				var hastype = (!isnull) ? (typeof child.nodeType != "undefined") : false; 
				var istext = (hastype) ? (child.nodeType==3) : false;
				var iselem = (hastype) ? (child.nodeType==1) : false;
				var childtext = (istext) ? child.data : '';
				var childtrimmedtext = childtext.replace(/^\s+/, '').replace(/^\s+/, '');
				var childhtml = (iselem) ? child.innerHTML : '';
				var childtag = (iselem) ? child.tagName : '';
				var isscript = (iselem) ? (child.tagName.toLowerCase()=='script') : false;
				var childid = (iselem) ? (child.id) : false;
				// check if there is another elem with the same id as this child (aka. it's predecessor)
				var predecesor = (iselem && childid) ? document.getElementById(childid) : null;
				var haspredecesor = (iselem && childid) ? (predecesor!=null) : false;
				// decide what to do with child by setting the method variable
				if (iselem && haspredecesor) { var method = 'inject'; }
				else if (iselem && isscript) { var method = 'script'; }
				else if (iselem && !haspredecesor) { var method = 'append'; }
				else if (istext && childtrimmedtext!='') { var method = 'alerttext'; }
				else { method='skip' }	

				// add to debug container
				if (debug) var debugtitle = document.createElement('div');
				if (debug) debugtitle.innerHTML = 'method: '+method;
				if (debug) debugtitle.style.color = 'white';
				if (debug) debugtitle.style.background = 'green';
				if (debug) debugtitle.style.padding = '5px';
				if (debug) debugcontainer.appendChild(debugtitle);			
				if (debug) var debugchild = document.createElement('FIELDSET');			
				if (debug) debugchild.style.background = 'white';
				if (debug) debugchild.style.padding = '10px';
				if (debug) debugchild.style.margin = '0px';
				if (debug) var outerhtml = private.outerHTML(child);
				if (debug) while( outerhtml != (outerhtml=outerhtml.replace('<','&lt;')) ) {}
				if (debug) debugchild.innerHTML = outerhtml;
				if (debug) debugcontainer.appendChild(debugchild);
			
				// process with the 'skip' method if requested
				if (method=='skip') { 
					appendcontainer.appendChild( child ); 
					continue; 
				}
				// process with the 'inject' method if requested (replace predecessor)
				else if (method=='inject') { 		
					// remove child from elements div
					elements.removeChild( child );
					// overwrite predecessor html of child's html
					predecesor.innerHTML = childhtml; 								
				}
				// process with the 'script' method if requested (store script for eval at the nd)
				else if (method=='script') { 
					// add script to script container - this automatically removes it from the elements div
					scriptcontainer.appendChild( child );
				}
				// process with the 'append' method if requested (store script for eval at the nd)
				else if (method=='append') { 
					// add element to the end of the append container - this automatically removes it from the elements div				
					appendcontainer.appendChild( child ); 
					// set do append to true
					doappend = true;
				}
				// process with the 'alertext' method if requested (store script for alert at the end)
				else if (method=='alerttext') { 
					// append child text to the end of the alertext string
					alerttext += childtrimmedtext+' ';
					// also add element to the append container (but don't set doappend flag)
					appendcontainer.appendChild( child ); 
				}
			}
		
			// foreach element in the script container - eval the script
			while (scriptcontainer.childNodes.length>0) {
				// get script
				var script = scriptcontainer.firstChild;
				// try catch eval script text
				try {
					eval(script.innerHTML);
				} catch(err) {
					if (typeof window.handlerror == 'function') {
						err.stack = script.innerHTML;
						var msg = "script update error - "+get_r(err);
						window.handlerror(msg);
					}
				}
				// remove script from container
				script.parentNode.removeChild(script);
			}
			// if doappend flag set (and if there is something to append) append container to top of body
			if (doappend && appendcontainer.childNodes.length>0) { 
				// append container to top of page body
				document.body.insertBefore( appendcontainer, document.body.firstChild ); 
			}
			// if no doappend flag set and alert text is not empty, alert alertext
			if (!doappend && alerttext!='') {
				// alert alerttext
				alert(alerttext);
			}
			// show debug info
			if (debug) document.body.insertBefore( debugcontainer, document.body.firstChild ); 
			// delete elements just in case it matters.
			delete elements;
		} catch(err) {			
			//document.write(err['stack']);
			if (typeof window.handlerror == 'function') {
				err.stack = h(html);
				var msg = "update error - "+get_r(err);
				window.handlerror(msg);
			}
		}
	}
	private.flattenforpost = function(obj) {
		// Version 2009 07 09
		// --
		// define result if not defined
		var result = {} 
		// define path if not defined
		var path = [];
		// define private function that does the work
		var processfunc = function(obj) {
			// Version: 2009 07 09
			// recursive pointer to this function
			var recursive = arguments.callee; 
			// number of indexes in the obj 
			var indexcount = 0; 
			// loop through the object
			for(index in obj) {
				// skip null values
				if (obj[index]==null) { continue; }
				// check if this is an object 
				var isobj = typeof(obj[index])=='object';
				// check if this is a dom element
				var iselem = (isobj) ? (typeof obj[index].ownerDocument == 'object') : false;
				// increment index count
				indexcount++;
				// push this index as part of the current path
				path.push(index);
				// if the value at this index is *not* an object
				if (!isobj || iselem) { 
					// calculate the key name for the value of this object
					var front = path[0];
					var rest = path.slice(1).join('][');
					if (rest!='') { var name = front+"["+rest+"]"; } else { var name = front; }
					// if this is a boolean add 0 or 1
					if (typeof obj[index] == 'boolean') { obj[index] = obj[index] ? 1 : 0; }
					// add this key value pair to the result array
					result[ name ] = obj[index];
				}
				// if this value at this index is an object, call this function recursively
				if (isobj && !iselem) { 
					// call self recursively
					recursive(obj[index],path,result); 
				}
				// remove the to value from the path array
				path.pop();
			}
			// if this obj had no indexes, then it was an empty array
			if (indexcount==0) {
				// calculate the key name for the value of this object
				var front = path[0];
				var rest = path.slice(1).join('][');
				if (rest!='') { var name = front+"["+rest+"]"; } else { var name = front+"" };			
				// add this key to the result array with an empty value
				result[ name ] = '';
			}
		};
		// process obj
		processfunc(obj);
		// return the result object
		return result;
	}
	page.iframepost = function(options) {
		// create form obj
		var formobj = private.createform(options.data,options.url);
		// create a new iframe
		var iframeobj = document.createElement('iframe'); // create the iframe
		// create a unique name for the iframe
		var iframeidname = "frame"+new Date().getTime(); // unique idname for this iframe
		iframeobj.id = iframeidname;
		iframeobj.name = iframeidname;
		// style the iframe so that it is invisible and doesn't get in the way
		iframeobj.style.visibility='hidden'; 
		iframeobj.style.position='absolute'; 
		iframeobj.style.zIndex = '1000'; 
		iframeobj.style.top='0'; 
		iframeobj.style.left='0'; 
		// prevent new window in safari 
		iframeobj.style.display='block';			
		// attach the iframe to the page body
		document.body.appendChild(iframeobj); 
		// prevent new window in IE
	 	if(self.frames[iframeidname].name != iframeidname) { self.frames[iframeidname].name = iframeidname; }
		// configure form to targer iframe
		//formobj.target="_blank";			
		formobj.target=iframeobj.name;			
		// configure form using options parameter
		formobj.enctype="multipart/form-data";
		formobj.acceptCharset = "utf-8"; // because we are simulating javascript (ajax) and that's always utf-8
		//formobj.action = options.url; // set in createform
		formobj.method = options.type ? options.type : 'post';
		// run beforeSend callback function
		if (typeof options.beforeSend == 'function') options.beforeSend(); 	
		// submit form
		formobj.submit();
		// clean up form obj
		private.cleanupform(formobj);
		// remove iframe
		setTimeout(function(){ document.body.removeChild(iframeobj) },5000);
	}	
	private.iframecall = function(formobj,options) {
		// create a new iframe
		var iframeobj = document.createElement('iframe'); // create the iframe
		// create a unique name for the iframe
		var iframeidname = "frame"+new Date().getTime(); // unique idname for this iframe
		iframeobj.id = iframeidname;
		iframeobj.name = iframeidname;
		// style the iframe so that it is invisible and doesn't get in the way
		iframeobj.style.visibility='hidden'; 
		iframeobj.style.position='absolute'; 
		iframeobj.style.zIndex = '1000'; 
		iframeobj.style.top='0'; 
		iframeobj.style.left='0'; 
		// prevent new window in safari 
		iframeobj.style.display='block';			
		// attach the iframe to the page body
		document.body.appendChild(iframeobj); 
		// prevent new window in IE
	 	if(self.frames[iframeidname].name != iframeidname) { self.frames[iframeidname].name = iframeidname; }
		// define form handler function to run when iframe is done loading
		var handler = function() {
			// get the iframe location
			var iframelocation = iframeobj.contentWindow.location;
			// get the iframe body html
			var iframehtml = 
				iframeobj.contentWindow.document.getElementsByTagName('HEAD')[0].innerHTML
				+ iframeobj.contentWindow.document.getElementsByTagName('BODY')[0].innerHTML;
			// prevent function from firing for empty iframe
			if (iframelocation=='about:blank') { return; }
			// run success callback function
			if (typeof options.success == 'function') options.success(iframehtml); 
			// remove iframe from body
			setTimeout(function(){ document.body.removeChild(iframeobj) },1);			
			// run complete callback function
			if (typeof options.complete == 'function') options.complete(iframehtml); 	
		}
		// attach success and remove functions for IE
		if (iframeobj.attachEvent) iframeobj.attachEvent('onload',handler); 
		// attach success and remove functions for non-IE browsers			
		if (iframeobj.addEventListener)	iframeobj.addEventListener('load',handler,false); 
		// configure form to targer iframe
		formobj.target=iframeobj.name;			
		// configure form using options parameter
		formobj.enctype="multipart/form-data";
		formobj.acceptCharset = "utf-8"; // because we are simulating javascript (ajax) and that's always utf-8
		//formobj.action = options.url; // set in passed form to prevent IE conflict
		formobj.method = options.type;
		// run beforeSend callback function
		if (typeof options.beforeSend == 'function') options.beforeSend(); 	
		// submit form
		formobj.submit();
	}	
	private.formredirect = function(options) {
		var formobj = private.createform(options.data);
		formobj.action = options.url;
		formobj.method = 'post';
		formobj.enctype="multipart/form-data";
		formobj.acceptCharset = "utf-8";
		formobj.target = "debug";
		window.open('','debug','width=500,height=500,resizable=1,scrollbars=1,location=1').focus();
		formobj.submit();
		// cleanup form - return file inputs to their original locations
		private.cleanupform(formobj);
	}
	private.outerHTML = function(obj) {
		var parent = obj.parentNode;
		var placeholder = document.createElement('div');
		parent.insertBefore(placeholder,obj);
		placeholder.appendChild(obj);
		var outerHTML = placeholder.innerHTML;
		parent.insertBefore(obj,placeholder);
		parent.removeChild(placeholder)
		return outerHTML;
	}
	private.urlparams = function(url) {
		// Version: 2009 05 13
		// qstring
		var qstring = (url+'').split('?').slice(1).join('?');
		// get params from url
		var paramstrs = qstring.split('&');
		var params = {};
		for(index in paramstrs) {
			if (paramstrs[index]=='') continue;
			var keyval = paramstrs[index].split('=');
			var key = keyval[0];
			var val = (typeof keyval[1] != 'undefined') ? keyval[1] : '';
			params[key] = val;
		}
		// return param associative array
		return params;
	}
	private.makeurl = function(url,newparams) {
		// Version: 2009 12 03
		// url
		var urlparams = tools.urlparams(url);
		var urlnoparams = (url+'').split('?').shift();		
		// array of strings
		var paramstrs = [];
		for(index in urlparams) {
			if (urlparams[index]==null) continue;
			if (typeof newparams[index] != "undefined") continue;			
			paramstrs.push(index+'='+urlparams[index]);
		}
		for(index in newparams) {
			if (newparams[index]==null) continue;
			paramstrs.push(index+'='+newparams[index]);
		}
		// return
		var paramstr = paramstrs.join('&');
		if (paramstr=='') {
			url =  urlnoparams;
		} else {
			url = urlnoparams+'?'+paramstr;
		}
		return url;
	}
	private.merge = function(obj1,obj2) {
		var merged = {};
		for(var index in obj1) {
			merged[index] = obj1[index];
		}
		for(var index in obj2) {
			merged[index] = obj2[index];
		}
		return merged;
	}

})( window.page = (window.page) ? window.page : {} );