/**
 * WJAeroplane is the main Aeroplane Javascript, this should be used to access Aeroplane functions
 *
 * @since Fri Jul 04 2008
 * @author Ron Rademaker
 **/
var WJAeroplane = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJAeroplane
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @return WJAeroplane
	 **/
	initialize: function() {
		this.menu = null;
		this.contextmenu = null;
		this.spinErrorHandlers = {};
		this.windowmanager = new WJWindowManager();
		this.unloadmanager = new WJUnloadManager(this.allowUnload.bind(this) );
		this.topbarId = "aeroplane_topbar";
		this.contentId = "aeroplane_main";
		this.contentClass = "aeroplane_sitemaincontent";
		this.contentBlockContainers = {};
		this.dbtables = {};
		this.layers = [];
	},

	/**
	 * aeroplaneSpinError
	 *
	 * Fallback function to handle unhandled spin (ajax) errors
	 *
	 * @since Wed Jan 07 2009
	 * @access public
	 * @param response response
	 * @return void
	 **/
	aeroplaneSpinError: function(response, lastSpin) {
		for (code in this.spinErrorHandlers) {
			if (code == response.status && typeof(this.spinErrorHandlers[code])=="function") {
				this.spinErrorHandlers[code](response, lastSpin);
				return;
			}
		}
		var reload = function() {
			this.unloadmanager.suspend();
			document.location.reload();
		}.bind(this);
		WJWindow.alert("Er heeft zich een fout voorgedaan, Windmill CMS zal automatisch worden herladen. Neem contact op met Connectholland wanneer dit probleem zich blijft voordoen.", reload);
	},

	/**
	 * registerSpinErrorHandler
	 *
	 * Registers a callback to the error status
	 *
	 * @since Thu Feb 12 2009
	 * @access public
	 * @param integer code
	 * @param Function callback
	 * @return void
	 **/
	registerSpinErrorHandler: function(code, callback) {
		this.spinErrorHandlers[code] = callback;
	},

	/**
	 * addMainMenuItem
	 *
	 * Adds an item to the main menu, these are used only to access the submenu's
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @param string id
	 * @param WJMenu menu
	 **/
	addMainMenuItem: function(id, menu) {
		if (this.menu == null) {
			this.menu = new WJMenu("main", -1);
		}
		this.menu.addItem(id, menu);
	},

	/**
	 * addMenuItem
	 *
	 * Adds a menu item to a main menu item, these contain the actual actions
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @param string mainid
	 * @param string id
	 * @param WJMenu menu
	 **/
	addMenuItem: function(mainid, id, menu) {
		this.menu.getItem(mainid).addItem(id, menu);
	},

	/**
	 * openApplication
	 *
	 * Creates an application interface and lets the window manager run the application
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @param string application
	 * @param mixed applicationargs (not in declaration)
	 * @return WJApplicationInterface
	 **/
	openApplication: function(application) {
		var applicationInterface = window["WJ" + application + "Interface"].construct(arguments);
		this.windowmanager.startApplication(applicationInterface);
		return applicationInterface;
	},

	/**
	 * createTopBar
	 *
	 * Creates the html elements for the topbar
	 * Uses dom manipulation to keep references made by other scripts intact
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	createTopBar: function() {
		var bodyChildren = Element.extend(document.body).childElements();
		var topbarDiv = document.body.appendChild(new Element("div", {id: this.topbarId} ) );
		var mainDiv = document.body.appendChild(new Element("div", {"class": this.contentClass, id: this.contentId} ) );
		for (i=0; i < bodyChildren.length; i++) {
			if (bodyChildren[i].tagName != "script") {
				mainDiv.appendChild(bodyChildren[i] );
			}
		}
	},

	/**
	 * printPage
	 *
	 * Interface to native window print because 'Nightmare from Redmond 7' can't bind natives
	 *
	 * @since Fri Feb 27 2009
	 * @access public
	 * @return void
	 **/
	printPage: function() {
		window.print();
	},

	/**
	 * showTopBar
	 *
	 * Fills the topbar element from the temporary topbar and shows it
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @param string temporaryid
	 * @return void
	 **/
	showTopBar: function(temporaryid) {
		// prevent a scrollbars on the page when in CMS mode, if needed it should be in the div
		document.body.setStyle({"overflow": "hidden"});
		Element.extend(document.documentElement).setStyle({"overflow": "hidden"});
		var tempTop = document.getElementById(temporaryid);
		var topbar = document.getElementById(this.topbarId);
		topbar.innerHTML = tempTop.innerHTML;
		topbar.className = tempTop.className;
		tempTop.parentNode.removeChild(tempTop);
		var fixMainDivHeight = function(mainDiv, topbar) {
			$(mainDiv).setStyle({"height": ($(document).viewport.getHeight() - $(topbar).getHeight() ) + "px"});
		}.bind(window, this.getContentElement(), this.getTopbar() );
		Event.observe(window, "resize", fixMainDivHeight);
		fixMainDivHeight();
	},

	/**
	 * getContentElement
	 *
	 * Gets the main content element
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return DOMElement
	 **/
	getContentElement: function() {
		return document.getElementById(this.contentId);
	},

	/**
	 * getTopbar
	 *
	 * Gets the topbar element
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return DOMElement
	 **/
	getTopbar: function() {
		return document.getElementById(this.topbarId);
	},

	/**
	 * registerContentBlock
	 *
	 * Registers a content block on the page
	 *
	 * @since Mon Aug 04 2008
	 * @access public
	 * @param string id
	 * @param string identifier
	 * @param string module
	 * @param string basemodule
	 * @param object properties
	 * @return void
	 **/
	registerContentBlock: function(id, cbcid, identifier, module, basemodule, properties) {
		var properties = properties || {};
		var cblock = new WJContentBlock(id, module, properties);
		cblock.setIdentifier(identifier);
		this.contentBlockContainers[cbcid].registerContentBlock(cblock);
	},

	/**
	 * registerDBTable
	 *
	 * Registers a dbtable record (with contentblocks) on the page
	 *
	 * @since Tue Mar 31 2009
	 * @access public
	 * @param string htmlid
	 * @param string module
	 * @param string collection
	 * @return void
	 **/
	registerDBTable: function(htmlid, module, collection) {
		this.dbtables[htmlid] = new WJDBTable(htmlid, module, collection);
	},

	/**
	 * registerContentBlockContainer
	 *
	 * Registers a content block container, a cbc contains (or is able to contain) content blocks
	 *
	 * @since Mon Sep 22 2008
	 * @access public
	 * @param string id
	 * @param string identifier
	 * @param string module
	 * @param string basemodule
	 * @param object properties
	 * @return void
	 **/
	registerContentBlockContainer: function(id, identifier, module, basemodule, properties) {
		var properties = properties || {};
		var cbc = new WJContentBlockContainer(id, identifier, module, properties);
		this.contentBlockContainers[identifier] = cbc;
	},

	/**
	 * editContentBlock
	 *
	 * Opens an editor to edit the content block in the html element with id
	 *
	 * @since Mon Aug 04 2008
	 * @access public
	 * @param string cbcid
	 * @param string blockid
	 * @return void
	 **/
	editContentBlock: function(cbcid, blockid) {
		WJDebugger.log(WJDebugger.NOTICE, "Edit content block", cbcid, blockid);
		var contentBlock = this.getContentBlock(cbcid, blockid);
		if (contentBlock) {
			var application = this.openApplication(contentBlock.getApplicationName(), contentBlock.getModule(), contentBlock.getIdentifier(), contentBlock.getProperties() );
			this.contentBlockContainers[cbcid].registerUpdate(blockid);
			application.setCBC(this.contentBlockContainers[cbcid]);
			application.setReloadCallback(this.contentBlockContainers[cbcid].callUpdateCallbacks.bind(this.contentBlockContainers[cbcid]) );
		}
	},

	/**
	 * getCBCs
	 *
	 * Gets all active cbcs for module
	 *
	 * @since Tue Sep 23 2008
	 * @access public
	 * @param string module
	 * @return void
	 **/
	getCBCs: function(module) {
		var cbcs = new Array();
		for (var key in this.contentBlockContainers) {
			if (this.contentBlockContainers[key].getModule() == module) {
				cbcs.push(this.contentBlockContainers[key]);
			}
		}
		return cbcs;
	},

	/**
	 * getContentBlock
	 *
	 * Returns the block identified by id
	 *
	 * @since Wed Aug 6 2008
	 * @access public
	 * @param string cbcid
	 * @param string blockid
	 * @return WJContentBlock
	 **/
	getContentBlock: function(cbcid, blockid) {
		return (this.contentBlockContainers[cbcid]) ? this.contentBlockContainers[cbcid].getContentBlock(blockid) : false;
	},

	/**
	 * isContentBlock
	 *
	 * Tells if the given id identifies a contentBlock
	 *
	 * @since Mon Sep 8 2008
	 * @access public
	 * @param string cbcid
	 * @param string blockid
	 * @return boolean
	 **/
	isContentBlock: function(cbcid, blockid) {
		return (this.contentBlockContainers[cbcid] && this.contentBlockContainers[cbcid].getContentBlock(blockid) );
	},

	/**
	 * contextMenu
	 *
	 * Handles the case a contextmenu fires an event
	 *
	 * @since Fri Aug 8 2008
	 * @access public
	 * @param Event event
	 * @param string blockid
	 * @return void
	 **/
	contextMenu: function(cbcid, blockid, action) {
		if (blockid == "dbtable") {
			switch (action) {
				case "update":
					this.dbtables[cbcid].openContentEditor();
					break;
				case "delete":
					this.dbtables[cbcid].deleteRecord();
					break;
			}
		}
		else {
			switch (action) {
				case "update":
					this.editContentBlock(cbcid, blockid);
					break;
				case "delete":
					this.deleteContentBlock(cbcid, blockid);
					break;
				case "online":
					this.setContentBlockOnline(true, cbcid, blockid);
					break;
				case "offline":
					this.setContentBlockOnline(false, cbcid, blockid);
					break;
				case "moveUp":
				case "moveDown":
					this.contentBlockFunction(action, cbcid, blockid);
					break;
			}
		}
	},

	/**
	 * contentBlockFunction
	 *
	 * Calls action on the contentblock with blockid in cbcid
	 *
	 * @since Mon Feb 16 2009
	 * @access public
	 * @param string action
	 * @param string cbcid
	 * @param string blockid
	 * @return void
	 **/
	contentBlockFunction: function(action, cbcid, blockid) {
		var spin = new WJSpin();
		var contentBlock = this.getContentBlock(cbcid, blockid);
		var contentBlockContainer = this.contentBlockContainers[cbcid];
		spin.update(new WJUrl({"ct": "wmdynamic", "module": "Wmcb", "collections": contentBlock.properties.collection}), "Wmcb", action, {objid: contentBlock.getIdentifier()}, [contentBlock.postActionHandler.bind(contentBlock, action), contentBlockContainer.postActionHandler.bind(contentBlockContainer, contentBlock, action)]);
	},

	/**
	 * deleteContentBlock
	 *
	 * Deletes a contentblock (used from the context menu
	 * todo: dry this with WJContentEditorInterface's delete contentblock
	 *
	 * @since Fri Jan 09 2009
	 * @access public
	 * @param string cbcid
	 * @param string blockid
	 * @return void
	 **/
	deleteContentBlock: function(cbcid, blockid) {
		WJWindow.booleanConfirm("Weet je zeker dat je dit contentblok wilt verwijderen?", function(cbcid, blockid, window, e, confirm) {
			if (!confirm) {
				return;
			}
			var spin = new WJSpin();
			var contentBlock = this.getContentBlock(cbcid, blockid);
			var cbcon = this.contentBlockContainers[cbcid];
			spin.update(new WJUrl({"ct": "wmdynamic", "module": "Wmcb", "collections": contentBlock.properties.collection}), "Wmcb", "delete", {objid: contentBlock.getIdentifier() }, [contentBlock.removeContextMenu.bind(contentBlock), contentBlock.getElement().remove.bind(contentBlock.getElement() ), cbcon.refreshContextMenuButtons.bind(cbcon)]);
		}.bind(this, cbcid, blockid) );
	},

	/**
	 * setContentBlockOnline
	 *
	 * Sets a contentBlock online / offline
	 *
	 * @since Fri Jan 09 2009
	 * @access public
	 * @param boolean online
	 * @param string cbcid
	 * @param string blockid
	 * @return void
	 **/
	setContentBlockOnline: function(online, cbcid, blockid) {
		var spin = new WJSpin();
		var contentBlock = this.getContentBlock(cbcid, blockid);
		var func = online ? "online" : "offline";
		var message = online ? "Het contentblock staat nu online": "Het contentblock staat nu offline";
		var updateClassname = online ? function() { this.removeClassName("offline"); }.bind(contentBlock.getElement() ) : function() { this.addClassName("offline"); }.bind(contentBlock.getElement() );
		spin.update(new WJUrl({"ct": "wmdynamic", "module": "Wmcb", "collections": contentBlock.properties.collection}), "Wmcb", func, {objid: contentBlock.getIdentifier() }, [WJWindow.notice.bind(WJWindow, message), updateClassname]);
	},

	/**
	 * registerContextMenu
	 *
	 * Binds a listener to the contextmenu
	 *
	 * @since Fri Aug 8 2008
	 * @access public
	 * @param string menuid
	 * @param string cbcid
	 * @param string blockid
	 * @return void
	 **/
	registerContextMenu: function(menuid, cbcid, blockid) {
		if (!this.contextmenu) {
			this.contextmenu = new WJContextMenu();
		}
		this.contextmenu.register(menuid, cbcid, blockid);
	},
	
	/**
	 * registerDBTableContextMenu
	 *
	 * Binds a listener to the contextmenu
	 *
	 * @since Tue Mar 31 2009
	 * @access public
	 * @param string menuid
	 * @param string htmlid 
	 * @param string id 
	 * @return void
	 **/
	registerDBTableContextMenu: function(menuid, htmlid, id) {
		if (!this.contextmenu) {
			this.contextmenu = new WJContextMenu();
		}
		this.contextmenu.registerDBTable(menuid, htmlid, id);
	},

	/**
	 * hideAllContextMenus
	 *
	 * Calls the hideAll method on the contextmenu handler
	 *
	 * @since Fri Aug 8 2008
	 * @access public
	 * @return void
	 **/
	hideAllContextMenus: function() {
		this.contextmenu.hideAll();
	},

	/**
	 * logout
	 *
	 * Logs the user out
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @param string logouturl
	 * @return void
	 **/
	logout: function(logouturl) {
		if (logouturl == null) {
			document.location.href = "/?useraction=logout";
		}
		else {
			document.location.href = logouturl;
		}
	},

	/**
	 * loadingCursor
	 *
	 * Changes the current cursor to a loader or back to normal
	 *
	 * @since Thu Sep 25 2008
	 * @access public
	 * @param boolean loading
	 * @param wjwindow wjwindow
	 * @param function callback
	 * @return void
	 **/
	loadingCursor: function(loading, wjwindow, callback) {
		var wjwindow = wjwindow || false;
		var callback = callback || false;
		if (wjwindow) {
			wjwindow.setLoading(loading, callback);
		}
		if (loading) {
			this.layers.push(new WJModalLayer() );
			$(document.body).setStyle({"cursor": "wait"});
			$$(".aeroplane_button").each(function(s) { $(s).setStyle({"cursor": "wait"}); } );
			$$(".dijitTab").each(function(s) { $(s).setStyle({"cursor": "wait"}); } );
		}
		else {
			this.layers.each(function(l) { l.destroy(); });
			this.layers = [];
			$(document.body).setStyle({"cursor": "auto"});
			$$(".aeroplane_button").each(function(s) { $(s).setStyle({"cursor": "default"}); } );
			$$(".dijitTab").each(function(s) { $(s).setStyle({"cursor": "pointer"}); } );
		}
	},

	/**
	 * respinMain
	 *
	 * Executes a spin request to replace the entire main div with the latest info from the database (use this when local ajax updates can't be used to show updated information)
	 *
	 * @since Wed Feb 25 2009
	 * @access public
	 * @return void
	 **/
	respinMain: function(callback) {
		var callback = Object.isFunction(callback) ? callback : function() {};
		var baseUrl = document.location.href.replace("#", "");
		var url = (baseUrl.indexOf("?") == -1) ? baseUrl + "?wmtrigger[]=requestufts&wmtrigger[]=spin" : baseUrl + "&wmtrigger[]=requestufts&wmtrigger[]=spin";
		url = (url.indexOf("dt=") == -1) ? url + "&dt=default" : url;
		var spin = new WJSpin();
		if (!$("aeroplane_main") ) {
			aeroplane.loadingCursor(false);
			return;
		}
		spin.content(new WJUrl({}, url), [function(response) {
			var newcontent = response.documentElement.getElementsByTagName("body")[0];
			if (document.importNode) {
				var stub = new Element("div");
				stub.appendChild($("aeroplane_main").ownerDocument.importNode(newcontent, true) );
				stub.innerHTML += "";
				var newdoc = stub.firstDescendant();
				var origdoc = $("aeroplane_main");

				for (var i = 0; i < origdoc.childNodes.length; i++) {
					if ( (origdoc.childNodes[i].nodeName == newdoc.nodeName) && (origdoc.childNodes[i].className == newdoc.className) ) {
						$(origdoc.childNodes[i]).update(newdoc);
					}
				}
			}
			else {
				var origdoc = $("aeroplane_main");

				for (var i = 0; i < origdoc.childNodes.length; i++) {
					if ( (origdoc.childNodes[i].nodeName == newcontent.nodeName) && (origdoc.childNodes[i].className == newcontent.className) ) {
						$(origdoc.childNodes[i]).update(newcontent);
					}
				}
				origdoc.innerHTML = origdoc.innerHTML;
			}
			aeroplane.loadingCursor(false);
		}, callback]);
	},

	/**
	 * allowUnload
	 *
	 * Does a check to tell if an unload is allowed (says it is not allowed if any application that is not readonly is open)
	 *
	 * @since Tue Feb 17 2009
	 * @access public
	 * @return boolean
	 **/
	allowUnload: function() {
		var apps = this.windowmanager.getRunningApplication();
		for (var i = 0; i < apps.length; i++) {
			if (!apps[i].readonly) {
				return false;
			}
		}
		return true;
	}
});

