
//
// Ajax Manager 0.18
//
// Door Arnout Vreugdenhil, Argeweb Hosting BV
// www.argeweb.nl
// Versie: 08-02-2011
//
// Objecten:
//
// AjaxFactory
// AjaxRequest
// AjaxHandler
//
// Remarks:
// #1:  IE error
// Returning false is not an option.
// If you do, any if( this.Requester == false ) will cause errors
// #2:  IE error
// The ActiveX object does not know an onerror event handler
// Setting it will cause errors

/*

UPDATES
========================================
Send en BuildQuery methods verbeterd
Manier POST/GET werken nu beide
DoShow aangepast zodat deze ook met een TR om kan gaan

// 0.6 fix:
optionsToSelect is aangepast: Hij onthoudt nu welke option er gekozen was, zonder kans op errors als er niks gekozen is of geen options meer zijn..

0.7
Deze versie moet een Request.queue() method hebben, die send vervangt en pas send als de voorgaande klaar zijn.
Verder is er misschien een optie om een maxConcurrent = 4 in te bouwen, en dat send() gewoon queuet als er al teveel zaken draaien.

0.8
Time-out teller werd ingezet bij het enQueueen ipv bij het runnen. Is gefikst.

0.9
Functie toevoegen om een bepaalde tbody in een tabel te vervangen met ALLE tbodies uit de responseXML.
(Met inachtneming van de plaats van de oude tbody in de tabel)

AddTBODYAfter

0.10
het opvragen van de value van een radio element werkt niet goed.
Is nu opgelost via Victim.form[Victim.name].length ipv Victim.length

0.11
Het opvragen van de value van een radio element was nog niet helemaal goed
Er werd vanuit gegaan dat er altijd een form was, maar die is optioneel
(bijv als je de checked waarde zoekt mbt getElementsByTagsName, zoals de poll.

0.12
ReplaceContentsById met spans lijkt niet helemaal te werken in IE.
Oplossing:  GEEN innerHTML gebruiken, maar de childelements 1 voor 1 overplempen.

0.13
Bij het bouwen van de query-string a.h.v. de postvalues en postsources wordt ervoor gezorgd dat er geen dubbele values kunnen bestaan.

0.14
Een ajax-request moet voorrang kunnen krijgen op de wachtende queue.
DoShow op een input veld deed display:block, maar dat moet display:inline zijn, anders gaat de input naar de volgende regel.
Naast een Request.prio moet er een Request.final zijn, die alle andere Requests (met prio 0) doodschiet.
Doorvoor moesten de Abort functies geactualiseerd worden.

BuildQuery:
Handled hield ook veld[] velden tegen, dus een array van vinkjes kon niet gepost worden.
Nu is er als uitzondering Victim.match( /\[\]$/ ) toegevoegd

0.15
FireFox 3 werkt anders met synchronous calls.
De onReadyStateChanged handler wordt niet aangeroepen. Dat moeten we dus helemaal zelf doen, nadat de send() is voltooid.
Geen idee of dat alleen in FF 3 zo is.
Gefikst door in de Run() expliciet de onreadystatechange aan te roepen
De onreadystatechange moest wel eerst lokaal gedeclareerd worden ipv direct in de Requester, anders mochten we hem niet zomaar aanroepen.

0.16
De + binnen een query-value werd niet geescaped, en dus een spatie.
Bij het ophalen van de waarde van een submit-button kwam een alert() dat submit niet herkend werd.

0.17
IE8 retro modes do not behave exactly like IE7 and such.
We now have a effective browser detector function:
Ajax.browser = Ajax.GetBrowser();
*/

function isArray( MaybeArray ) {

   if( MaybeArray.constructor.toString().indexOf( "Array" ) == -1)
      return false;
   else
      return true;

}



function AjaxInit( URL ){

	// Just using Ajax doesn't work in IE
	// Using window.Ajax makes it a singleton (not really)
	// Anyone using the var Ajax will not disturb anything

	if( window.Ajax )
		return; // already set

	window.Ajax = new AjaxFactory( URL );


	// abort all running requests when the body unloads.

	if( window.onunload )
		var old_onunload = window.onunload;
	else
		var old_onunload = function(){};

	window.onunload = function(){
		old_onunload();
		window.Ajax.AbortAll();
	}


}