/**
 * WJWindowManager manages all windows (javascript WJWindows) for Aeroplane
 *
 * @since Fri Jul 04 2008
 * @author Ron Rademaker
 **/
var WJWindowManager = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJWindowManager
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @return void
	 **/
	initialize: function() {
		this.applications = new Array(); // Currently running applications (javascript interfaces to the application)
		this.windows = {}; // Current windows, showing or hidden. Each window must be associated with an application
		this.applicationRunner.bind(this).repeat(0.25);
		this.applicationRun = false;
		this.pid = 0; // give all running applications a unique process id
		dojo.require("dojo.parser");
	},

	/**
	 * getMaxZIndex
	 *
	 * Gets the highest used z-index is the current dom
	 *
	 * @since Wed Nov 19 2008
	 * @access public
	 * @return int
	 **/
	getMaxZIndex: function() {
		var nodes = $$("."); 
		var lenght = nodes.lenght; 
		var z = 0; 
		for (var i = 0; i < lenght; i++) { 
			z = Math.max(z, nodes[i].getStyle("z-index") ); 
		}
		return z;
	},

	/**
	 * startApplication
	 *
	 * Starts an application from the application interfaces
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @param WJApplicationInterface application
	 * @return void
	 **/
	startApplication: function(application) {
		this.pid++;
		application.setPid(this.pid);
		var pid = application.getPid();
		this.windows["application" + pid] = {};
		this.applications.push(application);
		application.wakeUp();
	},

	/**
	 * applicationRunner
	 *
	 * Wakes all running applications up and lets the do something (if they need to do something)
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @return void
	 **/
	applicationRunner: function() {
		if (!this.applicationRun) {
			this.applicationRun = true;
			for (var i = 0; i < this.applications.length; i++) {
				this.applications[i].wakeUp();
			}
			for (var i = 0; i < this.applications.length; i++) {
				if (this.applications[i].closed() ) {
					this.closeApplication(this.applications[i], i);
				}
			}

			this.cleanUpDijitWidgets();
			this.applicationRun = false;
		}
	},

	/**
	 * getRunningApplication
	 *
	 * Returns the running applications
	 *
	 * @since Wed Feb 18 2009
	 * @access public
	 * @return Array
	 **/
	getRunningApplication: function() {
		return this.applications;
	},

	/**
	 * cleanUpDijitWidgets
	 *
	 * Destroys all dijit widgets that are no longer in the dom
	 *
	 * @since Tue Nov 04 2008
	 * @access public
	 * @return void
	 **/
	cleanUpDijitWidgets: function() {
		dijit.registry.forEach(function(widget) {
			if (!$(widget.domNode).descendantOf(widget.domNode.ownerDocument.documentElement) ) {
				WJDebugger.log(WJDebugger.INFO, "Destroying dijit widget", widget);
				widget.destroyRecursive(false);
			}
		});
	},

	/**
	 * closeApplication
	 *
	 * Closes the main window of this application and removes the application from the applications array
	 *
	 * @since Wed Jul 16 2008
	 * @access public
	 * @param WJApplicationInterface application
	 * @param integer index
	 * @return void
	 **/
	closeApplication: function(application, index) {
		var wjwindow = this.getMainWindow(application);
		wjwindow.hide();
		this.applications.splice(index, 1);
	},
	
	/**
	 * destroyMainWindow
	 *
	 * Destroy the main window of the given application
	 *
	 * @since Tue Aug 19 2008
	 * @access public
	 * @param WJApplicationInterface application
	 * @return void
	 **/
	destroyMainWindow: function(application) {
		WJDebugger.log(WJDebugger.INFO, "Destroy main window", this);
		this.getMainWindow(application).destroy();
	},
	
	/**
	 * getMainWindow
	 *
	 * Gets the main window for application
	 * Creates a window if there is no window
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @param WJApplicationInterface application
	 * @return DOMElement
	 **/
	getMainWindow: function(application) {
		return this.getWindow(application, "main");
	},

	/**
	 * applicationResponse
	 *
	 * Handles according to the response inside the application
	 *
	 * @since Tue Aug 19 2008
	 * @access public
	 * @param WJApplicationInterface application
	 * @param WJWindow wjwindow
	 * @param Event event
	 * @return void
	 **/
	applicationResponse: function(application, wjwindow, event) {
		WJDebugger.log(WJDebugger.NOTICE, "Application response", application, wjwindow, event);
		switch (event.eventName) {
			case "wjgui:save":
				application.save();
				break;
			case "wjgui:close":
			case "wjgui:false":
			case "wjgui:cancel":
				application.close();
				break;
			default:
				var eventName = event.eventName.sub("wjgui:", "");
				application.handleWindowEvent(eventName);
				break;
		}
	},

	/**
	 * getWindow
	 *
	 * Gets a window of tyoe for application
	 * Creates a window if there is no window
	 *
	 * @since Mon Jul 28 2008
	 * @access public
	 * @param WJApplicationInterface application
	 * @param string type
	 * @return DOMElement
	 **/
	getWindow: function(application, type) {
		var pid = application.getPid();
		if (this.windows["application" + pid]) {
			if (this.windows["application" + pid][type]) {
				return this.windows["application" + pid][type];
			}
		}
		if (type == "main") {
			this.windows["application" + pid][type] = application.createMainWindow();
		}
		else {
			this.windows["application" + pid][type] = application.createWindow(type);
		}
		return this.windows["application" + pid][type];
	},
	
	/**
	 * getMainWindowWidth
	 *
	 * Gets the width for main windows (ie. full apps)
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return integer
	 **/
	getMainWindowWidth: function() {
		return (document.viewport.getWidth() - (this.getMainWindowLeft() + this.getMainWindowRight() ) );
	},

	/**
	 * getMainWindowHeight
	 *
	 * Gets the height for main windows (ie. full apps)
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return integer
	 **/
	getMainWindowHeight: function() {
		return (document.viewport.getHeight() - (this.getMainWindowTop() + this.getMainWindowBottom() ) );
	},

	/**
	 * getMainWindowTop
	 *
	 * Gets the Y position for main windows (ie. full apps)
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return integer
	 **/
	getMainWindowTop: function() {
		if (aeroplane.menu) {
			return 42;
		}
		else {
			return 2;
		}
	},

	/**
	 * getMainWindowBottom
	 *
	 * Gets the Y position for the bottom of main windows (ie. full apps)
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @return integer
	 **/
	getMainWindowBottom: function() {
		return 2;
	},
	
	/**
	 * getMainWindowLeft
	 *
	 * Gets the X position for main windows (ie. full apps)
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return integer
	 **/
	getMainWindowLeft: function() {
		return 5;
	},

	/**
	 * getMainWindowRight
	 *
	 * Gets the X position for the right of main windows (ie. full apps)
	 *
	 * @since Fri Aug 08 2008
	 * @access public
	 * @return integer
	 **/
	getMainWindowRight: function() {
		return 5;
	},
	
	/**
	 * maximize
	 *
	 * Maximizes the given window according to the padding set in this manager
	 *
	 * @since Fri Aug 8 2008
	 * @access public
	 * @param WJWindow window
	 * @return void
	 **/
	maximize: function(window) {
		window.maximize(this.getMainWindowTop(), this.getMainWindowRight(), this.getMainWindowBottom(), this.getMainWindowLeft() );
	}
});

/**
 * WJApplicationInterface is the base interface to applications. Manages communication with the server through WJSpin
 *
 * @since Fri Jul 04 2008
 * @author Ron Rademaker
 **/
var WJApplicationInterface = Class.create({
	/**
	 * WJApplicationInterface keeps track of the current state of the running application. Possible states are:
	 * starting: the application is starting up
	 * running: the application is running fine
	 * waiting: the application is waiting for a server response or for user input
	 * busy: the application is running slowly
	 * crashed: the application has crashed (of course, being able to determine this is rare)
	 * closing: the application is closing down
	 * closed: the application has been closed and others may forget about it
	 **/

	/**
	 * initialize
	 *
	 * Creates a new WJApplicationInterface
	 *
	 * @since Fri Jul 04 2008
	 * @access public
	 * @param string appname
	 * @return void
	 **/
	initialize: function(appname) {
		this.spin = new WJSpin();
		this.url = new WJUrl({}, "/index.php");
		this.url.setCt("wmdynamic");
		this.url.addParameter("application", appname);
		this.appname = appname;
		this.state = "starting";
		this.pid = null;
		this.readonly = false;
	},

	/**
	 * handleDatacollector
	 *
	 * Accepts a JSON datacollector, turns it into a WJDataCollector and sets things in motion to fill the data collector (ie. load a gui)
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @param object datacollector
	 * @return void
	 **/
	handleDatacollector: function(datacollector) {
		var factory = new WJDatacollectorFactory();
		this.datacollector = factory.importDatacollector(datacollector);
		if (this.datacollector.fields.aeroplaneprofile) {
			this.aeroplaneprofile = new WJAeroplaneprofile(this.datacollector.fields.aeroplaneprofile.getValue(), this);
		}
		WJDebugger.log(WJDebugger.INFO, "Imported datacollector", this.datacollector);
		if (!this.gui) {
			this.gui = this.buildGui();
		}
		this._loadDatacollectorGui();
	},

	/**
	 * _loadDatacollectorGui
	 *
	 * Loads the current datacollector in the current gui
	 *
	 * @since Fri Sep 19 2008
	 * @access public
	 * @return void
	 **/
	_loadDatacollectorGui: function() { 
		this.gui.setDatacollector(this.datacollector);
		this.gui.addButtons(aeroplane.windowmanager.getMainWindow(this) );
		WJDebugger.log(WJDebugger.DEBUG, "Load content into", this.currentComponent);
		this.currentComponent.update("");
		aeroplane.windowmanager.cleanUpDijitWidgets();
		this.gui.show(aeroplane.windowmanager.getMainWindow(this), this.currentComponent, this.spin, this.contentUpdated.bind(this) );
	},

	/**
	 * setReloadCallback
	 *
	 * Sets a reload callback, reloads the page part with the content being edited in this application
	 *
	 * @since Mon Aug 04 2008
	 * @access public
	 * @param function callback
	 * @return void
	 **/
	setReloadCallback: function(callback) {
		this.reloadCallback = callback;
	},

	/**
	 * buildGui
	 *
	 * Creates a WJGui for the datacollector currently loaded in this application
	 *
	 * @since Tue Jul 15 2008
	 * @access public
	 * @return WJGui
	 **/
	buildGui: function() {
		return new WJGui(this); // easy to extend to e.g. WJEditorGui
	},

	/**
	 * fillDatacollector
	 *
	 * Updates the info in the datacollector from the gui
	 *
	 * @since Mon Aug 04 2008
	 * @access public
	 * @return void
	 **/
	fillDatacollector: function() {
		WJDebugger.log(WJDebugger.INFO, "Fill datacollector", this, this.gui, this.datacollector);
		this.gui.fillDatacollector(this.datacollector);
		if (!this.datacollector.valid() ) {
		}
	},

	/**
	 * closed
	 *
	 * Returns true if this application is closed
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return boolean
	 **/
	closed: function() {
		return (this.state == "closed");
	},

	/**
	 * wakeUp
	 *
	 * Gives the application the chance to do whatever it is they want to do
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	wakeUp: function() {
		WJDebugger.log(WJDebugger.DEBUG, "Application wakeup", this, this.state);
		switch (this.state) {
			case "starting":
				this.startApplication();
				break;
			case "running":
				this.pingApplication();
				break;
			case "waiting": 
				this.testTimeout();
				break;
			case "busy":
				this.handleBusy();
				break;
			case "crashed":
				this.reportCrash();
				break;
			case "closing":
				WJDebugger.log(WJDebugger.DEBUG, "Go cleanup application", this);
				this.cleanUp();
				break;
			case "closed":
				break;
		}
	},

	/**
	 * setPid
	 *
	 * Sets a PID for this application
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @param integer pid
	 * @return void
	 **/
	setPid: function(pid) {
		this.pid = pid;
	},
	
	/**
	 * getPid
	 *
	 * Gets the PID for this application
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return integer
	 **/
	getPid: function() {
		return this.pid;
	},

	/**
	 * createMainWindow
	 *
	 * Creates the main WJWindow for this application
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return WJWindow
	 **/
	createMainWindow: function() {
		var window = new WJWindow(aeroplane.windowmanager.applicationResponse.bind(aeroplane.windowmanager, this) );
		window.getWindowElement().addClassName("aeroplane_window_applicationwindow").addClassName("aeroplane_window_applicationwindow_" + this.appname.toLowerCase() );
		window.setTitle(this.getWindowTitle() );
		aeroplane.windowmanager.maximize(window);
		window.hide();
		return window;
	},

	/**
	 * getWindowTitle
	 *
	 * Tells what title to put on the window
	 *
	 * @since Mon Sep 22 2008
	 * @access public
	 * @return string
	 **/
	getWindowTitle: function() {
		return "";
	},

	/**
	 * closeMainWindow
	 *
	 * Callback for closing the main window (ie. sets the state to closing)
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	close: function() {
		WJDebugger.log(WJDebugger.NOTICE, "Requesting application close", this);
		// allow a last processing of normal operations
		this.wakeUp();
		// make sure the app will close down on next wakeup
		this.state = "closing";
	},

	/**
	 * startApplication
	 *
	 * Starts this application, should end up with a state different from starting
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	startApplication: function() {
		if (this.pid == null) {
			throw "Trying to start an application without a pid";
		}
		this.currentComponent = aeroplane.windowmanager.getMainWindow(this).getContentElement("main");
		this.updateContent();
	},

	/**
	 * createWindow
	 *
	 * Creates a helper window for type
	 *
	 * @since Mon Jul 28 2008
	 * @access public
	 * @param string type
	 * @return WJWindow
	 **/
	createWindow: function(type) {
		var wjwindow = new WJWindow();
		wjwindow.setWidth(aeroplane.windowmanager.getMainWindowWidth() );
		wjwindow.setHeight(aeroplane.windowmanager.getMainWindowHeight() );
		wjwindow.setX(aeroplane.windowmanager.getMainWindowLeft() );
		wjwindow.setY(aeroplane.windowmanager.getMainWindowTop() );
		return wjwindow;
	},
	
	/**
	 * updateContent
	 *
	 * Sends a request to the server to update the content of the editor application
	 * 
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	updateContent: function() {
		this.url.addParameter("state", this.state);
		this.url.addParameter("type", "json");
		this.url.addParameter("mode", "datacollector");
		this.spin.content(this.url, [this.handleDatacollector.bind(this)]);
		this.url.deleteParameter("type");
		this.url.deleteParameter("mode");
		this.state = "waiting";
	},
	
	/**
	 * contentUpdated
	 *
	 * Callback to notify the editor interface that the content has been updated
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	contentUpdated: function() {
		WJDebugger.log(WJDebugger.DEBUG, "Content updated", this);
		this.state = "running";
		var loaderWnd = aeroplane.windowmanager.getWindow(this, "Loader");
		loaderWnd.destroy.bind(loaderWnd).delay(2.1); // should be ready in 2 sec so 2.1 is 0.1 sec. safe
		aeroplane.windowmanager.getMainWindow(this).evalContentElement("main");
	},

	/**
	 * pingApplication
	 *
	 * Gives this application the chance to do /something/, should end up with a state different from starting
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	pingApplication: function() {
	},

	/**
	 * testTimeout
	 *
	 * The application is currently waiting for something, this function gives the possibility to detect a timeout and deal with it
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	testTimeout: function() {

	},

	/**
	 * handleBusy
	 *
	 * The application is currently not keeping up, this function can be used to close non essential stuff and get the application out of the busy state
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	handleBusy: function() {

	},

	/**
	 * reportCrash
	 *
	 * The application has actually told us it has crashed (:D), this function deals with the crash (ie. report it per mail to the windmill maintainer)
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	reportCrash: function() {

	},

	/**
	 * cleanUp
	 *
	 * The application wants to stop running, clean up anything open and move state to closed
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @return void
	 **/
	cleanUp: function() {
		WJDebugger.log(WJDebugger.NOTICE, "Cleaning up application", this);
		aeroplane.windowmanager.destroyMainWindow(this);
		aeroplane.loadingCursor(false);
		this.state = "closed";
	},

	/**
	 * handleWindowEvent
	 *
	 * Get's called by the windowmanager if an event occured in the window that has no default call to the application
	 *
	 * @since Thu Aug 21 2008
	 * @access public
	 * @param string eventName
	 * @return void
	 **/
	handleWindowEvent: function(eventName) {
	}
});

/**
 * Base GUI class (fallback / defaults for everything not configured / overwritten)
 *
 * @since Tue Jul 15 2008
 * @author Ron Rademaker
 **/
var WJGui = Class.create({
	/**
	 * __construct
	 *
	 * Creates a new WJGui
	 *
	 * @since Tue Jul 15 2008
	 * @access public
	 * @return WJGui
	 **/
	initialize: function(application) {
		WJDebugger.log(WJDebugger.INFO, "create gui for datacollector");
		this.application = application;
		this._initComponentMap();
		this.loadingCallback = false;
		WJDebugger.log(WJDebugger.INFO, "finished creating gui", this);
	},

	/**
	 * setDatacollector
	 *
	 * Create components for the datacollector
	 *
	 * @since Mon Sep 15 2008
	 * @access public
	 * @param WJDatacollector datacollector
	 * @return void
	 **/
	setDatacollector: function(datacollector) { 
		this.components = this.createComponents(datacollector);
		this.postProcesComponents();
		WJDebugger.log(WJDebugger.INFO, "finished creating components", this);
	},

	/**
	 * _initComponentMap
	 *
	 * Initializes the component map
	 *
	 * @since Fri Sep 12 2008
	 * @access protected
	 * @return void
	 **/
	_initComponentMap: function() {
		this.componentMap = { // this component map decides what components are used for datatype, overwrite this in specific application WJGui's
			WMAddress: window["WJText"],
			WMArray: window["WJCheckbox"],
			WMBoolean: window["WJRadio"],
			WMConstant: window["WJComponent"],
			WMDate: window["WJDate"],
			WMEnumeration: window["WJSelect"],
			WMEmailAddress: window["WJText"],
			WMFile: window["WJFile"],
			WMFloat: window["WJText"],
			WMImage: window["WJImage"],
			WMFlash: window["WJFlash"],
			WMFlashVideo: window["WJFlashVideo"],
			WMInteger: window["WJText"],
			WMPassword: window["WJPassword"],
			WMPostalCode: window["WJText"],
			WMString: window["WJText"],
			WMRichString: window["WJRichtext"],
			WMPhonenumber: window["WJText"],
			WMTime: window["WJTime"],
			WMCompositeDatatype: window["WJGroup"],
			WMList: window["WJList"],
			WMPointer: window["WJTab"],
			WMContentblockPointer: window["WJHtml"],
			WMTagCloud: window["WJTagCloud"]
		};
	},

	/**
	 * postProcesComponents
	 *
	 * Post processes created components, ie things like grouping
	 *
	 * @since Thu Aug 21 2008
	 * @access public
	 * @return void
	 **/
	postProcesComponents: function() {

	},

	/**
	 * groupComponents
	 *
	 * A method that creates a labelled group containing de components that have an id in fieldNames or are of type in fieldTypes
	 * The new group component will be placed on the level where the first component that is being grouped is found
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @param string groupName
	 * @param string groupLabel
	 * @param Array fieldNames
	 * @param Array components
	 * @param object group
	 * @return WJGroup
	 **/
	groupComponents: function(groupName, groupLabel, fieldNames, fieldTypes, components, group, region) {
		WJDebugger.log(WJDebugger.INFO, "grouping", arguments);

		var fieldNames = fieldNames || [];
		var fieldTypes = fieldTypes || [];

		var newComponents = new Array();

		var length = components.components.length;
		for (var i = 0; i < length; i++) {
			var grouped = false;
			if (fieldNames.indexOf(components.components[i].id) != -1 || fieldTypes.indexOf(components.components[i].componentType) != -1) {
				if (group == null) {
					WJDebugger.log(WJDebugger.NOTICE, "Creating group", groupName);
					group = new WJGroup(groupName);
					var label = new WJLabel(groupName + "_label", groupLabel);
					group.setLabel(label);
					if (region) {
						group.setRegion(region);
					}
					newComponents.push(group);
				}

				WJDebugger.log(WJDebugger.NOTICE, "Adding component to group", group, components.components[i], fieldNames);
				group.addComponent(components.components[i]);
				grouped = true;
			}
			else if (components.components[i].components) {
				group = this.groupComponents(groupName, groupLabel, fieldNames, fieldTypes, components.components[i], group, region);
			}

			if (!grouped) {
				newComponents.push(components.components[i]);
			}
		}

		components.components = newComponents;
		
		return group;
	},

	/**
	 * getOrderedFields
	 *
	 * Reorders the components in datacollector in the right order, meant for overriding in specific GUI's
	 *
	 * @since Tue Jul 29 2008
	 * @access public
	 * @return void
	 **/
	getOrderedFields: function(datacollector) {
		return $H(datacollector.fields).keys();
	},

	/**
	 * createComponents
	 *
	 * Creates WJComponents for the passed datacollector
	 *
	 * @since Tue Jul 15 2008
	 * @access public
	 * @param WJDatacollector datacollector
	 * @return Array
	 **/
	createComponents: function(datacollector) {
		var comp = this.getComponent(datacollector);
		var fieldNames = this.getOrderedFields(datacollector).without("aeroplaneprofile");

		for (var i = 0; i < fieldNames.length; i++) {
			if (!Object.isArray(comp.components) ) {
				comp.components = new Array();
			}
			comp.components.push(this.createComponents(datacollector.fields[fieldNames[i] ]) );
		}
		WJDebugger.log(WJDebugger.INFO, "Completed component", datacollector, comp, comp.components);
		return comp;
	},

	/**
	 * fillDatacollector
	 *
	 * Fills the datacollector with all info available in this gui for this datacollector
	 *
	 * @since Mon Aug 04 2008
	 * @access public
	 * @param WJDatacollector datacollector
	 * @return void
	 **/
	fillDatacollector: function(datacollector) {
		if (datacollector.getName) {
			var component = this.getComponentForField(datacollector.getName() );
			if (component) {
				datacollector.setValue(component.getValue() );
			}
		}
		if (datacollector.fields) {
			for (var key in datacollector.fields) {
				this.fillDatacollector(datacollector.fields[key]);
			}
		}
	},

	/**
	 * getComponentForField
	 *
	 * Gets the WJComponent in this gui for a field with the name name
	 *
	 * @since Tue Aug 05 2008
	 * @access public
	 * @param string name
	 * @param object component
	 * @return WJComponent
	 **/
	getComponentForField: function(name, component) {
		var component = component || this.components;

		for (var cmp in component) {
			if (cmp == "id") {
				if (component[cmp] == name) {
					return component;
				}
				else if (component[cmp] == name.substr(0, (name.length - ("_" + this.application.pid).length) ) ) {
					return component;
				}
			}

			if (cmp == "component") {
				WJDebugger.log(WJDebugger.INFO, "Recurse into component", name, component, cmp)
				if (component[cmp]) {
					var comp = this.getComponentForField(name, component[cmp]);
					if (comp) {
						return comp;
					}
				}
			}

			if (cmp == "components") {
				for (var i = 0; i < $A(component[cmp]).length; i++) {
					WJDebugger.log(WJDebugger.INFO, "Recurse into components", name, component, cmp, component[cmp][i]);
					if (component[cmp][i]) {
						var comp = this.getComponentForField(name, component[cmp][i]);
						if (comp) {
							return comp;
						}
					}
				}
			}
		}
	},

	/**
	 * getComponentType
	 *
	 * Creates the component for datacollector in this GUI
	 *
	 * @since Tue Jul 29 2008
	 * @access public
	 * @param WJDatacollector datacollector
	 * @return klass
	 **/
	createComponent: function(datacollector) {
		if (this.componentMap[datacollector.getDatatype()]) {
			return new this.componentMap[datacollector.getDatatype()](datacollector.getName() );
		}
		else {
			WJDebugger.log(WJDebugger.WARNING, "No component defined for " + datacollector.getDatatype() + ", falling back on WJComponent");
			return new WJComponent(datacollector.getName() );
		}
	},

	/**
	 * getComponent
	 *
	 * Gets the component for datacollector
	 *
	 * @since Wed Jul 16 2008
	 * @access public
	 * @param WJDatacollector datacollector
	 * @return object
	 **/
	getComponent: function(datacollector) {
		var component = {};
		if (datacollector.getDatatype) {
			var component = this.createComponent(datacollector);
			component.initComponent(datacollector, this);
			var label = new WJLabel(datacollector.getName() + "_label");
			label.text = datacollector.getUfName();
			component.setLabel(label);
		}
		return component;
	},

	/**
	 * show
	 *
	 * Loads this GUI into wnd
	 *
	 * @since Tue Jul 15 2008
	 * @access public
	 * @param WJWindow wjwindow
	 * @param DomNode elem
	 * @param WJSpin spin
	 * @param function finished
	 * @param WJUrl url - optional
	 * @return void
	 **/
	show: function(wjwindow, elem, spin, finished, url) {
		if (!url) {
			var url = new WJUrl({}, "/index.php");
			url.setCt(this.getCt() );
			url.setDt(this.getDt() );
			url.addParameter("mode", "gui");
			url.addParameter("uft[]", this.getUfts() );
			url.addParameter("wmtrigger[]", ["requestufts"]);
			url.addParameter("components", $H(this.components).toJSON() );
			url.addParameter("pid", this.application.getPid() );

			WJDebugger.log(WJDebugger.DEBUG, "GUI Component JSON", $H(this.components).toJSON() );
		}
		
		wjwindow.show();
		
		spin.content(url, [elem, this.coupleComponents.bind(this, this.components), finished, aeroplane.windowmanager.cleanUpDijitWidgets.bind(aeroplane.windowmanager), this.parseGUI.bind(this, elem, wjwindow) ]);
	},
	
	/**
	 * preCleanUp
	 *
	 * Cleans up the gui before spinning for content
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @param WJWindow wjwindow
	 * @return void
	 **/
	preCleanUp: function(wjwindow) {
		var tinyMCELoaded = (typeof(tinyMCE) != "undefined") ? true : false;
		if (tinyMCELoaded) {
			$H(tinyMCE.editors).keys().each(function(id) {
				if (!$(id) || $(id).descendantOf(this.getWindowElement() ) ) {
					WJDebugger.log(WJDebugger.NOTICE, "Removing tinyMCE editor: ", id);
					tinymce.EditorManager.execCommand("mceRemoveControl", false, id)
				}
			}.bind(wjwindow) );
		}
	},

	/**
	 * parseGUI
	 *
	 * Parses the GUI
	 *
	 * @since Wed Sep 3 2008
	 * @access public
	 * @param Element context
	 * @return void
	 **/
	parseGUI: function(context, wjwindow) {
		WJDebugger.log(WJDebugger.DEBUG, "Parse GUI", context, wjwindow);
		if (dojo && dojo.parser) {
			WJDebugger.log(WJDebugger.DEBUG, "Ready to parse GUI");
			this._parseGUI(context, wjwindow);
		}
		else {
			arguments.callee.bind(this, context, wjwindow).delay(0.5); // try it later, because the content is not ready yet
		}
	},

	/**
	 * _parseGui
	 *
	 * Parses and handles the GUI
	 *
	 * @since Fri Sep 12 2008
	 * @access protected
	 * @param Element context
	 * @return array
	 **/
	_parseGUI: function(context, wjwindow) {
		WJDebugger.log(WJDebugger.INFO, "Dojo parsing new Gui", context, wjwindow);
		aeroplane.windowmanager.cleanUpDijitWidgets();
		try {
			var widgets = dojo.parser.parse(context);
		}
		catch (e) {
			WJDebugger.log(WJDebugger.ERROR, "Dojo parsing new Gui failed", e);
			WJWindow.alert("De grafische gebruikersinterface kon niet correct worden opgebouwd."+ "\n" + "Neem contact op met Connectholland."+ "\n" + "De applicatie zal worden gesloten.", this.application.close.bind(this.application) );
		}
		this._handleGUILoad(context, wjwindow);
		return widgets;
	},

	/**
	 * handleGUILoad
	 *
	 * Does actions after the gui is loaded and parsed
	 *
	 * @since Thu Sep 4 2008
	 * @access protected
	 * @param Element context
	 * @param WJWindow wjwindow
	 * @return void
	 **/
	_handleGUILoad: function(context, wjwindow) {
		this.focusElement(context); 
		aeroplane.loadingCursor(false, wjwindow, this.loadingCallback);
	},
	
	/**
	 * focusElement
	 *
	 * Gives the possibility to focus a preferred element (as default tries to find a first element)
	 *
	 * @since Wed Sep 3 2008
	 * @access public
	 * @param context
	 * @return void
	 **/
	focusElement: function(context) {
		if (!context) {
			return;
		}
		var element = context.select("input").first();
		if (Object.isElement(element) ) {
			try {
				element.focus();
			}
			catch (err) {
				// ie gets here... don't crash
			}
		}
	},
	
	/**
	 * getCt
	 *
	 * Returns the ct for gui-building - makes it possible to use another ct in a subclass
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return string
	 **/
	getCt: function() {
		return "wmgui";
	},

	/**
	 * getDt
	 *
	 * Returns the dt for gui-building - makes it possible to use another dt in a subclass
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return string
	 **/
	getDt: function() {
		return "gui";
	},

	/**
	 * getUfts
	 *
	 * Returns the ufts for gui-building - makes it possible to use other uft's in a subclass
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return Array
	 **/
	getUfts: function() {
		return ["gui"];
	},

	/**
	 * addButtons
	 *
	 * Gives the possibility to add buttons to the window
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @param WJWindow window
	 * @return void
	 **/
	addButtons: function(window) {
		// here for extending purposes only
	},

	/**
	 * getComponentId
	 *
	 * Gets the html id of a component with id id (actual id depends on pid to avoid double ids)
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param string id
	 * @return string
	 **/
	getComponentId: function(id) {
		if ($(id + "_" + this.application.pid) ) {
			return id + "_" + this.application.pid;
		}
		else {
			return id;
		}
	},

	/**
	 * coupleComponents
	 *
	 * Couples the javascript components with HTML form elements (the visual representations of the components)
	 * By now there should be input elements for all components
	 *
	 * @since Mon Jul 21 2008
	 * @access public
	 * @param object component
	 * @return void
	 **/
	coupleComponents: function(component) {
		WJDebugger.log(WJDebugger.INFO, "coupling", component);
		for (var cmp in component) {
			if (cmp == "id") {
				if ($(this.getComponentId(component[cmp]) ) ) {
					component.setHtmlelement($(this.getComponentId(component[cmp]) ) );
				}
				else {
					var possibles = document.getElementsByName(component[cmp]);
					$A(possibles).each(component.setHtmlelement.bind(component) );
				}
			}

			if (cmp == "component") {
				this.coupleComponents(component[cmp]);
			}

			if (cmp == "components") {
				if (component[cmp].each) {
					$A(component[cmp]).each(this.coupleComponents.bind(this) );
				}
			}
		}
	}
});