// AjaxRequest object
function AjaxRequest( Factory ){

	var Request = this; // Voor gebruik in geinjecteerde functies

	// children

	this.Name = 'noname';
	this.Manier = Factory.Manier;
	this.Factory = Factory;
	this.Prio = 0;
	this.Final = 0;

	this.Async = true;

	try{
		//  Remark #1
		this.Requester = this.Factory.GetRequester();
	}catch( e ){
		alert( "Could not get Requester for AJAX object." );
		return;
	}

	this.CommandId = this.Factory.GetNewCommandId();
	this.Factory.Stack.push( this );

	this.StartHandler = new AjaxHandler( this, false );
	this.SuccessHandler = new AjaxHandler( this, true );
	this.ErrorHandler = new AjaxHandler( this, true );

	// Settings
	this.ErrorOnString = Factory.ErrorOnString;
	this.CleanWhenDone = Factory.CleanWhenDone;
	this.CleanWhenDone = Factory.CleanWhenDone;

	this.Query = '';

	this.overrideMimeType = '';

	this.onreadystatechange = function(){


		// 0 uninitialized
		// 1 loading
		// 2 loaded
		// 3 interactive
		// 4 done


		if( Request.Requester.readyState == 4 ){

			// if this generates a weird exception the request has been aborted.
			try{
				if( Request.Requester.status != 200 ){};
			}catch(e){
				return;
			}

			// The IE version resets to not sent when abort is called.
			if( Request.Requester.status == 0 )
				return;

			if( Request.Requester.status != 200 )
				Request.ErrorHandler.DoActions( "AjaxError, " + Request.Requester.status + ":" + Request.Requester.statusText );
			else if( Request.ErrorOnString == true && isNaN( Request.ValueOf() ) )
				Request.ErrorHandler.DoActions( "Response was not a number: " + Request.Requester.responseText );
			else{
				try{
					Request.SuccessHandler.DoActions();
				}catch( e ){
					alert( "Exception in Request.SuccessHandler " + Request.Name + e + "  " + e.description );
				}
			}

		}
	}

	this.Requester.onreadystatechange = this.onreadystatechange;
	
	try{
		// Remark #2
		this.Requester.onerror = function(){


			if( Request.Requester.status == 0 ){
				// cancelled FF3
			}else if( Request.Requester.status != 200 ){
				alert( "Error! Status: " + Request.Requester.status );
				Request.ErrorHandler.DoActions( "AjaxError, Status=" + Request.Requester.status );
			}
		}
	}catch( e ){}

	this.PostSourceStack = new Array();
	this.PostValueStack = new Array();

	// properties
	this.URL = this.Factory.URL;

	this.TimeOut = this.Factory.TimeOut; // in seconden
	this.TimeOutId = null;
	this.Message = "";

	// methods

	this.DoTimeOut = function(){

		Request.Requester.abort();
		Request.ErrorHandler.DoActions( "Time Out after " + Request.TimeOut + " seconds." );

		this.Factory.UnQueue( this ); // Running queue opruimen

	}

	this.Abort = function(){

		Ajax.Log2( "Abort request: " + this.CommandId );
		// Abort self, body onunload, for example.

		this.Requester.abort();

		// abort my own time-out
		if( this.TimeOutId ){
			Ajax.Log2( "Timeout actie verwijderen..." );
			this.Factory.Timer.RemoveAction( this.TimeOutId );
		}

		this.Factory.UnQueue( this ); // Running queue opruimen

		Ajax.Log( "aborted....\n" );

	}

	this.PostForm = function( Form ){

		Form = this.Factory.GetObject( Form );

		// Add all form fields to the PostSource Array

		for( var i = 0;i < Form.elements.length;i++ )
			this.AddPostSource( Form.elements[i], Form.elements[i].name );

	}

	this.AddPostSource = function( PostSource, PostName ){
		// Object toevoegen waarvan de .value genomen moet worden

		var PostObject = this.Factory.GetObject( PostSource );

		if( PostName == "" || PostName == null )
			PostName = PostObject.name;
		if( PostName == "" || PostName == null )
			PostName = PostObject.id;

		this.PostSourceStack.push( new ArrayElement( PostName, PostObject ) );
	}

	this.AddPostValue = function( PostValue, PostName ){

		// Waarde toevoegen

		this.PostValueStack.push( new ArrayElement( PostName, PostValue ) );
	}

	this.BuildQuery = function(){

		// door PostStack loopen
		while( this.PostSourceStack.length > 0 ){

			var PostSource = this.PostSourceStack.pop();

			this.PostValueStack.push( new ArrayElement( PostSource.Name, this.Factory.GetValue( PostSource.Value ) ) );

		}

		// Query string bouwen

		var Query = ( this.Manier == 'GET' ? "?" : "" );
		var QueryPiece = "";

		var Handled = new Array();

		for( var i = 0;i < this.PostValueStack.length; i++ ){

			if( !Handled[this.PostValueStack[i].Name] || this.PostValueStack[i].Name.match( /\[\]$/i ) ){

				QueryPiece = this.PostValueStack[i].QueryString();

				if( QueryPiece != null ){

					if( i < this.PostValueStack.length - 1 )
						QueryPiece += "&";

					Query += QueryPiece;

				}

				Handled[this.PostValueStack[i].Name] = true;

			}

		}

		return Query;

	}

	this.Send = function(){

		this.Query = this.BuildQuery();

		// Do preperations
		this.StartHandler.DoActions();


		this.Factory.Log( "Request to: " + this.Query );



		if( this.Factory.Manier == 'POST' ){

			this.Requester.open("POST", this.URL, this.Async );
			this.Requester.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

			//this.Requester.send( Query );

		}else if( this.Factory.Manier == 'GET' ){

			this.Requester.open("GET", this.URL + Query, this.Async );
			this.Requester.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

			//this.Requester.send( "" );

		}

		//if( this.overrideMimeType != '' && this.Requester.overrideMimeType )
		//	this.Requester.overrideMimeType = this.overrideMimeType;

		this.Factory.Queue( this );

	}

	this.Run = function(){


		this.Factory.Log( this.CommandId + " Running..." );
		this.Factory.Log2( this.CommandId + " Running..." );


		// Add to Timer if needed
		if( this.TimeOut > 0 )
			this.TimeOutId = this.Factory.Timer.AddTimeOut( this );

		if( this.Factory.Manier == 'POST' ){

			this.Requester.send( this.Query );
		}else{

			this.Requester.send( "" );
		}

		if( this.Async == false ){
			this.onreadystatechange();
		}

	}

	this.ValueOf = function(){

		// Return a primitive number or NaN
		return new Number( Request.Requester.responseText );

	}

	this.Done = function(){

		// I'm done: tell the factory
		this.Factory.OneDone( this );

	}

	this.DestroySelf = function(){

		// Post-natal abortion
		this.Factory.Destroy( this.CommandId );

	}

}