/**
 * WJLoginInterface is the interface to the user module for the login process.
 *
 * @since Tue Aug 12 2008
 * @author Giso Stallenberg
 **/
WJLoginInterface = Class.create(WJApplicationInterface, {
	/**
	 * initialize
	 *
	 * Initializes a WJLoginInterface
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @param string appname
	 * @return WJLoginInterface
	 **/
   initialize: function($super, appname, module) {
		$super(appname);
		this.module = module;
		this.url.addParameter("module", this.module);
		this.readonly = true;
	},

	/**
	 * buildGui
	 *
	 * Creates a WJLoginGui for the datacollector currently loaded in this editor
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @return WJLoginGui
	 **/
	buildGui: function() {
		return new WJLoginGui(this);
	},

	/**
	 * closeMainWindow
	 *
	 *
	 *
	 * @since Thu Aug 14 2008
	 * @access
	 * @param
	 * @return
	 **/
	handleWindowEvent: function(eventName) {
		if (eventName == "true") {
			this.login();
		}
		else {
			aeroplane.windowmanager.getMainWindow(this).hide();
		}
	},

	/**
	 * login
	 *
	 * Returns the datacollector to the right module with the right function (update or add)
	 *
	 * @since Mon Aug 11 2008
	 * @access public
	 * @return void
	 **/
	login: function() {
		var wjwindow = aeroplane.windowmanager.getMainWindow(this);
		aeroplane.loadingCursor(true);
		this.fillDatacollector();
		if (this.datacollector.valid() ) {
			this.url.addParameter("username", this.datacollector.getValue("username") );
			this.url.addParameter("password", this.datacollector.getValue("password") );
			this.spin.content(this.url, [this.loginCorrect.bind(this)], {"401": this.loginIncorrect.bind(this), "403": this.accessDenied.bind(this) });
			this.url.deleteParameter("username");
			this.datacollector.setValue("username", "");
			this.url.deleteParameter("password");
			this.datacollector.setValue("password", "");
		}
	},

	/**
	 * loginCorrect
	 *
	 * Handles the case a login was succesful
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @param XMLHTTPRequest response
	 * @return void
	 **/
	loginCorrect: function(response) {
		if (typeof(this.loginCorrectHandler) == "function") {
			this.loginCorrectHandler(response);
			aeroplane.windowmanager.destroyMainWindow(this);
		}
		else if (false) { // the module cannot give an url yet
			this._forward();
		}
		else {
			this._reload();
		}
	},

	/**
	 * setLoginCorrectHandler
	 *
	 * Sets the handler to use for a correct login
	 *
	 * @since Thu Feb 12 2009
	 * @access public
	 * @param Function callback
	 * @return void
	 **/
	setLoginCorrectHandler: function(callback) {
		this.loginCorrectHandler = callback;
	},

	/**
	 * _forward
	 *
	 * Forwards to the given url
	 *
	 * @since Thu Aug 14 2008
	 * @access protected
	 * @param string url
	 * @return void
	 **/
	_forward: function(url) {
		document.location.href = url;
	},

	/**
	 * _reload
	 *
	 * Reloads the document (but makes sure there's no useraction=logout, otherwise uses _forward to the stripped url)
	 *
	 * @since Thu Aug 14 2008
	 * @access protected
	 * @return void
	 **/
	_reload: function() {
		var loc = document.location;
		if (loc.href.indexOf("useraction=logout") != -1) {
			var url = loc.href.replace("useraction=logout&", "");
			url = url.replace("useraction=logout", "");
			if (url.endsWith("?") ) {
				url = url.replace("?", "");
			}
			this._forward(url);
		}
		else {
			document.location.reload();
		}
	},

	/**
	 * loginIncorrect
	 *
	 * Handles the case a login was incorrect
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @param XMLHTTPRequest response
	 * @return void
	 **/
	loginIncorrect: function(response) {
		aeroplane.loadingCursor(false);
		WJWindow.alert("De door u opgegeven gebruikersnaam/wachtwoord combinatie is onbekend", this.showWindow.bind(this) );
	},

	/**
	 * showWindow
	 *
	 * Shows the window
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @return void
	 **/
	showWindow: function() {
		var win = aeroplane.windowmanager.getMainWindow(this);
		win.show();
	},

	/**
	 * accessDenied
	 *
	 * Handles the case a login was not ok for aeroplane
	 *
	 * @since Wed Aug 13 2008
	 * @access public
	 * @param XMLHTTPRequest response
	 * @return void
	 **/
	accessDenied: function(response) {
		var win = aeroplane.windowmanager.getMainWindow(this);
		aeroplane.loadingCursor(false);
		WJWindow.alert("U heeft geen toegang tot dit deel van het systeem, maar bent wel succesvol aangemeld.", this._reload.bind(this) );
	}
});

/**
 * GUI class for the paragraph editor
 *
 * @since Wed Aug 13 2008
 * @author Giso Stallenberg
 **/
var WJLoginGui = Class.create(WJGui, {
	/**
	 * show
	 *
	 * Loads this GUI into wnd
	 *
	 * @since Tue Jul 15 2008
	 * @access public
	 * @param WJWindow wnd
	 * @param WJSpin spin
	 * @param function finished
	 * @return void
	 **/
	show: function($super, wjwindow, spin, finished) {
		$super(wjwindow, spin, finished);
		wjwindow.getWindowElement().addClassName("aeroplane_reset");
		wjwindow.insertWindowRowBefore("main", "intro");
		wjwindow.setWidth(400);
		wjwindow.setHeight(528);
		wjwindow.setZ(1000);
		wjwindow.center();
		wjwindow.keepCentered();
	},

	/**
	 * addButtons
	 *
	 * Adds buttons to the window
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @param WJWindow window
	 * @return void
	 **/
	addButtons: function(wjwindow) {
		wjwindow.addButton("Aanmelden", "true", true);
	},

	/**
	 * postProcesComponents
	 *
	 * Adds upload URL information to the uploadbutton
	 *
	 * @since Thu Feb 05 2009
	 * @access public
	 * @return void
	 **/
	postProcesComponents: function($super) {
		$super();
		this.groupComponents("optimizedtext", "", ["text"], null, this.components, null, "top");
		this.groupComponents("loginfields", "Inloggen", ["username", "password"], null, this.components);
	},

	/**
	 * _parseGui
	 *
	 * Parses and handles the GUI
	 *
	 * @since Wed Feb 18 2009
	 * @access protected
	 * @param Element context
	 * @return array
	 **/
	_parseGUI: function($super, context, wjwindow) {
		var widgets = $super(context, wjwindow);
		wjwindow.getContentElement("intro").appendChild(wjwindow.getContentElement("main").down(".aeroplane_wjhtml") );
		return widgets;
	}
});

/**
 * WJDatacollector is the class that represents a datacollector. Basically, the WJDatacollectorFactory accepts any object and extends that object with method and then returns the extended object. Usage boils down to: get the json representation of a WMDatacollector, let WJSpin interpret the json and pass the object through WJDatacollectorFactory's import method
 *
 * @since Tue Jun 24 2008
 * @author Ron Rademaker
 **/
var WJDatacollectorFactory = Class.create({
	/**
	 * initialize
	 *
	 * Prototype demands an implementation
	 *
	 * @since Tue Jun 24 2008
	 * @access public
	 * @return WJDatacollectorFactory
	 **/
	initialize: function() {

	},

	/**
	 * importDatacollector
	 *
	 * Imports datacollector as a WJDatacollector; ie. adds the WJDatacollector methods to datacollector
	 *
	 * @since Tue Jun 24 2008
	 * @access public
	 * @param object datacollector
	 * @return WJDatacollector
	 **/
	importDatacollector: function(datacollector) {
		datacollector.getValue = function(key) {
			return this.fields[key].getValue();
		};

		datacollector.setValue = function(key, value) {
			this.fields[key].setValue(value);
		};

		datacollector.valid = function() {
			for (field in this.fields) {
				if (!this.fields[field].valid() ) {
					return false;
				}
			}

			return true;
		};
		
		this.rootDatacollector = datacollector;

		var fields = {};
		if (datacollector.fields) {
			for (var i = 0; i < datacollector.fields.length; i++) {
				fields[datacollector.fields[i].name] = this.importField(datacollector.fields[i]);
			}
		}
		datacollector.fields = fields;
	
		return datacollector;
	},

	/**
	 * importField
	 *
	 * Imports a datacollector field
	 *
	 * @since Tue Jun 24 2008
	 * @access public
	 * @param object datacollector
	 * @return WJDatacollectorField
	 **/
	importField: function(datacollector) {
		datacollector.valid = function() {
			if (!this.value.validate() ) {
				return false;
			}
			
			for (field in this.fields) {
				if (!this.fields[field].valid() ) {
					return false;
				}
			}

			return true;
		};

		datacollector.setValue = function(value) {
			this.value.setValue(value);
		};
		
		datacollector.getValue = function() {
			return this.value.getValue();
		};

		datacollector.getName = function() {
			return this.name;
		};

		datacollector.getUfName = function() {
			return this.ufname;
		};

		datacollector.getDatatype = function() {
			return this.value.datatype;
		};
		
		var fields = {};
		if (datacollector.fields) {
			for (var i = 0; i < datacollector.fields.length; i++) {
				fields[datacollector.fields[i].name] = this.importField(datacollector.fields[i]);
			}
		}
		datacollector.fields = fields;

		var value = {};
		value.value = datacollector.value;
		value = this.importDatatype(value, datacollector.datatype, datacollector.validateTree);
		datacollector.value = value;

		return datacollector;
	},

	/**
	 * importDatatype
	 *
	 * Transforms value into a datatype (adds the methods)
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @param object value
	 * @param string datatype
	 * @param object validateTree
	 * @return object
	 **/
	importDatatype: function(value, datatype, validateTree) {
		value.datatype = datatype;

		value.getValue = function() {
			return this.value;
		};

		value.setValue = function(value) {
			this.value = value;
		};

		value.validateTree = this.importValidateTree(validateTree, value);

		value.validate = function() {
			return this.validateTree.valid();
		};

		// Improve these few lines, it's not generic enough
		if (value.datatype == "WMCompositeDatatype") {
			value.composite = new Array();
			for (var key in value.value) {
				var compdatacol = {name: key, ufname: key, value: value.value[key].value, datatype: value.value[key].datatype};
				value.composite.push(this.importField(compdatacol) );
			}
		}

		return value;
	},

	/**
	 * importValidateTree
	 *
	 * Transforms validateTree to a WJValidationTree
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @param object validateTree
	 * @param object value
	 * @return object
	 **/
	importValidateTree: function(validateTree, value) {
		if (validateTree) {
			var factory = new WJValidationTreeFactory();
			return factory.importTree(validateTree, value, this.rootDatacollector);
		}
		else {
			return new WJValidationTree(new WJTrueTestClause(), value.getValue.bind(value) );
		}
	}
});

/**
 * WJUnloadManager makes sure no data get lost when an unload takes place
 *
 * @since Tue Feb 17 2009
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Aeroplane.Javascript
 **/
var WJUnloadManager = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJUnloadManager
	 *
	 * @since Tue Feb 17 2009
	 * @access public
	 * @return WJUnloadManager
	 **/
	initialize: function(checker) {
		this._checker = checker || function() {return false;};
		this.suspend(false);
		this._asking = false;
		Event.observe(window, "keydown", this.handleKeyUnload.bindAsEventListener(this) );
		Event.observe(window, "beforeunload", this.handleUnload.bindAsEventListener(this) );
	},
	
	/**
	 * handleUnload
	 *
	 * Handles the case where no keys were detected, but the page is about to unload
	 *
	 * @since Tue Feb 17 2009
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	handleUnload: function(event) {
		if (!this._checker() && !this.isSuspended() ) {
			event.returnValue = this._getMessage(true);
		}
	},

	/**
	 * handleKeyUnload
	 *
	 * Handles the case where a user might unload the page because of a key (combination)
	 *
	 * @since Tue Feb 17 2009
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	handleKeyUnload: function(event) {
		var modKs = (event.altKey ? 1 : 0) + (event.shiftKey ? 2 : 0) + (event.ctrlKey ? 4 : 0);
		var key = event.keyCode;
		if (!this._checker() && !this.isSuspended() ) {
			if ((modKs==4 || modKs==6) && key == 82) { // CTRL+R
				this._showMessage(this.doReload.bind(this) );
			}
			if (modKs==1 && (key == Event.KEY_LEFT || key == Event.KEY_RIGHT) ) { // ALT + (LEFT|RIGHT)
				this._showMessage(this.doHistory.bind(this, ( (key == Event.KEY_LEFT) ? -1 : +1) ) );
			}
			if ((modKs==0 || modKs==4) && key == 116) { // (CTRL+)F5
				this._showMessage(this.doReload.bind(this) );
			}
			/**
			 * Other options might be
			 * - CTRL+F4
			 * - ALT+F4
			 * - CTRL+W
			 * - CTRL+SHIFT+W
			 *
			 * But it needs to be possible to reproduce them through script. For now they're handled by handleUnload
			 **/
		}
		if (this._asking) {
			return this._stop(event);
		}
	},

	/**
	 * _stop
	 *
	 * Stops the event
	 *
	 * @since Tue Feb 17 2009
	 * @access protected
	 * @param Event event
	 * @return boolean
	 **/
	_stop: function(event) {
		if (event.stopPropagation) {
			event.stopPropagation();
		}
		event.stop();
		event.returnValue = false;
		return false;
	},
	
	/**
	 * doReload
	 *
	 * Performs the actual reload (as requested by the user)
	 *
	 * @since Tue Feb 17 2009
	 * @access public
	 * @param WJWindow wjwindow
	 * @param Event event
	 * @param boolean doReload
	 * @return void
	 **/
	doReload: function(wjwindow, event, doReload) {
		if (doReload) {
			this.suspend();
			document.location.reload();
		}
		else {
			this._asking = false;
		}
	},

	/**
	 * doHistory
	 *
	 * Performs the actual history change (as requested by the user)
	 *
	 * @since Tue Feb 17 2009
	 * @access public
	 * @param WJWindow wjwindow
	 * @param Event event
	 * @param integer direction
	 * @param boolean doHistory
	 * @return void
	 **/
	doHistory: function(direction, wjwindow, event, doHistory) {
		if (doHistory) {
			this.suspend();
			window.history.go(direction);
		}
		else {
			this._asking = false;
		}
	},

	/**
	 * suspend
	 *
	 * Makes this class skip the observers
	 *
	 * @since Mon Feb 23 2009
	 * @access public
	 * @param boolean doSuspend
	 * @return void
	 **/
	suspend: function(doSuspend) {
		var doSuspend = (doSuspend != false);
		this._suspended = doSuspend;
	},

	/**
	 * isSuspended
	 *
	 * Tells if this class is suspended
	 *
	 * @since Mon Feb 23 2009
	 * @access public
	 * @return boolean
	 **/
	isSuspended: function() {
		return this._suspended;
	},

	/**
	 * _showMessage
	 *
	 * Shows the message
	 *
	 * @since Tue Feb 17 2009
	 * @access protected
	 * @param Function callback
	 * @return void
	 **/
	_showMessage: function(callback) {
		if (!this._asking) {
			WJWindow.booleanConfirm(this._getMessage(false, true), callback);
			this._asking = true;
		}
	},

	/**
	 * getMessage
	 *
	 * Returns the message to show to the user
	 *
	 * @since Tue Feb 17 2009
	 * @access protected
	 * @param boolean useBrowserLang
	 * @return string
	 **/
	_getMessage: function(useBrowserLang, full) {
		var useBrowserLang = (useBrowserLang === true);
		var full = (full === true);
		
		if (useBrowserLang) {
			var lang = this._getBrowserLanguage();
			if (full) {
				return this._messages.body[lang] + "\n\n" + this._messages.question[lang];
			}
			else {
				return this._messages.body[lang];
			}
		}
		else {
			if (full) { // for now we use "might be" because this is not verified in any way
				return "Mogelijk zijn er nog niet opgeslagen gegevens."+ "\n\n" + "Weet je zeker dat u deze pagina wilt verlaten?";
			}
			else { // for now we use "might be" because this is not verified in any way
				return "Mogelijk zijn er nog niet opgeslagen gegevens.";
			}
			return ;
		}
	},

	/**
	 * _getBrowserLanguage
	 *
	 * Returns the language used in this browser
	 *
	 * @since Tue Feb 17 2009
	 * @access protected
	 * @return string
	 **/
	_getBrowserLanguage: function() {
		var lang = window.navigator["language"] || window.navigator["browserLanguage"];
		switch (lang) {
			case "nl":
			case "nl-NL":
			case "nl_NL":
			case "nl-be":
			case "nl-BE":
			case "nl_BE":
				return "nl_NL";
			default:
				return "default";
		}
	},

	/**
	 * The various messages
	 *
	 * @since Tue Feb 17 2009
	 * @access protected
	 * @var Object
	 **/
	_messages: {
		body: { // for now we use "might be" because this is not verified in any way
			nl_NL: "Mogelijk zijn er nog niet opgeslagen gegevens.",
			"default": "There might be unsaved data."
		},

		question: {
			nl_NL: "Weet u zeker dat u deze pagina wilt verlaten?",
			"default": "Are you sure you want to navigate away?"
		}
	}
});
/**
 * WJComponent
 *
 * Base Component class
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJComponent = Class.create({
	/**
	 * Properties of WJComponent
	 *
	 * string id
	 * string value
	 * boolean disabled
	 * boolean readonly
	 * event focusEvent
	 * event blurEvent
	 * event changeEvent
	 **/

	/**
	 * initialize
	 *
	 * Creates a new WJComponent
	 *
	 * @since 
	 * @access public
	 * @param string id
	 * @return 
	 **/
	initialize: function(id) {
		this.id = id;
		this.value = null;
		this.disabled = false;
		this.readonly = false;
		this.focusEvent = "wj:focus";
		this.blurEvent = "wj:blur";
		this.changeEvent = "wj:change";
		this.componentType = "WJComponent";
		this.htmlelement = null;
		this.label = null;
		this.group = false;
	},

	/**
	 * setHtmlelement
	 *
	 * Sets the html element for this component
	 *
	 * @since Mon Jul 21 2008
	 * @access public
	 * @param htmlelement element
	 * @return void
	 **/
	setHtmlelement: function(element) {
		this.htmlelement = element;
	},

	/**
	 * setLabel
	 *
	 * Sets this component's label
	 *
	 * @since Wed Nov 19 2008
	 * @access public
	 * @param WJLabel label
	 * @return void
	 **/
	setLabel: function(label) {
		this.label = label;
	},

	/**
	 * fireEvent
	 *
	 * Fires the event with the original event wrapped in it
	 *
	 * @since Mon Jul 21 2008
	 * @access public
	 * @param string eventName
	 * @param event e
	 * @return void
	 **/
	fireEvent: function(eventName, e) {
		this.htmlelement.fire(eventName, e);
	},

	/**
	 * initComponent
	 *
	 * Inits this component from a datacollector
	 *
	 * @since Wed Jul 16 2008
	 * @access public
	 * @param WJDatacollector datacollector
	 * @return void
	 **/
	initComponent: function(datacollector) { 
		this.value = datacollector.getValue();
	},

	/**
	 * getValue
	 *
	 * Gets the value of this component
	 *
	 * @since 
	 * @access public
	 * @return 
	 **/
	getValue: function() {
		var usesDijit = (typeof(dijit) != "undefined") ? true : false;
		
		if (this.htmlelement && usesDijit && dijit.byId(this.htmlelement.id) && dijit.byId(this.htmlelement.id).getValue) {
			this.value = dijit.byId(this.htmlelement.id).attr("value"); // IE doesn't support getValue, FF doesn't (fully) support .value
		}
		else if (this.htmlelement && this.htmlelement.value) {
			this.value = this.htmlelement.value;
		}
		return this.value;
	},

	/**
	 * setValue
	 *
	 * Sets the value of this component
	 *
	 * @since Tue Nov 04 2008
	 * @access public
	 * @return void
	 **/
	setValue: function(value) {
		var usesDijit = (typeof(dijit) != "undefined") ? true : false;
		this.value = value;
		if (this.htmlelement && usesDijit && dijit.byId(this.htmlelement.id) && dijit.byId(this.htmlelement.id).setValue) {
			dijit.byId(this.htmlelement.id).setValue(this.value);
		}
		else if (this.htmlelement && this.htmlelement.value) {
			this.htmlelement.value = this.value;
		}
	},

	/**
	 * getId
	 *
	 * Gets this component's id
	 *
	 * @since 
	 * @access public
	 * @return 
	 **/
	getId: function() {
		return this.id;
	}
});

/**
 * WJText
 *
 * Textbox input component
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJText = Class.create(WJComponent, {
	/**
	 * Properties of WJText
	 *
	 * integer maxchars
	 * integer maxlines
	 **/

	/**
	 * initialize
	 *
	 * Initialize this WJText
	 *
	 * @since Mon Jul 28 2008
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJText";
	}
});

/**
 * WJPassword
 *
 * Textbox for password input
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJPassword = Class.create(WJText, {
	/**
	 * initialize
	 *
	 * Initialize this WJPassword
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJPassword";
	}
});

/**
 * WJLabel
 *
 * A label, the text that is usually found near an input component.
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
WJLabel = Class.create(WJComponent, {
	/**
	 * Properties of WJLabel
	 *
	 * string text
	 * string image
	 * WJComponent component
	 **/

	/**
	 * initialize
	 *
	 * Creates a new Label
	 *
	 * @since 
	 * @access public
	 * @param string id
	 * @param string text (default: null)
	 * @param string image (default: null)
	 * @return 
	 **/
	initialize: function($super, id, text, image) {
		$super(id);
		this.componentType = "WJLabel",
		this.text = text;
		this.image = image;
	}
});

/**
 * WJGroup
 *
 * A group of components
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
WJGroup = Class.create(WJComponent, {
	/**
	 * Properties of WJGroup
	 *
	 * Array components
	 **/

	/**
	 * initialize
	 *
	 * Initializes a new WJGroup
	 *
	 * @since Tue Aug 19 2008
	 * @access public
	 * @param string id
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJGroup";
		this.components = new Array();
		this.region = "center";
		this.hasGroups = false;
		this.group = true;
	},

	/**
	 * setRegion
	 *
	 * Sets the region where to show this group
	 *
	 * @since Wed Nov 12 2008
	 * @access public
	 * @param string region
	 * @return void
	 **/
	setRegion: function(region) {
		this.region = region;
	},

	/**
	 * addComponent
	 *
	 * Adds a component to this component group
	 *
	 * @since Tue Aug 19 2008
	 * @access public
	 * @param WJComponent comp
	 * @return void
	 **/
	addComponent: function(comp) {
		WJDebugger.log(WJDebugger.DEBUG, "Adding component to WJGroup " + this.id, comp);
		this.components.push(comp);
	},

	/**
	 * getComponents
	 *
	 * Gets the components in this group
	 *
	 * @since Tue Aug 19 2008
	 * @access public
	 * @return Array
	 **/
	getComponents: function() {
		return this.components;
	},

	/**
	 * initComponent
	 *
	 * Loads all components in this group into this component group
	 *
	 * @since Tue Aug 19 2008
	 * @access public
	 * @param WJDatacollector datacollector
	 * @param WJGui gui
	 * @return void
	 **/
	initComponent: function($super, datacollector, gui) {
		$super(datacollector, gui);

		WJDebugger.log(WJDebugger.DEBUG, "Initing a " + this.id + " WJGroup", datacollector, gui);

		if (datacollector.value.composite) {
			for (var i = 0; i < datacollector.value.composite.length; i++) {
				if (typeof(datacollector.value.composite[i]) == "object") {
					this.addComponent(gui.createComponents(datacollector.value.composite[i]) );
				}
			}
		}
	}
});

/**
 * WJHtml
 *
 * A html component, used to be able to use contentblocks with components without contentpanes
 *
 * @since Mon Jan 19 2009
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJHtml = Class.create(WJGroup, {
	/**
	 * initialize
	 *
	 * Initialize this WJHtml
	 *
	 * @since Mon Jan 19 2009
	 * @access public
	 * @return WJHtml
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJHtml";
		this.group = 0; // WJHtml has a lot of group properties but it isn't really a group (like WJLink). This makes sure it isn't treated as a group on the XSL level.
	}
});

var WJValidationTree = Class.create();
/**
 * WJValidationTree is a tree with WJTestClauses, it can be used to validate datatypes and form fields (incl. relations to other form fields). WJValidationTree can be used to completely validate any form (though that will require some creative tree building)
 *
 * Class translated from PHP to Javascript
 *
 * @since Wed Jul 02 2008
 * @author Ron Rademaker
 **/
WJValidationTree.prototype =  {
	/**
	 * initialize
	 *
	 * Creates a new WJValidationTree
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @param WJTestClause test
	 * @param array inputCallback
	 * @param array additionalCallback
	 * @return WMValidationTree
	 **/
	initialize: function(test, inputCallback, additionalCallback) {
		this.test = test;
		this.inputCallback = inputCallback;
		if (additionalCallback != null) {
			this.additionalCallback = additionalCallback;
		}
		this.trueTree = null;
		this.falseTree = null;
		this.className = "WJValidationTree";
	},

	/**
	 * setTrueTree
	 *
	 * Sets the tree to enter on true
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @param WJValidationTree tree
	 * @return WJValidationTree
	 **/
	setTrueTree: function(tree) {
		this.trueTree = tree;
		return this.trueTree;
	},

	/**
	 * setFalseTree
	 *
	 * Sets the tree to enter on false
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @param WJValidationTree tree
	 * @return WJValidationTree
	 **/
	setFalseTree: function(tree) {
		this.falseTree = tree;
		return this.falseTree;
	},

	/**
	 * valid
	 *
	 * Tests this tree, returns true if the tree is valid, false if not
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @return boolean
	 **/
	valid: function() {
		var additional = null;
		if (typeof(this.additionalCallback) == "function") {
			additional = this.additionalCallback();
		}
		if (typeof(this.inputCallback) != "function") {
			throw "Callback for input must be a function, it is a " + typeof(this.inputCallback);
		}
		var input = this.inputCallback();

		var result = this.test.test(input, additional);

		if (result) {
			if (this.trueTree) {
				return this.trueTree.valid();
			}
			else {
				return true;
			}
		}
		else {
			if (this.falseTree) {
				return this.falseTree.valid();
			}
			else {
				return false;
			}
		}
	}
}

var WJValidationTreeFactory = Class.create();
/**
 * A factory to create a validation tree from json
 *
 * @author Ron Rademaker
 * @since Mon Jul 14 2008
 **/

WJValidationTreeFactory.prototype = {
	initialize: function() { }, // prototype requires the function to exist 

	/**
	 * importTree
	 *
	 * Function to be used static to transform a json object into a WJValidationTree
	 *
	 * @since Thu Jul 03 2008
	 * @access public
	 * @param object tree
	 * @param object value
	 * @param object datacollector
	 * @return WJValidationTree
	 **/
	importTree: function(tree, value, datacollector) {
		var factory = new WJTestClauseFactory();
		var validateTree = new WJValidationTree(factory.importTest(tree.test), this.importCallback(tree.input, datacollector, value), this.importCallback(tree.additional, datacollector, value) );
		if (tree.trueTree) {
			validateTree.setTrueTree(this.importTree(tree.trueTree, value, datacollector) );
		}
		if (tree.falseTree) {
			validateTree.setFalseTree(this.importTree(tree.falseTree, value, datacollector) );
		}
		return validateTree;
	},

	/**
	 * importCallback
	 *
	 * Function to attempt to create a function object that does the same as the passed php callback
	 *
	 * @since Thu Jul 03 2008
	 * @access public
	 * @param array callback
	 * @param object datacollector
	 * @param object value
	 * @return function
	 **/
	importCallback: function(callback, datacollector, value) {
		if (typeof(callback) == "object") {
			if ( (callback.className == "WMRequest") && (callback.method == "getVariable") ) {
				return datacollector.getValue.bind(datacollector, callback.args);
			}
			if (callback.method == "getValue") { // assume class is a WMDatatype, can't check that though
				return value.getValue.bind(value);
			}
		}
	}
}

var WJTestClause = Class.create();
/** 
 * A test clause is something that test some input to some conditions and results in either true or false
 *
 * Class translated from PHP to Javascript
 *
 * @since Wed Jul 02 2008
 * @author Ron Rademaker
 **/
WJTestClause.prototype = {
	/**
	 * initialize
	 *
	 * Creates a new WJTestClause
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @return WJTestClause
	 **/
	initialize: function() {
		this.className = "WJTestClause";
	}
}

var WJTestClauseFactory = Class.create();
/**
 * Factory to create test clauses from json
 *
 * @since Mon Jul 14 2008
 * @author Ron Rademaker
 **/
WJTestClauseFactory.prototype = { 
	/**
	 * initialize
	 *
	 * Creates a new WJTestClause
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @return WJTestClause
	 **/
	initialize: function() { 
		this.className = "WJTestClauseFactory";
	},

	/**
	 * importTest
	 *
	 * Creates a WJTestClause for the info in test
	 *
	 * @since Thu Jul 03 2008
	 * @access public
	 * @param object test
	 * @return WJTestClause
	 **/
	importTest: function(test) {
		var testClass = test.test.replace("WM", "WJ");
		var resultTest = new window[testClass]();
		for (field in test) {
			if (field != "test") {
				resultTest[field] = test[field];
			}
		}
		return resultTest;

	}
}

/**
 * A test clause that always succeeds, regardless of its input
 *
 * Class translated from PHP to Javascript
 *
 * @since Tue Jul 01 2008
 * @author Ron Rademaker
 * @package Windmill.Framework
 **/
var WJTrueTestClause = Class.create(WJTestClause, {
	/**
	 * initialize
	 *
	 * Creates a new WJTrueTestClause
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @return WJTrueTestClause
	 **/ 
	initialize: function($super) {
		$super();
		this.className = "WJTrueTestClause";
	},

	/**
	 * test
	 *
	 * Returns true
	 *
	 * @since Tue Jul 01 2008
	 * @access public
	 * @param mixed input
	 * @param mixed additional
	 * @return boolean
	 **/
	test: function(input, additional) {
		return true;
	}
});


/**
 * WJScalarTestClause tests if something is a scalar
 *
 * Class translated from PHP to Javascript
 *
 * @since Wed Jul 02 2008
 * @author Ron Rademaker
 **/
var WJScalarTestClause = Class.create(WJTestClause, {
	/**
	 * initialize
	 *
	 * Creates a new WJScalarTestClause
	 *
	 * @since Tue Aug 05 2008
	 * @access public
	 * @return WJScalarTestClause
	 **/
	initialize: function() {
		this.className = "WJScalarTestClause";
	},

	/**
	 * test
	 *
	 * Returns true if input is a scalar
	 *
	 * @since Wed Jul 02 2008
	 * @access public
	 * @param mixed input
	 * @param mixed additional
	 * @return boolean
	 **/
	test: function(input, additional) {
		var datatype = typeof(input);
		return ( (datatype == "number") || (datatype == "string") || (datatype == "boolean") );
	}
});