// AjaxFactory object
function AjaxFactory( URL ){

	// This thing supplies AjaxRequest objects
	// It holds the AjaxTimer
	// It keeps track of all running AjaxRequests

	// properties
	this.URL = URL;
	this.Manier = 'POST';
	this.TimeOut = 10;
	this.ErrorOnString = false;
	this.CleanWhenDone = true;

	// children
	this.browser = {};
	
	this.LastCommandId = 0;
	this.Stack = new Array();

	this.Timer = new AjaxTimer();

	this.ErrorObject = null;
	this.LogObject = null;
	this.LogObject2 = null;

	this.Queued = new Array();
	this.Running = new Array();
	this.MaxConcurrent = 3;

	this.StaticHandler = null;

	this.GetBrowser = function(){
		
		
		var browser = {
			"userAgent" 		: "unknown",
			"browserFamily" 	: "unknown",
			"browser" 			: "unknown",
			"version" 			: "unknown",
			"preciseVersion" 	: "unknown",
			"browserMode" 		: "unknown",
			"docMode"			: "none",
			"effectiveBrowser" 	: "unknown"
		};
				
		browser.userAgent = navigator.userAgent;
		
		
		if( /msie/i.test( navigator.userAgent ) && !window.opera ){
			
			if( window.ActiveXObject && window.attachEvent ){
				// IE
				browser.browserFamily = "IE";
				browser.preciseVersion = (navigator.userAgent.match( /.+ie\s([\d.]+)/i ) || [])[1];
				browser.version = parseInt( browser.preciseVersion );
				
				// nep-IE7 rechtzetten
				if( document.documentMode && browser.version == 7 ){
					
					browser.version = 8;
					
					if( /trident\/\d/i.test( navigator.userAgent ) ){
						browser.browserMode = "IE8_compat";
					}else{
						browser.browserMode = "IE7";
					}
				}else if( browser.version == 8 && document.documentMode && browser.browserMode != "IE8_compat" )
					browser.browserMode = "IE8";
				
					
				
				if( document.documentMode )
					browser.docMode = document.documentMode;
				else if( document.compatMode && document.compatMode=="CSS1Compat" )
					browser.docMode = 7;
				else
					browser.docMode = 5;
					
				
					
				browser.effectiveBrowser = browser.browser + browser.docMode;
					
			}else{
				
				browser.browser = "FAKE_IE";
			}
			
		}else if( /Firefox/i.test( navigator.userAgent ) ){
			
			browser.browser = "FF";
			browser.preciseVersion = (navigator.userAgent.match( /Firefox\/([\d.]+)/i ) || [])[1];
			browser.version = parseInt( browser.preciseVersion );
			
			browser.effectiveBrowser = browser.browser + browser.version;
			
		}
		else if( /webkit/i.test( navigator.userAgent ) ){
			browser.browserFamily = "WK"; //WebKit browser. (Chrome, Safari)
			browser.version = '';
		}
		
		browser.browser = browser.browserFamily + browser.version;
		return browser;
		
	}	
	this.browser = this.GetBrowser();
	
	this.Queue = function( Request ){

		// place in a stack
		// when


		this.Log( Request.CommandId + " Queued..." );


		if( Request.Final > 0 ){
			this.AbortAll( Request.Prio );
			this.Log2( "Klaar met aborteren..." );
		}

		// if not too much already running, immediately start.
		if( this.Running.length < this.MaxConcurrent ){
			if( Request.Prio > 0 ){

				this.Log2( "Nieuwe request met prio " + Request.Prio + " (" + Request.CommandId + ") running..." );

				//if( Request.Final > 0 ){
				//	this.AbortAll( Request.Prio );
				//	this.Log2( "Klaar met aborteren..." );
				//}
				this.Running.unshift( Request );
			}else
				this.Running.push( Request );
			Request.Run();
			return;
		}

		if( Request.Prio > 0 ){

			this.Log2( "Nieuwe request met prio " + Request.Prio + " (" + Request.CommandId + ") queued..." );

			//if( Request.Final > 0 ){
			//	this.AbortAll( Request.Prio );
			//	this.Log2( "Klaar met aborteren..." );
			//}
			this.Queued.unshift( Request );
		}else
			this.Queued.push( Request );

	}

	this.UnQueue = function( Request ){

		for( var i = 0; i < this.Queued.length; i++ ){
			if( this.Queued[i].CommandId == Request.CommandId )
				this.Queued.splice( i, 1 );
		}

		this.OneDone( Request ); // voor het geval hij al liep

	}

	this.OneDone = function( Request ){

		// uit de running-lijst kicken

		for( var i = 0; i < this.Running.length; i++ ){
			if( this.Running[i].CommandId == Request.CommandId )
				this.Running.splice( i, 1 );
		}

		// eventueel een nieuwe pakken



		if( this.Queued.length > 0 && this.Running.length < this.MaxConcurrent ){
			var NewRequest = this.Queued.shift();
			this.Running.push( NewRequest );
			NewRequest.Run();
		}

	}

	// Serve an AjaxRequest
	this.GetAjaxRequest = function(){

		var Request = new AjaxRequest( this );


		return Request;
	}

	// Get a HTTP requester
	this.GetRequester = function(){

		try{
			var requester = new XMLHttpRequest();
		}catch( error ){

			try{
				var requester = new ActiveXObject( "Microsoft.XMLHTTP" );
			}catch( error ){
				this.LogError( "GetRequester: Not even IE?!?" + error );
				throw "No requester!";

			}
		}

		//if( requester.overrideMimeType )
		//	requester.overrideMimeType('text/xml; charset=iso-8859-1');

		return requester;
	}

	this.GetObject = function( Victim ){

		// Gets a mixed victim
		// Returns an HTML element if possible, even if an id was given

		if( typeof( Victim ) == "string" ){
			try{
				return document.getElementById( Victim );
			}catch( error ){
			}
		}

		return Victim;

	}

	this.GetValue = function( Victim ){

		// Get the value of the mixed Victim reference to a form element
		// Do not return values of disabled elements
		// Makes an Array of multiple select values
		// Returns null is nothing is found

		Victim = this.GetObject( Victim );

		//alert( "GetValue: \n Victim: " + Victim + "\ntypeof: " + typeof( Victim ) + "\n .type: " + Victim.type );

		if( typeof( Victim ) != 'object' )
			return "";

		if( !Victim.type ){


			switch( Victim.tagName ){
				case 'OPTION':

					try{
						// IE6 (en misschien 7)
						if( Victim.attributes['value'].specified )
							return Victim.attributes['value'].value;
						else
							return Victim.innerHTML;
					}catch(e){
						// FF e.d.
						return Victim.value;
					}
			}

		}

		switch( Victim.type ){

			case 'hidden':
				return Victim.value;

			case 'text':
			case 'textarea':
			case 'password':
				if( Victim.disabled )
					return null;
				return Victim.value;


			case "radio":

				if( Victim.checked && Victim.value )
					return Victim.value;

				if( Victim.length ){
					for( i = 0; i < Victim.length; i++ )
						if( Victim[i].checked )
							return Victim[i].value;
				}else if( Victim.form && Victim.form[Victim.name].length > 0 ){
					for( i = 0; i < Victim.form[Victim.name].length; i++ )
						if( Victim.form[Victim.name][i].checked )
							return Victim.form[Victim.name][i].value;
				}else{
					// geen .length
					// geen .form
					// hoe dan???
				}

				return null;

			case "checkbox":

				if( Victim.checked && !Victim.disabled )
					return Victim.value;
				return null;

			case "select-one":

				if( Victim.disabled || Victim.options.length == 0 )
					return null;

				try{
					// IE6 (en misschien 7)
					if( Victim.options[ Victim.selectedIndex ].value )
						return Victim.options[ Victim.selectedIndex ].value;
					else if( Victim.options[ Victim.selectedIndex ].attributes['value'].specified )
						return Victim.options[ Victim.selectedIndex ].attributes['value'].value;
					else
						return Victim.options[ Victim.selectedIndex ].innerHTML;
				}catch(e){
					// FF e.d.
					if( Victim.options[ Victim.selectedIndex ] )
						return Victim.options[ Victim.selectedIndex ].value;
					else
						return null;
				}

			case "select-multiple":
				if( !Victim.disabled ){

					var Values = new Array();
					var optElem;

					for (var j = 0; j < Victim.length; j++){

						optElem = Victim.options[j];
						if (optElem.selected == true)
							Values.push( optElem.value );

					}
					//alert( "Multiple: return array: " + Values + " typeof: " + typeof( Values ) + " type: " + Values.valueOf() );
					return Values;
				}
				return null;



			case "button":
			case "submit":
				return null;

			default:
				alert( "No way to get value of a " + typeof( Victim ) + " of type " + Victim.toString() + " with inputtype " + Victim.type );
				return null;

		}
	}

	this.GetNewCommandId = function(){
		this.LastCommandId++;
		return this.LastCommandId;
	}

	this.SetErrorObject = function( Target ){

		// Lets you set a textarea object for Error output.

		var TargetObj = this.GetObject( Target );
		if( TargetObj.type != 'textarea' )
			alert( "Please only use testareas for debugging" );
		else
			this.ErrorObject = TargetObj;
	}

	this.LogError = function( Message ){

		// Writes the message to the error-textarea, if set

		if( this.ErrorObject != null && this.ErrorObject.type == 'textarea' ){
			try{
				this.ErrorObject.value += Message + "\n";
			}catch( e ){
			}
		}
	}

	this.SetLogObject2 = function( Target ){

		// Lets you set a textarea object for Log output.

		var TargetObj = this.GetObject( Target );

		if( TargetObj == null )
			return;

		if( TargetObj.type != 'textarea' )
			alert( "Please only use testareas for debugging" );
		else
			this.LogObject2 = TargetObj;
	}

	this.SetLogObject = function( Target ){

		// Lets you set a textarea object for Log output.

		var TargetObj = this.GetObject( Target );

		if( TargetObj == null )
			return;

		if( TargetObj.type != 'textarea' )
			alert( "Please only use testareas for debugging" );
		else
			this.LogObject = TargetObj;
	}

	this.Log = function( Message ){

		// Writes the message to the logging-textarea, if set

		if( this.LogObject != null && this.LogObject.type == 'textarea' ){
			try{
				this.LogObject.value += Message + "\n";
			}catch( e ){
			}
		}
	}

	this.Log2 = function( Message ){

		// Writes the message to the logging-textarea, if set

		if( this.LogObject2 != null && this.LogObject2.type == 'textarea' ){
			try{
				this.LogObject2.value += Message + "\n";
			}catch( e ){
			}
		}
	}




	this.SendTextToDiv = function( Button, Source, Target ){

		// Common AJAX action template
		// disables the button while busy
		// sends a single value
		// sticks output into the supplied target

		var Button = this.GetObject( Button );
		if( Button == null )
			return alert( "Given Button is not an object" );

		var Source = this.GetObject( Source );
		if( Source == null )
			return alert( "Given Source is not an object" );

		var Target = this.GetObject( Target );
		if( Target == null )
			return alert( "Given Target is not an object" );

		Button.onclick = function(){

			var Request = Ajax.GetAjaxRequest();
			Request.AddPostSource( Source );
			Request.StartHandler.Disable( Button );
			Request.SuccessHandler.HTMLTo( Target );
			Request.SuccessHandler.Enable( Button );
			Request.ErrorHandler.Enable( Button );
			Request.Send();

		}

	}

	this.SendFormToDiv = function( Button, SendForm, Target ){

		// Common AJAX action template
		// disables the button while busy
		// sends a complete form
		// sticks output into the supplied target

		var Button = this.GetObject( Button );

		var SendForm = this.GetObject( SendForm );
		if( SendForm == null )
			return alert( "Given SendForm is not an object" );

		var Target = this.GetObject( Target );
		if( Target == null )
			return alert( "Given Target is not an object" );

		var TempFunc = function(){

			var Request = Ajax.GetAjaxRequest();
			Request.StartHandler.Disable( Button );
			Request.PostForm( SendForm );
			Request.SuccessHandler.HTMLTo( Target );
			Request.SuccessHandler.Enable( Button );
			Request.ErrorHandler.Enable( Button );
			Request.Send();

		};

		if( Button != null )
			Button.onclick = TempFunc;

		SendForm.onsubmit = TempFunc;

	}

	this.Destroy = function( CommandId ){

		// Destroy a given AjaxRequest

		for( var i = 0;i < this.Stack.length;i++ ){

			if( this.Stack[i] != null && this.Stack[i].CommandId == CommandId ){
				this.Stack.splice(i,1);
				return;
			}

		}

	}


	this.AbortAll = function( Prio ){

		// For body onunload
		// Kill all running Ajax requests
		// From end to begin !!!!!!!!

		this.Log2( "AbortAl called met prio " + Prio );


		for( var i = this.Stack.length - 1; i >= 0; i-- ){
			if( !Prio || Prio > this.Stack[i].Prio )
				this.Stack[i].Abort();
		}


	}

	this.addStaticHandler = function(){
		var Request = this.GetAjaxRequest();
		this.StaticHandler = Request.StartHandler;
	}
	this.addStaticHandler();



	function implementEvent( victim, event, code ){

		victim = Ajax.GetObject( victim );

		if( event == 'load' && victim.loaded == true ){
			code();
		}else if( victim.addEventListener ){
			victim.addEventListener( event, code, false );
		}else{
			victim.attachEvent( 'on' + event, code );
		}

	}

	this.implementEvent = implementEvent;

}