/**
 * class WJWindow
 *
 * The base window class
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindow = Class.create({
	/**
	 * Properties of Window
	 *
	 * string _title
	 * string _type
	 * DOMElement _content
	 * mixed _contenttype
	 * Function _callbackFunction
	 * boolean _visible
	 * integer _x
	 * integer _y
	 * integer _z
	 * integer _w
	 * integer _h
	 **/
	DEFAULT_PARENT: document.body,

	/**
	 * initialize
	 *
	 * Creates a new WJWindow
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param Function callback
	 * @param DOMElement parent (default: document.body)
	 * @return WJWindow
	 **/
	initialize: function(callback, parent) {
		this._loading = false;
		this._basetitle = this._title = "";
		this._theme = "default";
		this._parent = parent || WJWindow.DEFAULT_PARENT || document.body;
		this._createWindow();
		this._listeners = new Hash();
		this._addDefaultListeners();

		this._addCloseButton();

		this.setCallback(callback);
		this.setBaseTitle(WJGuiSettings.windowBaseTitle);
	},

	/**
	 * _createWindow
	 *
	 * Creates a new window DOMElement
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return void
	 **/
	_createWindow: function() {
		var classname = this._getBaseClassname();
		this._windowElement = new Element("div", {"class": classname});
		this._windowElement.setStyle({"display": "none"});
		this._createWindowRows(["title", "main", "buttons", "bottom"], classname);
		this._windowElementId = this._windowElement.identify();
		this._parent.insert(this._windowElement);
		this._absolutizeTopLeft();
		this.hide();
		this._outerElement = this._windowElement;
		this.setTheme();
	},

	/**
	 * insertWindowRowBefore
	 *
	 * Inserts a new row before given rowname with name newrowname
	 *
	 * @since Tue Sep 23 2008
	 * @access public
	 * @param string rowname
	 * @param string newrowname
	 * @return DOMElement
	 **/
	insertWindowRowBefore: function(rowname, newrowname) {
		if (rowname === "title") {
			return;
		}
		var classname = this._getBaseClassname();
		var row = this._windowElement.select("." + classname + "_" + rowname)
		row = row.first();
		var newrowhtml = this._createRow(newrowname, classname, " " + classname + "_body");
		var div = new Element("div");
		div.update(newrowhtml);
		var toprow = row.parentNode;
		var newrow = toprow.insertBefore(div.firstChild, row );
		newrow = Element.extend(newrow);
		newrow = newrow.select("." + classname + "_content");
		this._contentElements[newrowname] = newrow.first();
		return newrow;
	},

	/**
	 * replaceWindowRow
	 *
	 * Replaces windowrow old with new, keeps references to old alive (they'll return the new rows)
	 * Returns the removed row
	 *
	 * @since Thu Feb 12 2009
	 * @access public
	 * @param string oldrow
	 * @param string newrow
	 * @return htmlelement
	 **/
	replaceWindowRow: function(oldrow, newrow) {
		var inserted = this.insertWindowRowBefore(oldrow, newrow);
		var toremove = this.getContentElement(oldrow);
		this._contentElements[oldrow] = this._contentElements[newrow];
		return toremove.remove();
	},

	/**
	 * _addCloseButton
	 *
	 * Adds a button to close the window
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_addCloseButton: function() {
		var title = this.getContentElement("title");
		//title.appendChild(new Element("div", {"class": this._getBaseClassname() + "_closebutton", "onclick": "Element.fire(this, \"wjgui:close\")", "title": "Venster sluiten"} ) );

 		title.appendChild(new Element("div", {"class": this._getBaseClassname() + "_closebutton", "onclick": "this.parentNode.getWJWindowObject().fireClose(this)", "title": "Venster sluiten"} ) );
	},

	/**
	 * fireClose
	 *
	 * Fires the close event from the given element
	 *
	 * @since Thu Oct 16 2008
	 * @access public
	 * @param Element element
	 * @return void
	 **/
	fireClose: function(element) {
		element = $(element);

		/* Observe once function */
		var func = function() {
			this.destroy();
			Event.stopObserving(document, "wjgui:close", arguments.callee.observerFunction);
		}
		var bound = func.bindAsEventListener(this);
		func.observerFunction = bound;
		/* End observe once function */

		Event.observe(document, "wjgui:close", bound);
		element.fire("wjgui:close");
		Event.stopObserving.defer(document, "wjgui:close", bound);
	},

	/**
	 * _addDefaultListeners
	 *
	 * Adds custom event listeners to the window
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_addDefaultListeners: function(element) {
		this.addListener("true", this.windowResult.bindAsEventListener(this, true) );
		this.addListener("false", this.windowResult.bindAsEventListener(this, false) );
		this.addListener("close", this.windowResult.bindAsEventListener(this, false) );
		this.addListener("save", this.windowResult.bindAsEventListener(this) );
		this.addListener("delete", this.windowResult.bindAsEventListener(this) );
		this.addListener("cancel", this.windowResult.bindAsEventListener(this) );
		this._addDefaultKeyListener();
	},

	/**
	 * _addDefaultKeyListener
	 *
	 * Adds a listener for key's like return and esc
	 *
	 * @since Fri Sep 5 2008
	 * @access protected
	 * @return void
	 **/
	_addDefaultKeyListener: function() {
		// here for extending purposes only
		var element = element || this._windowElement;
		Event.observe(element, "keydown", this.keyHandle.bindAsEventListener(this) );
	},

	/**
	 * keyHandle
	 *
	 * Handles pressing enter or esc
	 *
	 * @since Fri Sep 5 2008
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	keyHandle: function(event) {
		var element = event.element();
		if (Object.isElement(element.up(".wjgui_window") ) ) {
			switch (event.keyCode) {
				case Event.KEY_RETURN:
					if (this.isVisible() ) {
						element.fire("wjgui:true");
					}
					break;
				case Event.KEY_ESC:
					if (this.isVisible() ) {
						element.fire("wjgui:close");
					}
					break;
				default:
					return;
			}
		}
	},

	/**
	 * addListener
	 *
	 * Adds a listener for a custom event that calls the given callback or the default callback of this window
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return WJWindow
	 **/
	addListener: function(eventName, callback, element) {
		WJDebugger.log(WJDebugger.INFO, "Adding listener in WJWindow", eventName, callback);
		var callback = callback || this.windowResult.bindAsEventListener(this);
		var element = element || this._windowElement;

		Event.observe(element, "wjgui:" + eventName, callback);
		this._setListener(eventName, {"element": element, "callback": callback} );
		return this;
	},

	/**
	 * _setListener
	 *
	 * Registers a listener function
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param string key
	 * @param Object elementAndCallback
	 * @return void
	 **/
	_setListener: function(key, elementAndCallback) {
		this._listeners.set(key, elementAndCallback);
	},

	/**
	 * removeListener
	 *
	 * Removes the listener set for key
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return WJWindow
	 **/
	removeListener: function(key) {
		var listener = this.getListener(key);
		Event.stopObserving(listener.element, "wjgui:" + key, listener.callback);
		this._listeners.unset(key);
		return this;
	},

	/**
	 * removeListeners
	 *
	 * Removes all listeners
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @return WJWindow
	 **/
	removeListeners: function() {
		this._listeners.each(function(info) {
			this.removeListener(info.key);
		}.bind(this) );
		return this;
	},

	/**
	 * windowResult
	 *
	 * Handles the window result event
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	windowResult: function(event) {
		this._callback.apply(this, arguments);
	},

	/**
	 * getListeners
	 *
	 * Returns the listeners hash
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return Hash
	 **/
	getListeners: function() {
		return this._listeners;
	},

	/**
	 * getListener
	 *
	 * Returns the listener info set for key
	 *
	 * @since Tue Aug 12 2008
	 * @access
	 * @param
	 * @return
	 **/
	getListener: function(key) {
		return this._listeners.get(key);
	},

	/**
	 * _getBaseClassname
	 *
	 * Returns the base classname used for windows
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return string
	 **/
	_getBaseClassname: function() {
		return "wjgui_window";
	},

	/**
	 * _getWindowRowTemplate
	 *
	 * Returns a template that can be used to create rows in windows
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return Template
	 **/
	_getWindowRowTemplate: function() {
		return new Template("<div class='#{classprefix}_#{rowname} #{classprefix}_row#{body}'><div class='#{classprefix}_left #{classprefix}_column'><div class='#{classprefix}_right #{classprefix}_column'><div class='#{classprefix}_center #{classprefix}_column'><div class='#{classprefix}_content'>&#160;</div></div></div></div></div>");
	},

	/**
	 * _createWindowRows
	 *
	 * Creates rows with names in the rows argument, appends them to windowElement and prefixes all classes with classprefix
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @param Array rows
	 * @param string classprefix
	 * @param Element windowElement
	 * @return array
	 **/
	_createWindowRows: function(rows, classprefix, windowElement) {
		var windowElement = windowElement || this._windowElement;
		rows.each(function(windowElement, classprefix, rowname, index) {
			var body = " " + classprefix + "_body";
			if (index == 0 || index == (rows.length - 1) ) {
				body = "";
			}
			windowElement.innerHTML += this._createRow(rowname, classprefix, body);
		}.bind(this, windowElement, classprefix));
		this._saveRows(rows, classprefix, windowElement);
		this._addWindowObjectGetters();
	},

	/**
	 * _addWindowObjectGetters
	 *
	 * Adds a getWJWindowObject getter to all content elements and the main window element
	 *
	 * @since Thu Oct 16 2008
	 * @access protected
	 * @return Array
	 **/
	_addWindowObjectGetters: function() {
		var test = [$H(this._contentElements).values(), this._windowElement].flatten();
		test.each(function(el) {
			el.getWJWindowObject = function() { return this; }.bind(this);
		}, this);
		
		return test;
	},

	/**
	 * _saveRows
	 *
	 * Saves all rows in this._contentElements
	 *
	 * @since Tue Sep 23 2008
	 * @access protected
	 * @param Array rows
	 * @param string classprefix
	 * @param Element windowElement
	 * @return WJWindow
	 **/
	_saveRows: function(rows, classprefix, windowElement) {
		var windowElement = windowElement || this._windowElement;
		this._contentElements = {};
		rows.each(function(windowElements, rowname, index) {
			this._contentElements[rowname] = windowElements[index];
		}.bind(this, windowElement.select("." + classprefix + "_content") ) );
		return this;
	},

	/**
	 * _createRow
	 *
	 * Creates the HTML of a row
	 *
	 * @since Tue Sep 23 2008
	 * @access protected
	 * @param string rowname
	 * @param string classprefix
	 * @param string body
	 * @return string
	 **/
	_createRow: function(rowname, classprefix, body) {
		var row = this._getWindowRowTemplate();
		return row.evaluate({"rowname": rowname, "classprefix": classprefix, "body": body});
	},

	/**
	 * getContentElement
	 *
	 * Returns the content element identified by rowname
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param string rowname
	 * @return DOMElement
	 **/
	getContentElement: function(rowname) {
		return this._contentElements[rowname];
	},

	/**
	 * evalContentElement
	 *
	 * Evaluates the script parts in the content element identified by rowname
	 *
	 * @since Wed Jul 30 2008
	 * @access public
	 * @param string rowname
	 * @return WJWindow
	 **/
	evalContentElement: function(rowname) {
		var element = this.getContentElement(rowname);
		element.innerHTML.evalScripts();
		return this;
	},

	/**
	 * _absolutizeTopLeft
	 *
	 * Puts the window in the top left corner of the viewport
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @param Element element
	 * @return void
	 **/
	_absolutizeTopLeft: function(element) {
		var element = element || this._windowElement;
		element.absolutize();
		element.setStyle({height: "", width: ""});
		this.setX(0, element);
		this.setY(0, element);
	},

	/**
	 * _checkMaxHeight
	 *
	 * Checks the window wo be inside the viewport
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @param DOMElement element
	 * @return void
	 **/
	_checkMaxHeight: function(element) {
		var element = element || this.getContentElement("main");
		if (this.getY() + this.getHeight() > document.viewport.getHeight() ) {
			this.setHeight(document.viewport.getHeight() - this.getY(), element, false);
		}
		else {
			element.setStyle({"maxHeight": ""});
		}
	},

	/**
	 * show
	 *
	 * Shows the window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	show: function(element) {
		var element = element || this._outerElement || this._windowElement;
		element.style.display = "block";
		try { element.focus() } catch(e) {} // TODO think of something better to get focus in browsers and a window in IE6
		return this;
	},

	/**
	 * hide
	 *
	 * Hides the window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	hide: function(element) {
		var element = element || this._outerElement || this._windowElement;
		element.style.display = "none";
		return this;
	},

	/**
	 * destroy
	 *
	 * Destroys the window
	 *
	 * @since Mon Jul 28 2008
	 * @access public
	 * @return WJWindow
	 **/
	destroy: function(element) {
		var element = element || this._outerElement || this._windowElement;
		if (element.parentNode) {
			element.remove();
		}
		return this;
	},

	/**
	 * _callback
	 *
	 * Does the callback that this window should do
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return mixed
	 **/
	_callback: function() {
		if (Object.isFunction(this._callbackFunction) ) {
			var args = $A(arguments);
			args.unshift(this);
			return this._callbackFunction.apply(this._callbackFunction,  args);
		}
	},

	/**
	 * _close
	 *
	 * Closes the window
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return mixed
	 **/
	_close: function(event) {
		this.hide();
	},

	/**
	 * setBaseTitle
	 *
	 * Changes the base title
	 *
	 * @since Wed Sep 10 2008
	 * @access public
	 * @param string title
	 * @return WJWindow
	 **/
	setBaseTitle: function(title) {
		this._basetitle = title;
		this.setTitle(this.getTitle() );
		return this;
	},

	/**
	 * setTitle
	 *
	 * Changes the title
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return WJWindow
	 **/
	setTitle: function(title) {
		this._title = title;
		var headers = this.getContentElement("title").getElementsByTagName("h1");
		if (headers.length < 1) {
			this.getContentElement("title").innerHTML = "<h1>&#160;</h1>" + this.getContentElement("title").innerHTML;
			return this.setTitle(this._title);
		}
		headers[0].innerHTML = this._getComposedTitle();
		return this;
	},

	/**
	 * _getComposedTitle
	 *
	 * Creates a nice looking title
	 *
	 * @since Wed Sep 10 2008
	 * @access protected
	 * @return string
	 **/
	_getComposedTitle: function() {
		return this._title + ( (this._basetitle != "" &&  this._basetitle != this._title) ? ( (this._title != "") ? " - " : "") + this._basetitle : "");
	},

	/**
	 * setContent
	 *
	 * Changes the content
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param mixed content
	 * @return WJWindow
	 **/
	setContent: function(content) {
		if (Object.isString(content) ) {
			this.getContentElement("main").innerHTML = content;
		}
		if (Object.isElement(content) ) {
			this.getContentElement("main").appendChild(content);
		}
		this._content = content;
		return this;
	},

	/**
	 * addButton
	 *
	 * Adds a button to the window
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @param string caption
	 * @param mixed callback
	 * @param boolean defaultButton
	 * @return DOMElement
	 **/
	addButton: function(caption, eventHandler, defaultButton) {
		WJDebugger.log(WJDebugger.INFO, "Adding button to window", caption, eventHandler, this);
		var button = WJButton.create(caption, eventHandler, defaultButton, this.getContentElement("buttons") );
		this._checkMaxHeight(); // this function is likely to change the height of the bottom row
		return button;
	},

	/**
	 * addStatusbar
	 *
	 * Adds a statusbar to the window
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	addStatusbar: function() {
		WJDebugger.log(WJDebugger.INFO, "Adding statusbar to window", this);
		this._statusbar = new Element("div", {"class": "wjgui_statusbar"});
		this.getContentElement("buttons").insert(this._statusbar);
		this._checkMaxHeight();
	},

	/**
	 * setStatusbar
	 *
	 * Sets the content of the statusbar
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @param string content
	 * @param integer fade
	 * @return void
	 **/
	setStatusbar: function(content, fade) {
		var fade = fade || -1;
		if (this._statusbar) {
			this._statusbar.update(content);
			if (fade > 0) {
				this.hideStatusbar.bind(this).delay(fade);
			}
		}
	},

	/**
	 * hideStatusbar
	 *
	 * Hides the contents of the statusbar, and then clears its contents
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	hideStatusbar: function() {
		Effect.Fade(this._statusbar, {duration: 3.0});
		this.clearAndShowStatusbar.bind(this).delay(3.5);
	},

	/**
	 * clearAndShowStatusbar
	 *
	 * Clears and shows the statusbar
	 *
	 * @since Mon Feb 02 2009
	 * @access public
	 * @return void
	 **/
	clearAndShowStatusbar: function() {
		this._statusbar.update("");
		this._statusbar.show();
	},

	/**
	 * setCallback
	 *
	 * Changes the callback function
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param Function callback
	 * @return WJWindow
	 **/
	setCallback: function(callback) {
		if (Object.isFunction(callback) ) {
			this._callbackFunction = callback;
		}
		return this;
	},

	/**
	 * setX
	 *
	 * Changes the x position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer x
	 * @return WJWindow
	 **/
	setX: function(x, element) {
		this._x = x || 0;
		var element = element || this._windowElement;
		element.style.left = x + "px";
		return this;
	},

	/**
	 * setY
	 *
	 * Changes the y position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer y
	 * @return WJWindow
	 **/
	setY: function(y, element) {
		this._y = y || 0;
		var element = element || this._windowElement;
		element.style.top = y + "px";
		this._checkMaxHeight(element);
		return this;
	},

	/**
	 * setZ
	 *
	 * Changes the z-index of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer z
	 * @return WJWindow
	 **/
	setZ: function(z, element) {
		this._z = z || 10000;
		var element = element || this._windowElement;
		element.style.zIndex = z;
		return this;
	},

	/**
	 * setWidth
	 *
	 * Changes the width of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer width
	 * @return WJWindow
	 **/
	setWidth: function(width, element) {
		this._width = width;
		var element = element || this._windowElement;
		element.setStyle({"width": width + "px"});
		element.fire("wjgui:resize");
		return this;
	},

	/**
	 * setHeight
	 *
	 * Changes the height of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @param integer height
	 * @return WJWindow
	 **/
	setHeight: function(height, element, checkHeight) {
		this._height = height;
		var element = element || this.getContentElement("main");

		var wasVisible = this.isVisible();
		var origX = this.getX();
		var origY = this.getY();
		if (!wasVisible) {
			this.setX(-10000);
			this.setY(-10000);
			this.show();
		}

		var otherRowsHeight = 0;
		for (var key in this._contentElements) {
			if (key != "main") {
				otherRowsHeight += this._contentElements[key].getHeight();
			}
		}

		var height = (Object.isNumber(height) == false) ? height : (height - otherRowsHeight) + "px";
		element.setStyle({"height": height});

		if (!wasVisible) {
			this.hide();
			this.setX(origX);
			this.setY(origY);
		}

		if (checkHeight != false) {
			this._checkMaxHeight(element);
		}
		if (!checkHeight) {
			element.fire("wjgui:resize");
		}
		return this;
	},

	/**
	 * getBaseTitle
	 *
	 * Returns the title of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getBaseTitle: function() {
		return this._basetitle;
	},

	/**
	 * getTitle
	 *
	 * Returns the title of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getTitle: function() {
		return this._title;
	},

	/**
	 * getType
	 *
	 * Tells what window type this window is
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return string
	 **/
	getType: function() {
		return this._type;
	},

	/**
	 * getContent
	 *
	 * Returns the content of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return mixed
	 **/
	getContent: function() {
		return this._content;
	},

	/**
	 * getContenttype
	 *
	 * Returns the type of the content
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return mixed
	 **/
	getContenttype: function() {

	},

	/**
	 * getCallback
	 *
	 * Returns the callback function
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return Function
	 **/
	getCallback: function() {
		return this._callbackFunction;
	},

	/**
	 * isVisible
	 *
	 * Tells if this window can be seen
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return boolean
	 **/
	isVisible: function() {
		var xInLow = (this.getX() < 0 && (this.getX() + this.getWidth() ) > 0);
		var xInHigh = (this.getX() >= 0 && this.getX() < document.viewport.getWidth() );
		var yInLow = (this.getY() < 0 && (this.getY() + this.getHeight() ) > 0);
		var yInHigh = (this.getY() >= 0 && this.getY() < document.viewport.getHeight() );
		if ( (xInLow || xInHigh) && (yInLow || yInHigh) && this._windowElement.visible() ) {
			return true;
		}
		return false;
	},

	/**
	 * getX
	 *
	 * Tells the x position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getX: function(element) {
		var element = element || this._windowElement;
		return parseInt(element.style.left);
	},

	/**
	 * getY
	 *
	 * Tells the y position of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getY: function(element) {
		var element = element || this._windowElement;
		return parseInt(element.style.top);
	},

	/**
	 * getZ
	 *
	 * Tells the zIndex of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getZ: function() {
		var element = element || this._windowElement;
		return element.style.zIndex;
	},

	/**
	 * getWidth
	 *
	 * Tells the width of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getWidth: function(element) {
		var element = element || this._windowElement;
		return element.getWidth();
	},

	/**
	 * getHeight
	 *
	 * Tells the height of this window
	 *
	 * @since Fri Jun 27 2008
	 * @access public
	 * @return integer
	 **/
	getHeight: function(element) {
		var element = element || this._windowElement;
		return element.getHeight();
	},

	/**
	 * getContentHeight
	 *
	 * Tells what's the height of the content element
	 *
	 * @since Tue Sep 16 2008
	 * @access public
	 * @return integer
	 **/
	getContentHeight: function() {
		return this.getContentElement("main").getHeight();
	},

	/**
	 * getContentWidth
	 *
	 * Tells what's the width of the content element
	 *
	 * @since Tue Sep 16 2008
	 * @access public
	 * @return integer
	 **/
	getContentWidth: function() {
		return this.getContentElement("main").getWidth();
	},

	/**
	 * getWindowElement
	 *
	 * Returns the windowElement
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return DOMElement
	 **/
	getWindowElement: function() {
		return this._windowElement;
	},

	/**
	 * center
	 *
	 * centers the given element
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param DOMElement element
	 * @return WJWindow
	 **/
	center: function(element) {
		var element = element || this._windowElement;
		var remainsX = document.viewport.getWidth() - this.getWidth(element);
		var remainsY = document.viewport.getHeight() - this.getHeight(element);

		this.setX(remainsX / 2, element).setY(remainsY / 2, element);
		return this;
	},

	/**
	 * keepCentered
	 *
	 * Makes sure the window stays centered
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param DOMElement element
	 * @return void
	 **/
	keepCentered: function(element) {
		if (!this._centerObserver) {
			var element = element || this._windowElement;
			this._centerObserver = this.center.bind(this, element);
			Event.observe(window, "resize", this._centerObserver);
		}
	},

	/**
	 * stopCentered
	 *
	 * Stops centering the window
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return void
	 **/
	stopCentered: function() {
		Event.stopObserving(window, "resize", this._centerObserver);
		this._centerObserver = null;
	},

	/**
	 * maximize
	 *
	 * Maximizes the window
	 *
	 * @since Fri Aug 8 2008
	 * @access public
	 * @return WJWindow
	 **/
	maximize: function(paddingTop, paddingRight, paddingBottom, paddingLeft, noScroll) {
		var paddingTop = paddingTop || 0;
		var paddingRight = paddingRight || paddingTop;
		var paddingBottom = paddingBottom || paddingTop;
		var paddingLeft = paddingLeft || paddingTop;
		var noScroll = noScroll || false;

		this.setX(paddingLeft).setY(paddingTop);

		if (noScroll) {
			this.setWidth(0).setHeight(0);
		}

		this.setWidth(document.viewport.getWidth() - (paddingLeft + paddingRight) ).setHeight(document.viewport.getHeight() - (paddingTop + paddingBottom) );
		return this;
	},

	/**
	 * keepMaximized
	 *
	 * Makes sure the window stays maximized
	 *
	 * @since Tue Sep 9 2008
	 * @access public
	 * @return void
	 **/
	keepMaximized: function() {
		if (!this._maximizedObserver) {
			var paddingTop = this.getY();
			var paddingLeft = this.getX();
			var paddingRight = document.viewport.getWidth() - this.getWidth() - paddingLeft;
			var paddingBottom = document.viewport.getHeight() - this.getHeight() - paddingTop;

			this._maximizedObserver = this.maximize.bind(this, paddingTop, paddingRight, paddingBottom, paddingLeft, true);
			Event.observe(window, "resize", this._maximizedObserver);
		}
	},

	/**
	 * stopMaximized
	 *
	 * Stops maximizing the window
	 *
	 * @since Tue Sep 9 2008
	 * @access public
	 * @return void
	 **/
	stopMaximized: function() {
		Event.stopObserving(window, "resize", this._maximizedObserver);
		this._maximizedObserver = null;
	},

	/**
	 * setLoading
	 *
	 * Mark this window as loading (or not)
	 *
	 * @since Wed Sep 3 2008
	 * @access public
	 * @param boolean loading
	 * @param function loadCallback;
	 * @return WJWindow
	 **/
	setLoading: function(loading, loadCallback) {
		var loadCallback = loadCallback || false;
		if (loading && !this.getLoading() ) {
			this.getWindowElement().addClassName("wjgui_window_loading");
			if (!loadCallback) {
				this.getContentElement("main").setStyle({"visibility": "hidden"});
			}
			else {
				loadCallback(this, loading);
			}
		}
		else if (!loading && this.getLoading() ) {
			this.getWindowElement().removeClassName("wjgui_window_loading");
			if (!loadCallback) {
				this.getContentElement("main").setStyle({"visibility": "visible"});
			}
			else {
				loadCallback(this, loading);
			}
		}
		this._loading = loading;
		return this;
	},

	/**
	 * getLoading
	 *
	 * Tells if this window is marked as loading
	 *
	 * @since Wed Sep 3 2008
	 * @access public
	 * @return boolean
	 **/
	getLoading: function() {
		return this._loading;
	},

	/**
	 * setTheme
	 *
	 * Sets the theme for the window
	 *
	 * @since Fri Dec 5 2008
	 * @access public
	 * @param string theme
	 * @return void
	 **/
	setTheme: function(theme) {
		if (theme == this.getTheme() ) {
			return;
		}
		this._removeOldTheme();
		this._setTheme(theme);
		this._addNewTheme();
	},

	/**
	 * _setTheme
	 *
	 * Sets the value of the _theme property
	 *
	 * @since Fri Dec 5 2008
	 * @access protected
	 * @param string theme
	 * @return void
	 **/
	_setTheme: function(theme) {
		this._theme = theme || "default";
	},

	/**
	 * _removeOldTheme
	 *
	 * Removes the previously set theme class from the window
	 *
	 * @since Fri Dec 5 2008
	 * @access
	 * @param
	 * @return
	 **/
	_removeOldTheme: function() {
		this.getWindowElement().removeClassName(this._getBaseClassname() + "theme_" + this.getTheme() );
	},

	/**
	 * _addNewTheme
	 *
	 * Adds the new theme class to the window
	 *
	 * @since Fri Dec 5 2008
	 * @access protected
	 * @return void
	 **/
	_addNewTheme: function() {
		this.getWindowElement().addClassName(this._getBaseClassname() + "theme_" + this.getTheme() );
	},

	/**
	 * getTheme
	 *
	 * Returns the value of the _theme property
	 *
	 * @since Fri Dec 5 2008
	 * @access public
	 * @return string
	 **/
	getTheme: function() {
		return this._theme;
	}
});


WJWindow._messagedialog = function(type, message, callback, show) {
	var win = new WJWindow(callback);
	var mwin = new window[type](win);
	mwin.setMessage(message);
	if (show) {
		mwin.show();
	}
	return mwin;
}
WJWindow.alert = function(message, callback) {
	return WJWindow._messagedialog("WJWindowAlert", message, callback, true);
}
WJWindow.notice = function(message, callback) {
	return WJWindow._messagedialog("WJWindowNotice", message, callback, true);
}
WJWindow.confirm = function(message, callback) {
	return WJWindow._messagedialog("WJWindowConfirm", message, callback, true);
}
WJWindow.booleanConfirm = function(message, callback) {
	return WJWindow._messagedialog("WJWindowBooleanConfirm", message, callback, true);
}
WJWindow.prompt = function(message, callback) {
	return WJWindow._messagedialog("WJWindowPrompt", message, callback, true);
}

/**
 * WJWindowModal
 *
 * The base class to decorate a window with modal properties
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowModal = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new instanceof WJWindowModal
	 *
	 * @since Fri Jul 4 2008
	 * @access public
	 * @param WJWindow wjwindow
	 * @return WJWindowModal
	 **/
	initialize: function(toDecorate) {
		this._decorate(toDecorate);
		this._createModalLayer();
		this._maxblink = 6;
		this._addBlinkListener();
		this._rebindListeners();
		this._rebindWindowObjectGetters();
		this.setZ(2147483647);
		this.show(); // Remove this
	},

	/**
	 * _decorate
	 *
	 * Decorates the given object
	 *
	 * @since Tue Jul 8 2008
	 * @access protected
	 * @param WJWindow toDecorate
	 * @return void
	 **/
	_decorate: function(toDecorate) {
		this._decorated = toDecorate;

		for (property in this._decorated) {
			// Add all methods not defined in this
			if (!Object.isFunction(this[property]) ) {
				this[property] = this._decorated[property];
			}
		}
	},

	/**
	 * _rebindListeners
	 *
	 * Stops the listeners observing and rebinds 'this' to the listeners
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_rebindListeners: function() {
		this.removeListeners();
		this._addDefaultListeners();
	},

	/**
	 * _createModalLayer
	 *
	 * Creates a layer to simulate modality
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @return void
	 **/
	_createModalLayer: function() {
		var windowElement = this._decorated.getWindowElement();
		
		this._modalLayer = new WJModalLayer();
		this._modalLayer.getLayer().insert(windowElement);
		this._outerElement = this._modalLayer.getLayer();
	},

	/**
	 * show
	 *
	 * Shows the window
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return void
	 **/
	show: function(element) {
		var element = element || this._outerElement || this._modalLayer.getLayer();
		element.style.display = "block";
		this._setBodyOverflow("hidden");
		this._decorated.show();
	},

	/**
	 * hide
	 *
	 * Hides the window
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @return void
	 **/
	hide: function(element) {
		var element = element || this._outerElement || this._modalLayer.getLayer();
		element.style.display = "none";
		this._setBodyOverflow();
		this._decorated.hide();
	},

	/**
	 * _setBodyOverflow
	 *
	 * Set's the overflow property of body (and html for IE7)
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @param string value
	 * @return void
	 **/
	_setBodyOverflow: function(value) {
		var value = value || "";
		$(document.body).setStyle({overflow: value});
		$(document.getElementsByTagName("html")[0]).setStyle({overflow: value});
	},

	/**
	 *
	 *
	 *
	 *
	 * @since Thu Jul 10 2008
	 * @access
	 * @param
	 * @return
	 **/
	_addBlinkListener: function() {
		Event.observe(this._modalLayer.getLayer(), "click", this.blink.bindAsEventListener(this) );
	},

	/**
	 *
	 *
	 *
	 *
	 * @since Thu Jul 10 2008
	 * @access
	 * @param
	 * @return
	 **/
	blink: function(event, doBlink, count) {
		var count = count || 0;
		if (Event.element(event).className == this._getBaseClassname() + "_modality") {
			if (doBlink) {
				this.getWindowElement().addClassName(this._getBaseClassname() + "_blink");
				doBlink = false;
			}
			else {
				this.getWindowElement().removeClassName(this._getBaseClassname() + "_blink");
				doBlink = true;
			}
			if (count < this._maxblink) {
				count++
				this.blink.bind(this, event, doBlink, count).delay(0.05);
			}
		}
	},

	/**
	 * _rebindWindowObjectGetters
	 *
	 * Adds a getWJWindowObject getter to all content elements and the main window element and binds it to this not the decorated window
	 *
	 * @since Fri Feb 13 2009
	 * @access protected
	 * @return void
	 **/
	_rebindWindowObjectGetters: function() {
		var els = this._decorated._addWindowObjectGetters();
		els.each(function(el) {
			el.getWJWindowObject = el.getWJWindowObject.bind(this);
		}, this);
		return els;
	}
});

/**
 * WJModalLayer is a class to create a modal layer on the page to disallow any user interaction other that on things on top op the layer
 **/
var WJModalLayer = Class.create({
	/**
	 * initialize
	 *
	 * Creates and applies a WJModalLayer
	 *
	 * @since Tue Jan 06 2009
	 * @access public
	 * @param Element parent
	 * @param Element modalLayer
	 * @return void
	 **/
	initialize: function(parent, modalLayer) {
		var parent = parent || document.body;
		this._modalLayer = (modalLayer || new Element("div") );
		Element.extend(this._modalLayer).addClassName("wjgui_window_modality");
		parent.appendChild(this._modalLayer);
		this._removed = false;
		this._absolutizeTopLeft();
		this._fillViewport();

		Event.observe(window, "resize", this._fillViewport.bind(this, this._modalLayer) ); // on purpose, no need to bind as event listener
	},

	/**
	 * _absolutizeTopLeft
	 *
	 * Puts the window in the top left corner of the viewport
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @param Element element
	 * @return void
	 **/
	_absolutizeTopLeft: function(element) {
		var element = this._modalLayer;
		element.absolutize();
		element.setStyle({left: 0, top: 0, zIndex: 2147483647});
	},

	/**
	 * _fillViewport
	 *
	 * Stretches the given element to fill the viewport
	 *
	 * @since Mon Jul 7 2008
	 * @access protected
	 * @param DOMElement element
	 * @return void
	 **/
	_fillViewport: function() {
		var element = $(this._modalLayer);
		if ($(element.parentNode) && $(element.parentNode).getHeight) {
			element.setStyle( {width: $(element.parentNode).getWidth() + "px", height: $(element.parentNode).getHeight() + "px"} );
		}
		else {
			element.setStyle( {width: document.viewport.getWidth() + "px", height: document.viewport.getHeight() + "px"} );
		}
	},

	/**
	 * getLayer
	 *
	 * Gets the layer element
	 *
	 * @since Tue Jan 06 2009
	 * @access public
	 * @return htmlelement
	 **/
	getLayer: function() {
		return this._modalLayer;
	},

	/**
	 * destroy
	 *
	 * Destroys this modal layer
	 *
	 * @since Tue Jan 06 2009
	 * @access public
	 * @return void
	 **/
	destroy: function() {
		if (!this._removed) {
			this._modalLayer.remove();
			this._removed = true;
		}
	}
});