function AjaxHandler( Request, CleanWhenDone ){

	// AjaxHandler object
	//
	// A container for all actions that are to be done
	// when a AjaxRequest object gets a certain state
	//
	// used for:
	// AjaxRequest.StartHandler    Called before sending the request
	// AjaxRequest.ErrorHandler    Called on time-out or http-error
	// AjaxRequest.SuccessHandler  Called when done

	// runtime stuff
	this.Request = Request;		// parent AjaxRequest object
	this.Actions = new Array(); // Stack of things to do

	// settings
	// inherit if AjaxRequest should be destroyed when done
	this.CleanWhenDone = CleanWhenDone;

	// methods

	this.DoActions = function( Message ){

		// Called by AjaxRequest when started, done or error
		// Loops through all actions and attempts to do them

		this.Request.Message = Message;

		// ErrorHandlers receive a message
		if( Message != null )
			this.Request.Factory.Log( "Handler, message: " + Message );

		for( var i = 0; i < this.Actions.length; i++ ){
			var Action = this.Actions[i];

				Ajax.Log( "Action doen" );
			try{
				Action.Name( Action.Value );
				Ajax.Log( "Action gedaan" );


			}catch( e ){
				Ajax.Log( "Action error" );
				alert( "Fout in request " + this.Request.Name + ": " + Action.Name + " value: " + Action.Value + " Error: " + e + " Error: " + e.description + " Error: " + e.message + " Error: " + e.stack + " Error: " + e.lineNumber );
			}
		}

		// Destroy TimeOut task, if set
		if( this.CleanWhenDone && this.Request.TimeOut > 0 )
			this.Request.Factory.Timer.RemoveAction( this.Request.TimeOutId );

		this.Request.Done();

		// Destroy AjaxRequest object, if setting demands it
		if( this.CleanWhenDone && this.Request.CleanWhenDone )
			this.Request.DestroySelf();

	}

	// Acties maken
	this.Destroy = function( Victim ){
		// destroy the victim HTMLelement when done
		this.Actions.push( new ArrayElement( this.DoDestroy, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.Enable = function( Victim ){
		// enable the victim HTMLelement when done
		this.Actions.push( new ArrayElement( this.DoEnable, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.Disable = function( Victim ){
		// disable the victim HTMLelement when done
		this.Actions.push( new ArrayElement( this.DoDisable, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.Hide = function( Victim ){
		// hide the victim HTMLelement when done
		this.Actions.push( new ArrayElement( this.DoHide, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.Show = function( Victim ){
		// show the victim HTMLelement when done
		this.Actions.push( new ArrayElement( this.DoShow, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.JSEvalTags = function( Victim ){

		// LET OP! parsed ALLEEN script tags als er iets voor staat,
		// zoals een <div>&nbsp;</div> (MET spatie, anders wordt de div ook gefilterd)

		this.Actions.push( new ArrayElement( this.DoJSEvalTags, null ) );

	}

	this.JSEval = function( Victim ){
		// Ignore victim
		// Find tags by id and replace them
		this.Actions.push( new ArrayElement( this.DoJSEval, null ) );
	}
	this.ReplaceById = function( Victim ){
		// Ignore victim
		// Find tags by id and replace them
		this.Actions.push( new ArrayElement( this.DoReplaceById, null ) );
	}

	this.AddTBODYBefore = function( Victim ){
		this.Actions.push( new ArrayElement( this.DoAddTBODYBefore, Victim ) );
	}

	this.AddTBODYAfter = function( Victim ){
		this.Actions.push( new ArrayElement( this.DoAddTBODYAfter, Victim ) );
	}

	this.ReplaceTBODYById = function( Victim ){
		// Ignore victim
		// Find tags by id and replace them
		this.Actions.push( new ArrayElement( this.DoReplaceTBODYById, null ) );
	}

	this.OptionsToSelect = function( Victim ){

		this.Actions.push( new ArrayElement( this.DoOptionsToSelect, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.ReplaceContentsById = function( Victim ){
		// Ignore victim
		// Find tags by id and replace them
		this.Actions.push( new ArrayElement( this.DoReplaceContentsById, null ) );
	}

	this.AddHTMLTo = function( Victim ){
		// Dump response into Victim.innerHTML
		this.Actions.push( new ArrayElement( this.DoAddHTMLTo, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.HTMLTo = function( Victim ){
		// Dump response into Victim.innerHTML
		this.Actions.push( new ArrayElement( this.DoHTMLTo, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.HTMLTRTo = function( Victim ){

		// Error sensitive little bastard
		// Take <TR> tags from the responseText and add them to a <table> or <tbody> tag.
		// Only works for <tr> tags within a <table> tag.



		this.Actions.push( new ArrayElement( this.DoHTMLTRTo, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.HTMLTBODYTo = function( Victim ){

		// Error sensitive little bastard
		// Take <TR> tags from the responseText and add them to a <table> or <tbody> tag.
		// Only works for <tr> tags within a <table> tag.



		this.Actions.push( new ArrayElement( this.DoHTMLTBODYTo, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.MessageTo = function( Victim ){

		// Send error message to Victim.innerHTML

		this.Actions.push( new ArrayElement( this.DoMessageTo, this.Request.Factory.GetObject( Victim ) ) );
	}

	this.Call = function( Victim ){
		// Call a user-function
		this.Actions.push( new ArrayElement( this.DoCall, Victim ) );
	}

	// Do the actions in the stack

	this.DoDestroy = function( Victim ){
		Victim.parentNode.removeChild( Victim );
	}

	this.DoEnable = function( Victim ){
		Victim.disabled = false;
	}
	this.DoDisable = function( Victim ){
		Victim.disabled = true;
	}
	this.DoHide = function( Victim ){
		
		if( ( Victim.tagName == 'TBODY' || Victim.tagName == 'TR' ) && (Ajax.browser.browser != "IE8" || Ajax.browser.effectiveBrowser == "IE8") && Ajax.browser.browser != "WK" ){
			try{
				Victim.style.visibility = 'collapse';
				return;
			}catch(e){
			}
		}

		Victim.style.visibility = 'hidden';
		Victim.style.display = 'none';


	}
	this.DoShow = function( Victim ){

		Victim.style.visibility = 'visible';


		if( Ajax.browser.browserMode == 'IE8_compat'){
			
			if( Victim.tagName == 'TABLE' )
				Victim.style.display = 'block';
			else if( Victim.tagName == 'TBODY' )
				Victim.style.display = 'block';
			else if( Victim.tagName == 'TR' )
				Victim.style.display = 'block';
			else if( Victim.tagName == 'INPUT' )
				Victim.style.display = 'inline';
			else
				Victim.style.display = 'block';
				
			return;
		}
		
		try{

			if( Victim.tagName == 'TABLE' )
				Victim.style.display = 'table';
			else if( Victim.tagName == 'TBODY' )
				Victim.style.display = 'table-row-group';
			else if( Victim.tagName == 'TR' )
				Victim.style.display = 'table-row';
			else if( Victim.tagName == 'INPUT' )
				Victim.style.display = 'inline';
			else
				Victim.style.display = 'block';
		}catch(e){
			// IE doesn't know table-row
			Victim.style.display = 'block';
		}

	}


	this.DoHideTR = function( Victim ){
		
		if( (Ajax.browser.browser != "IE8" || Ajax.browser.effectiveBrowser == "IE8") && Ajax.browser.browser != "WK" ){
			try{
				Victim.style.visibility = 'collapse';
				return;
			}catch(e){
			}
		}

		Victim.style.visibility = 'hidden';
		Victim.style.display = 'none';

	}


	this.DoShowTR = function( Victim ){

		Victim.style.visibility = 'visible';

		try{

			if( Victim.tagName == 'TABLE' )
				Victim.style.display = 'table';
			else if( Victim.tagName == 'TBODY' )
				Victim.style.display = 'table-row-group';
			else if( Victim.tagName == 'TR' )
				Victim.style.display = 'table-row';
			else
				Victim.style.display = 'block';
		}catch(e){
			// IE doesn't know table-row
			Victim.style.display = 'block';
		}
	}

	this.DoJSEval = function( Fake ){
		eval( Request.Requester.responseText );
	}

	this.DoJSEvalTags = function( Fake ){

		// LET OP! parsed ALLEEN script tags als er iets voor staat,
		// zoals een <div>&nbsp;</div> (MET spatie, anders wordt de div ook gefilterd)

		var div = document.createElement( 'DIV' );
		div.innerHTML = Request.Requester.responseText;

		var scripts = div.getElementsByTagName( 'SCRIPT' );

		for( var i = 0; i < scripts.length; i++ ){
			eval( scripts[i].innerHTML );
		}

	}

	this.DoReplaceById = function( Fake ){

		// Take any IDed tags from the responseText
		// Find any tags in the document having the same ID
		// Replace them

		// create virtual DIV as a DOM workbench
		var TempDiv = document.createElement('DIV');
		TempDiv.innerHTML = Request.Requester.responseText;

		// loop through all 1st level tags in the response
		var Child;
		var Target;


				Ajax.Log( "DoReplaceById" + TempDiv.childNodes.length );
				Ajax.Log( "Inhoud: " + TempDiv.innerHTML );

		while( Child = TempDiv.firstChild ){
				Ajax.Log( "tagname: " + Child.tagName );
			try{
				if( Child.id != undefined && Child.id != "" ){

					Target = document.getElementById( Child.id );

					try{
						Target.parentNode.replaceChild( Child, Target );
					}catch(e){
						Request.Factory.Log( "DoReplaceById: Fout bij replaceChild: " + e.description + e );
						throw e;
					}
				}else{
					TempDiv.removeChild( Child );
				}
			}catch(e){
				// ID not found in document: So what? Discard tag.
				Request.Factory.Log( "DoReplaceById: Geen Target gevonden voor " + Child.TagName + " met id " + Child.id + " error: " + e );
				TempDiv.removeChild( Child );
			}

		}

	}

	this.DoReplaceContentsById = function( Fake ){

		//debugger;
		// Take any IDed tags from the responseText
		// Find any tags in the document having the same ID
		// Replace them

		// create virtual DIV as a DOM workbench
		var TempDiv = document.createElement('DIV');
		TempDiv.innerHTML = Request.Requester.responseText;

		// loop through all 1st level tags in the response
		var Child;
		var SubChild;
		var Target;

		while( Child = TempDiv.firstChild ){
			try{
				if( Child.id != undefined && Child.id != "" ){

					Target = document.getElementById( Child.id );

					try{

						// Target.parentNode.replaceChild( Child, Target );
						while( SubChild = Target.firstChild )
							Target.removeChild( SubChild );
						while( SubChild = Child.firstChild )
							Target.appendChild( SubChild );
						//Target.innerHTML = Child.innerHTML;
						TempDiv.removeChild( Child );

					}catch(e){
						Request.Factory.Log( "DoReplaceContentsById: Fout bij replaceChild: " + e.description + e );
						throw e;
					}
				}else{
					TempDiv.removeChild( Child );
				}
			}catch(e){
				// ID not found in document: So what? Discard tag.
				Request.Factory.Log( "DoReplaceById: Geen Target gevonden voor " + Child.TagName + " met id " + Child.id + " error: " + e );
				TempDiv.removeChild( Child );
			}

		}

	}


	this.DoReplaceTBODYById = function( Fake ){

		// condition: tbody in response text MUST be within table tags!

		/*
		innerHTML Property
		The property is read/write for all objects except the following, for which it is read-only:
		COL, COLGROUP, FRAMESET, HTML, STYLE, TABLE, TBODY, TFOOT, THEAD, TITLE, TR.
		The property has no default value.
		*/

		//debugger;
		// Take any IDed tags from the responseText
		// Find any tags in the document having the same ID
		// Replace them

		// create virtual DIV as a DOM workbench
		var TempDiv = document.createElement('DIV');
		TempDiv.innerHTML = Request.Requester.responseText;

		// loop through all 1st level tags in the response
		var Child;
		var Target;

		var Tbodies = TempDiv.getElementsByTagName( 'TBODY' );

		while( Child = Tbodies[0] ){

			try{

				if( Child.id != undefined && Child.id != "" ){

					Target = document.getElementById( Child.id );

					try{

						Target.parentNode.replaceChild( Child, Target );

					}catch(e){

						Request.Factory.Log( "DoReplaceTBODYById: Fout bij replaceChild: " + e.description + e );
						Tbodies.removeChild( Child );
						throw e;
					}
				}
			}catch(e){
				// ID not found in document: So what? Discard tag.
				Request.Factory.Log( "DoReplaceTBODYById: Geen Target gevonden voor " + Child.TagName + " met id " + Child.id + " error: " + e );
				Tbodies.removeChild( Child );
			}
		}
	}


	this.DoAddTBODYAfter = function( Victim ){

		// Victim moet een tbody in een tabel zijn
		// Daaronder gaan we alle tbodies uit de responseXML invoegen.

		var TempDiv = document.createElement('DIV');
		TempDiv.innerHTML = Request.Requester.responseText;

		// plaats van Victim bepalen
		var table = Victim.parentNode;

		var Tbodies = TempDiv.getElementsByTagName( 'TBODY' );
		var Child;
		var Last = Victim;
		while( Child = Tbodies[0] ){

			table.insertBefore( Child, Last.nextSibling );
			Last = Child;

		}

		// 1 voor 1 de tbodies invoegen


	}
	this.DoAddTBODYBefore = function( Victim ){

		// Victim moet een tbody in een tabel zijn
		// Daaronder gaan we alle tbodies uit de responseXML invoegen.

		var TempDiv = document.createElement('DIV');
		TempDiv.innerHTML = Request.Requester.responseText;

		// plaats van Victim bepalen
		var table = Victim.parentNode;

		var Tbodies = TempDiv.getElementsByTagName( 'TBODY' );
		var Child;
		var Last = Victim;
		while( Child = Tbodies[0] ){

			table.insertBefore( Child, Last );

		}

		// 1 voor 1 de tbodies invoegen


	}

	this.DoHTMLTRTo = function( Victim ){


		// Dumps received <tr> tags from the response table into
		// a <table> or <tbody> in the document

		// Clean out Victim
		var Child = null;
		while( Child = Victim.firstChild ){
			Victim.removeChild( Child );
		}

		// create virtual DIV as a DOM workbench
		var tmpdiv = document.createElement('DIV');
		tmpdiv.innerHTML = Request.Requester.responseText;

		// Transfer TR's
		var Rows = tmpdiv.getElementsByTagName('tr');

 		while( Rows[0] != null ){

			try{
				Victim.appendChild( Rows[0] );
			}catch( e ){
				alert( "Error: " + e.message );
			}

		}

	}

	this.DoHTMLTBODYTo = function( Victim ){


		// Dumps received <tbody> tags from the response table into
		// a <table> or <tbody> in the document

		// Clean out Victim
		var Child = null;
		while( Child = Victim.firstChild ){
			Victim.removeChild( Child );
		}

		// create virtual DIV as a DOM workbench
		var tmpdiv = document.createElement('DIV');
		tmpdiv.innerHTML = Request.Requester.responseText;

		// Transfer TBODY'S
		var Rows = tmpdiv.getElementsByTagName('tbody');

 		while( Rows[0] != null ){

			try{
				Victim.appendChild( Rows[0] );
			}catch( e ){
				alert( "Error: " + e.message );
			}

		}

	}

	this.DoOptionsToSelect = function( Victim ){

		// Dumps received <option> and <optgroup> tags from the response table into
		// a <select> in the document

		var selectedIndex = Victim.selectedIndex;

		// Clean out Victim
		var Child = null;
		while( Child = Victim.firstChild ){
			Victim.removeChild( Child );
		}

		// create virtual DIV as a DOM workbench
		var TempDiv = document.createElement('DIV');
		TempDiv.innerHTML = Request.Requester.responseText;

		// Transfer TR's
		var Rows = TempDiv.getElementsByTagName('select');

 		if( Rows[0] != null ){

			while( Child = Rows[0].firstChild ){

				var Tag = new String( Child.tagName );

				if( Tag.toLowerCase() == 'option' || Tag.toLowerCase() == 'optgroup' ){

					try{

						Victim.appendChild( Child );
						//Rows[0].removeChild( Child );

					}catch(e){
						Request.Factory.Log( "DoReplaceById: Fout bij append of removeChild: " + e.description + e );
						throw e;
					}
				}else{
					Rows[0].removeChild( Child );
				}

			}



		}

		if( selectedIndex == -1 && Victim.length > 0 )
			Victim.selectedIndex = 0;
		else
			Victim.selectedIndex = selectedIndex;

	}


	this.DoAddHTMLTo = function( Victim ){



		var TempDiv = document.createElement('DIV');
		TempDiv.innerHTML = Request.Requester.responseText;

		while( Child = TempDiv.firstChild ){
			Victim.appendChild( Child );
		}

	}

	this.DoHTMLTo = function( Victim ){

		Victim.innerHTML = Request.Requester.responseText;

	}
	this.DoMessageTo = function( Victim ){
		Victim.innerHTML = Request.Message;
	}

	this.DoCall = function( Victim ){
		Victim();
	}


}

function AjaxTimer(){

	// AjaxTimer object
	//
	// Holds a stack of TimerTasks and does them when needed.
	// Used for AjaxRequest time-outs but can also be used for custom actions

	this.Secs = 0;
	this.Running = false;
	this.Delay = 1000;

	this.TimerId = null;

	// Stack of TimerTask objects
	this.Tasks = new Array();


	this.AddAction = function( Action, After, Once, Name ){

		// Add an AjaxTimerTask object
		// After defines in how many seconds the action should take place
		// Once tells wether to keep rescheduling the Task, or to destroy it when done

		if( Once == null )
			Once = true;

		var NewTask = new AjaxTimerTask( this, After, Once, Name );
		NewTask.Action = Action;

		return this.Tasks.push( NewTask ) - 1;
	}

	this.AddTimeOut = function( AjaxRequest ){

		return this.AddAction( function(){ AjaxRequest.DoTimeOut() }, AjaxRequest.TimeOut, true, "TimeOut" );

	}

	this.RemoveAction = function( ActionId ){

		this.Tasks[ActionId] = null;
	}

	this.Tick = function(){

        this.Running = true;

		var Task;
		for( var i = 0; i < this.Tasks.length; i++ ){

			Task = this.Tasks[i];

			if( Task != null && this.Secs >= Task.ActionTime ){

				try{
					Task.Action();
				}catch( e ){
					alert( "Task " + i + " failed: " + e );
				}

				if( Task.Once == true )
					this.Tasks[i] = null;
				else
					Task.ActionTime = this.Secs + Task.After;

			}

		}


		this.Secs++;

        this.TimerId = window.setTimeout( "Ajax.Timer.Tick()", this.Delay );
	}

	this.Tick();

}

function AjaxTimerTask( AjaxTimer, After, Once, Name ){

	if( Once == null )
		Once = true;

	this.Name = Name;

	this.AjaxTimer = AjaxTimer;
	this.After = After;
	this.Once = Once;

	this.ActionTime = AjaxTimer.Secs + After;

	this.Action = null;
	this.Done = false;

	// alert( "Nieuwe TimerTask gemaakt. ActionTime: " + this.ActionTime + "Naam: " + this.Name );

}


// ArrayElement object
// De keys vna een Array zijn altijd numeriek, dus er is geen naam/waarde opbouw mogelijk.
// Met dit ding lukt het wel

function ArrayElement( Name, Value ){

	this.Name = Name;
	this.Value = Value;

	this.QueryString = function(){

		if( this.Value == null )
			return null;

		if( isArray( this.Value ) ){

			var Result = "";

			for( var i = 0; i < this.Value.length; i++ ){
				if( this.Value[i] != null ){

					Result += this.Name + "[]" + "=" + this.escapeVal( this.Value[i] );
					if( i < this.Value.length - 1 )
						Result += '&';

				}
			}
			//alert( Result );
			return Result;

		}

		return this.Name + '=' + this.escapeVal( this.Value );
	}
	
	this.escapeVal = function( val ){
		var escaped = escape(val);
		escaped = escaped.replace( new RegExp( '\\+','g' ), '%2B' );
		return escaped;
	}
	
}