/**
 * WJWindowMessageDialog
 *
 * A class handling modal alert messages
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowMessageDialog = Class.create(WJWindowModal, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowMessageDialog
	 *
	 * @since Mon Jul 7 2008
	 * @access public
	 * @param Class $super
	 * @param Function callback
	 * @param string type
	 * @return WJWindowMessageDialog
	 **/
	initialize: function($super, toDecorate) {
		$super(toDecorate);
		this.center();
		this.keepCentered();
		this._drawDefaultContent();
		this._buttons = null;
		this._addButtons();
	},

	/**
	 * _drawDefaultContent
	 *
	 * Draws the default alert content
	 *
	 * @since Tue Jul 8 2008
	 * @access protected
	 * @return void
	 **/
	_drawDefaultContent: function() {
		var content = this.getContentElement("main");
		var replaces = this._getTemplateValues();
		var template = this._getTemplate();
		content.innerHTML = template.evaluate(replaces);
		this._contentElements["message"] = content.down("." + replaces.classprefix+"_message");
	},

	/**
	 *
	 *
	 *
	 *
	 * @since Wed Jul 9 2008
	 * @access
	 * @param
	 * @return
	 **/
	_getTemplateValues: function() {
		return {"classprefix": this._getBaseClassname(), "windowtype": this._type};
	},

	/**
	 * _addButtons
	 *
	 * Adds the right buttons
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_addButtons: function() {
		if (this._buttons == null) {
			this._buttons = new Hash();
			this._buttons.set("ok", WJButton.create("OK", "true", true, this.getContentElement("buttons") ) ).focus();
		}
		return this._buttons;
	},

	/**
	 * _getMainTemplate
	 *
	 *
	 *
	 * @since Tue Jul 8 2008
	 * @access protected
	 * @return Template
	 **/
	_getTemplate: function() {
		return new Template("<div class='#{classprefix}_messageicon #{classprefix}_messageicon_#{windowtype}'>&#160;</div><div class='#{classprefix}_message'>&#160;</div>");
	},

	/**
	 * setMessage
	 *
	 * Sets the message for this window
	 *
	 * @since Tue Jul 8 2008
	 * @access public
	 * @param string message
	 * @return void
	 **/
	setMessage: function(message) {
		this.message = message.stripTags().stripScripts().split("\n").join("<br/>");
		this.getContentElement("message").innerHTML = this.message;
	},

	/**
	 * _callback
	 *
	 * Does the callback that this window should do (and hides the message)
	 *
	 * @since Fri Jun 27 2008
	 * @access protected
	 * @return mixed
	 **/
	_callback: function() {
		this.hide();
		return this._decorated._callback.apply(this._decorated, $A(arguments) );
	}
});

/**
 * WJWindowAlert
 *
 * A class handling modal alert messages
 *
 * @since Fri Jun 27 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowAlert = Class.create(WJWindowMessageDialog, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowAlert
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param WJWindow toDecorate
	 * @return WJWindowAlert
	 **/
	initialize: function($super, toDecorate) {
		if (!this._type) {
			this._type = "alert";
		}
		$super(toDecorate);
		this.getListener("close").callback.setArgument(0, true); // closing an alert means ok
		this.removeListener("false");// an alert can only be true
		this.removeListener("save");
		this.removeListener("cancel");
	}
});

/**
 * WJWindowNotice
 *
 * A class handling modal notice (friendly alert) messages
 *
 * @since Mon Jul 07 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJWindowNotice = Class.create(WJWindowAlert, {
	/**
	 * initialize
	 *
	 * Creates a new WJWindowNotice
	 *
	 * @since Mon Jul 07 2008
	 * @access public
	 * @param Class $super
	 * @param Function callback
	 * @param string type
	 * @return WJWindowNotice
	 **/
	initialize: function($super, toDecorate) {
		this._type = "notice";
		$super(toDecorate);
	}
});

/**
 * WJButton
 *
 * A class handling the creation of buttons
 *
 * @since Wed Jul 09 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Aeroplane
 **/
var WJButton = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJButton
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @param string caption
	 * @param Function|string event
	 * @param string type
	 * @return WJButton
	 **/
	initialize: function(caption, eventHandler, defaultButton, parentElement) {
		WJDebugger.log(WJDebugger.INFO, "Create new button", caption, eventHandler, defaultButton, parentElement);
		this._caption = caption;
		this._eventHandler = eventHandler;
		this._defaultButton = (defaultButton === true) ? true : false;
		this._parentElement = $(parentElement);
		
		this._buildEventHandler();
		this._createButton();
		this._addButtonMethods();
		this._addObserver();
		
		if (Object.isElement(this._parentElement) ) {
			this._parentElement.appendChild(this.getButton() );
		}
		this.updateCaption(this._caption);
	},

	/**
	 * _addButtonMethods
	 *
	 * Adds methods to the element to easy update the caption and enable or disable the button
	 *
	 * @since Thu Jul 10 2008
	 * @access protected
	 * @return void
	 **/
	_addButtonMethods: function() {
		this.getButton().updateCaption = this.updateCaption.bind(this);
		this.getButton().setCaption = this.getButton().updateCaption;
		this.getButton().getCaption = this.getCaption.bind(this);
		this.getButton().enable = this.enable.bind(this);
		this.getButton().disable = this.disable.bind(this);
	},

	/**
	 * updateCaption
	 *
	 * A method that updates the caption content of the button (and resizes the button if needed)
	 *
	 * @since Thu Jul 10 2008
	 * @access public
	 * @param string caption
	 * @return Element
	 **/
	updateCaption: function(caption) {
		this._caption = caption.stripTags().stripScripts();
		return this._setCaption.bind(this).defer();
	},

	/**
	 * setCaption
	 *
	 * Changes the caption of this button
	 *
	 * @since Mon Feb 16 2009
	 * @access public
	 * @param string caption
	 * @return Element
	 **/
	setCaption: function(caption) {
		this.updateCaption(caption);
	},

	/**
	 * _setCaption
	 *
	 * Performs the real setting of the caption (it used to be called with a defer from initialize, now it's deferred always (@see updateCaption), to avoid it being updated later by old calls)
	 *
	 * @since Fri Feb 13 2009
	 * @access protected
	 * @return void
	 **/
	_setCaption: function() {
		var contentElement = this.getContentElement();
		contentElement.update(this._caption);
		this.setWidth(this._getNewButtonWidth(this._caption, contentElement) );
		return this.getButton();
	},

	/**
	 * enable
	 *
	 * Enables the button
	 *
	 * @since Mon Nov 10 2008
	 * @access public
	 * @return this
	 **/
	enable: function() {
		this.getButton().removeAttribute("disabled");
		this.getButton().removeClassName("wjgui_button_disabled");
		return this;
	},

	/**
	 * disable
	 *
	 * Disables the button
	 *
	 * @since Mon Nov 10 2008
	 * @access public
	 * @return this
	 **/
	disable: function() {
		this.getButton().disabled = "disabled";
		this.getButton().addClassName("wjgui_button_disabled");
		return this;
	},

	/**
	 * getContentElement
	 *
	 * Returns the container of the content
	 *
	 * @since Tue Aug 19 2008
	 * @access public
	 * @return Element
	 **/
	getContentElement: function() {
		return this.getButton().down("." + this._getBaseClassName() + "_content");
	},

	/**
	 * getNewButtonWidth
	 *
	 * Returns the width needed to fit the content on the button
	 *
	 * @since Tue Aug 19 2008
	 * @access protected
	 * @param string text
	 * @param Element element
	 * @return integer
	 **/
	_getNewButtonWidth: function(text, element) {
		var fontfamily = element.getStyle("fontFamily");
		var fontsize = element.getStyle("fontSize");
		var fontweight = element.getStyle("fontWeight");
		WJButton.measureElement.setStyle({"fontFamily": fontfamily, "fontSize": fontsize, "fontWeight": fontweight}).update(text);
		var width = document.body.appendChild(WJButton.measureElement).getWidth();
		WJButton.measureElement.remove();
		width += this.getWidth() - element.getWidth();
		return (width > 100 || this.getButton().up("div.autowidth", 0) ) ? width : 100;
	},

	/**
	 * getWidth
	 *
	 * Tells the width of the button
	 *
	 * @since Mon Aug 18 2008
	 * @access public
	 * @return integer
	 **/
	getWidth: function() {
		return this.getButton().getWidth();
	},

	/**
	 * setWidth
	 *
	 * Sets the width of the button
	 *
	 * @since Mon Aug 18 2008
	 * @access public
	 * @param integer width
	 * @return void
	 **/
	setWidth: function(width) {
		this.getButton().setStyle({"width": width + "px"});
	},
	
	/**
	 * _buildEventHandler
	 *
	 * Creates a function to use as click event handler
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_buildEventHandler: function() {
		if (!Object.isFunction(this._eventHandler) ) {
			if (this._eventHandler.indexOf(":") == -1) {
				this._eventHandler = this._getEventPrefix() + ":" + this._eventHandler;
				WJDebugger.log(WJDebugger.INFO, "Build event handler for this, create event " + this._eventHandler);
				var func = function(event, eventHandler) {
					WJDebugger.log(WJDebugger.INFO, "Fire event in WJButton", event, eventHandler);
					Event.element(event).fire(eventHandler);
				}.bindAsEventListener(this.getButton(), this._eventHandler);
				this._eventHandler = func;
			}
		}
	},
	
	/**
	 * _getEventPrefix
	 *
	 * Returns the namespacing prefix for the event to fire
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return string
	 **/
	_getEventPrefix: function() {
		return "wjgui";
	},
	
	/**
	 * _createButton
	 *
	 * Creates the button element
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_createButton: function() {
		this._buttonElement = new Element("button");
		var template = this._getTemplate();
		var replaces = {};
		replaces.classprefix = this._getBaseClassName();

		this._buttonElement.className = replaces.classprefix + " " + replaces.classprefix + "_" + ((this._defaultButton) ? "" : "no") + "default";

		this._buttonElement.innerHTML = template.evaluate(replaces);
	},

	/**
	 * _getBaseClassName
	 *
	 * Returns the base className for buttons
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return string
	 **/
	_getBaseClassName: function() {
		return "wjgui_button";
	},

	/**
	 * _getTemplate
	 *
	 * Returns a template element on which the button innerHTML is based
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return Template
	 **/
	_getTemplate: function() {
		return new Template("<div class='#{classprefix}_left #{classprefix}_column'><div class='#{classprefix}_right #{classprefix}_column'><div class='#{classprefix}_center #{classprefix}_column'><div class='#{classprefix}_content'>&#160;</div></div></div></div>");
	},

	/**
	 * getButton
	 *
	 * Returns the created button
	 *
	 * @since Wed Jul 9 2008
	 * @access public
	 * @return DOMElement
	 **/
	getButton: function() {
		return this._buttonElement;
	},
	
	/**
	 * _addObserver
	 *
	 * Adds an observer function to the button's click event
	 *
	 * @since Wed Jul 9 2008
	 * @access protected
	 * @return void
	 **/
	_addObserver: function() {
		WJDebugger.log(WJDebugger.INFO, "_addObserver in WJButton", this._eventHandler);
		WJDebugger.log(WJDebugger.DEBUG, "WJButton observer code", this._eventHandler.toString() );
		Event.observe(this.getButton(), "click", this._eventHandler);
	},

	/**
	 * setObserver
	 *
	 * Sets the current click observer for this button to eventHandler
	 *
	 * @since Tue Sep 16 2008
	 * @access public
	 * @param function eventHandler
	 * @return void
	 **/
	setObserver: function(eventHandler) {
		Event.stopObserving(this.getButton(), "click", this._eventHandler);
		this._eventHandler = eventHandler;
		this._addObserver();
	},

	/**
	 * getCaption
	 *
	 * Returns the value of teh caption
	 *
	 * @since Tue Feb 10 2009
	 * @access public
	 * @return string
	 **/
	getCaption: function() {
		return this._caption;
	},

	/**
	 * recalculateWidth
	 *
	 * Recalculates the width by updating the caption with the current caption
	 *
	 * @since Tue Feb 10 2009
	 * @access public
	 * @return void
	 **/
	recalculateWidth: function() {
		this.updateCaption(this.getCaption() );
	}
});

/**
 * an element to measure string length in context
 *
 * @since Tue Aug 19 2008
 * @access public
 **/
WJButton.measureElement = new Element("div", {"style": "position: absolute; left: -1000px;"});

/**
 * create
 *
 * Static method to create a single button
 *
 * @since Wed Jul 9 2008
 * @access public
 * @param string caption
 * @param Function|string eventHandler
 * @param boolean defaultButton
 * @param DOMElement element
 * @return DOMElement
 **/
WJButton.create = function(caption, eventHandler, defaultButton, parentElement) {
	var button = new WJButton(caption, eventHandler, defaultButton, parentElement);
	return button.getButton();
}

/**
 * toWJButtonStyle
 *
 * Styles a given button as a WJButton (NOTE: does not create a WJButton instance and does not attach methods and so on)
 *
 * @since Fri Mar 20 2009
 * @access public
 * @param Element element
 * @param boolean defaultButton
 * @return Element
 **/
WJButton.toWJButtonStyle = function(element, defaultButton) {
	if (element.tagName.toLowerCase() == "button") {
		var element = $(element);
		var caption = element.getTextContent();
		var template = WJButton.prototype._getTemplate();
		var replaces = {classprefix: WJButton.prototype._getBaseClassName()};

		element.className = replaces.classprefix + " " + replaces.classprefix + "_" + ((defaultButton) ? "" : "no") + "default";
		element.update(template.evaluate(replaces) );
		element.down("." + replaces.classprefix + "_content").update(caption);
	}
	return element;
}



/**
 * WJGuiSettings
 *
 * Mixes existing WJGuiSettings with default values
 *
 * @since Fri Oct 3 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.WJGui.Javascript
 **/
var WJGuiSettings = Object.extend({
	windowBaseTitle: "Windmill CMS"
}, WJGuiSettings || {});
/**
 * WJLogin is a class that handles the event observers that lead to the login window for not-loggedin users
 *
 * @since Tue Aug 12 2008
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Aeroplane
 **/
var WJLogin = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJLogin
	 *
	 * @since Tue Aug 12 2008
	 * @access public
	 * @param string module
	 * @return WJLogin
	 **/
	initialize: function(module, loggedIn) {
		this.module = module;
		this.spinQueue = [];
		if (!loggedIn) {
			this._startObservers();
		}
	},

	/**
	 * _startObservers
	 *
	 * Starts the observers listening to a login request
	 *
	 * @since Thu Aug 14 2008
	 * @access protected
	 * @return void
	 **/
	_startObservers: function() {
		Event.observe(document, "keydown", this.observeKey.bindAsEventListener(this) );
	},

	/**
	 * observeKey
	 *
	 * Observes key events and starts a login session if CTRL+L is pressed
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	observeKey: function(event) {
		if (event.keyCode == 76) { // L
			if (event.ctrlKey) {
				Event.stop(event);
				// Somehow Event.stop(event) doesn't work full in FF
				if (event.preventDefault) {
					event.preventDefault();
				}
				this.startLogin();
			}
		}
	},

	/**
	 * startLogin
	 *
	 * Starts a login session
	 *
	 * @since Thu Aug 14 2008
	 * @access public
	 * @return void
	 **/
	startLogin: function() {
		this.application = aeroplane.openApplication("Login", this.module);
	},
	
	/**
	 * registerLoginHandler
	 *
	 * Registers the login app with the aeroplane status handler
	 *
	 * @since Thu Feb 12 2009
	 * @access public
	 * @return void
	 **/
	registerLoginHandler: function() {
		aeroplane.registerSpinErrorHandler(403, this.handleAccessDenied.bind(this) );
	},
	
	/**
	 * handleAccessDenied
	 *
	 * Hanles an AD spin response
	 *
	 * @since Thu Feb 12 2009
	 * @access 
	 * @param 
	 * @return 
	 **/
	handleAccessDenied: function(response, spinCall) {
		if (typeof (spinCall) == "function") {
			this.spinQueue.push(spinCall);
		}
		this.application = aeroplane.openApplication("Login", this.module);
		this.application.setLoginCorrectHandler(this.invokeSpinQueue.bind(this) );
	},

	/**
	 * invokeSpinQueue
	 *
	 * Invokes all stacked spins
	 *
	 * @since Thu Feb 12 2009
	 * @access public
	 * @return void
	 **/
	invokeSpinQueue: function(response) {
		if (typeof (this.spinQueue[0] ) == "function") {
			this.spinQueue[0]();
			delete this.spinQueue[0];
			this.invokeSpinQueue(response);
		}
	}
});


