/**
 * 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.componentType = "WJComponent";
		this.components = new Array();
		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;
	},

	/**
	 * observe
	 *
	 * Adds an observer to this component's htmlelement
	 *
	 * @since Fri Nov 06 2009
	 * @access public
	 * @param string event
	 * @param function callback
	 * @return void
	 **/
	observe: function(event, callback) {
		if (Object.isArray(this.htmlelement) ) {
			this.htmlelement.each(function(s) {
				s.observe(event, callback);
			});
		}
		else if (this.htmlelement) {
			this.htmlelement.observe(event, callback);
		}
	},
	
	/**
	 * 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 " + this.componentType + " " + 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;
	},


	/**
	 * valueChanged
	 *
	 * Returns true if the value of this component has changed
	 *
	 * @since Tue May 19 2009
	 * @access public
	 * @return boolean
	 **/
	valueChanged: function() {
		var oldValue = this.value;
		this.getValue();
		var changed = (oldValue == null && this.value == null) ? false : (oldValue != this.getValue() ); // extra null check is because null != null is true since null means unknown (and it is more likely that two unknowns would differ than that two unknowns are equal)
		this.value = oldValue;
		return changed;
	},

	/**
	 * getValue
	 *
	 * Gets the value of this component
	 *
	 * @since 
	 * @access public
	 * @param boolean nochange
	 * @return 
	 **/
	getValue: function(nochange) {
		var usesDijit = (typeof(dijit) != "undefined") ? true : false;
		var nochange = nochange || false;

		var oldvalue = this.value;
		
		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 != null) ) {
			this.value = this.htmlelement.value;
		}

		if (nochange) {
			var newval = this.value;
			this.value = oldvalue;
			return newval;
		}
		else {
			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;
	},

	/**
	 * toString
	 *
	 * Gives a string representation of this component
	 *
	 * @since Tue Apr 14 2009
	 * @access public
	 * @return string
	 **/
	toString: function() {
		if (this.componentType === null) {
			return "Uninitialized WJComponent";
		}
		return this.componentType + " instance '" + this.id + "'";
	}
});
/**
 * WJRestrictedInput
 *
 * Base component for a component with limited input options
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJRestrictedInput = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJRestrictedInput
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJRestrictedInput
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJRestrictedInput";
	}
});
/**
 * WJSinglevalueInput
 *
 * Base component for input type where only one value may be selected
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJSinglevalueInput = Class.create(WJRestrictedInput, {
	/**
	 * initialize
	 *
	 * Initialize this WJSinglevalueInput
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJSinglevalueInput
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJSinglevalueInput";
	}
});
/**
 * WJMultivalueInput
 *
 * Base component for selecting multiple values
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJMultivalueInput = Class.create(WJRestrictedInput, {
	/**
	 * initialize
	 *
	 * Initialize this WJMultivalueInput
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJMultivalueInput
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJMultivalueInput";
	}
});
/**
 * WJGroup
 *
 * A group of components
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
WJGroup = Class.create(WJComponent, {
	/**
	 * 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.region = "center";
		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;
	}
});
/**
 * WJText
 *
 * Textbox input component
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJText = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJText
	 *
	 * @since Mon Jul 28 2008
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJText";
	}
});
/**
 * 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, {
	/**
	 * 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;
	}
});
/**
 * WJTab
 *
 * A tab group
 *
 * @since Wed Sep 10 2008 (existed before, but completely reimplemented the class)
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJTab = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJTab
	 *
	 * @since Wed Sep 10 2008
	 * @access public
	 * @return WJTab
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJTab";
	},

	/**
	 * setTitle
	 *
	 * Sets the title of this tab (if represented by a dojo TabContainer / ContentPane)
	 *
	 * @since Mon Sep 15 2008
	 * @access public
	 * @param string title
	 * @return void
	 **/
	setTitle: function(title) {
		if (this.htmlelement && this.htmlelement["class"] == "dijitContentPane") {
			this.htmlelement.controlButton.containerNode.innerHTML = title;
		}
	}
});
/**
 * WJFile
 *
 * Component to select files (ie browse or interface to filemanager)
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJFile = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJFile
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJFile
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJFile";
	}
});
/**
 * WJImage
 *
 * Component to select an image
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJImage = Class.create(WJFile, {
	/**
	 * initialize
	 *
	 * Initialize this WJImage
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJImage
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJImage";
		this.changed = false;
	},

	/**
	 * valueChanged
	 *
	 * Returns the changed variable 
	 *
	 * @since Mon Jun 15 2009
	 * @access public
	 * @return boolean
	 **/
	valueChanged: function() {
		return this.changed;
	},

	/**
	 * selectImage
	 *
	 * Sets the value of this component
	 *
	 * @since Mon Dec 29 2008
	 * @access public
	 * @param string image
	 * @param string path
	 * @param integer section
	 * @return void
	 **/
	selectImage: function(image, path, section) {
		this.changed = true;
		this.setValue("/var/mediamanager/images/" + path + "/" + image);
		this.showThumbnail();
	},
		
	/**
	 * showThumbnail
	 *
	 * Shows the thumbnail for the current value
	 *
	 * @since Mon Sep 28 2009
	 * @access public
	 * @return void
	 **/
	showThumbnail: function() {				 
		if ( $(this.htmlelement.id + "_thumbnail") ) {
			var src = $(this.htmlelement.id + "_thumbnail").src;
			var basepath = src.match(/(\/images\/thumbs(\/__[^\/]*)*)\/?/);
			if (basepath) {
				$(this.htmlelement.id + "_thumbnail").src = basepath[1] + this.value;
			}
			else {
				 // make a (small) thumbnail anyway
				$(this.htmlelement.id + "_thumbnail").src = "/images/thumbs/__lw50h50" + this.value;
			}
			
			if ($(this.htmlelement.id + "_thumbnail").up(".aeroplane_nothumb") ) {
				$(this.htmlelement.id + "_thumbnail").up(".aeroplane_nothumb").removeClassName("aeroplane_nothumb");
			}
		}
	},

	/**
	 * setValue
	 *
	 * Sets the component's value from script
	 *
	 * @since Thu Feb 11 2010
	 * @access public
	 * @param string val
	 * @return void
	 **/
	setValue: function(val) {
		this.value = val;
		if (this.htmlelement.type == "hidden") {
			this.htmlelement.value = this.value;
		}
	},

	/**
	 * getValue
	 *
	 * Returns the current image value
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @return string
	 **/
	getValue: function() {
		if (this.htmlelement) {
			if (this.htmlelement.type == "file") {
				try {
					var body = this.htmlelement.up().previous("iframe").contentWindow.document.body;
					if (!body) {
						return this.value;
					}
					if (body.textContent) {
						var obj = body.textContent.evalJSON(true);
					}
					else {
						var obj = body.innerHTML.stripTags().replace(/\r\n/g, "").evalJSON(true);
					}
					if (obj.newname) {
						this.value = obj.newname;
					}
					else if (obj[0] && obj[0].newname) {
						this.value = "/var/mediamanager/images/" + document.forms[this.id + "_form"]["path"].value + "/" + obj[0].newname;
					}
				}
				catch (e) {
					if (e.name != "SyntaxError") {  // ignore when badly formed JSON string ends in an Exception, else rethrow
						throw e;
					}
				}
			}
			else if (this.htmlelement.type == "hidden") {
				this.value = this.htmlelement.value;
			}
		}
		return this.value;
	},

	/** 
 	 * uploadSingleFile 
 	 * 
 	 * Uploads one file using an iframe 
 	 * 
 	 * @since Wed Mar 04 2009 
 	 * @access public 
 	 * @param event event 
 	 * @return void 
 	 **/ 
 	uploadSingleFile: function(event) { 
		if (this.htmlelement.up().previous("iframe") ) {
			this._loadObserver = this.handleResponse.bindAsEventListener(this);
			this.htmlelement.up().previous("iframe").observe("load", this._loadObserver);
		}
 		document.forms[this.id + "_form"].submit(); 
 	},

	/**
	 * handleResponse
	 *
	 * Acts on the response of the iframe
	 *
	 * @since Mon May 18 2009
	 * @access public
	 * @param Event event
	 * @return 
	 **/
	handleResponse: function(event) {
		this.changed = true;
		this.htmlelement.up().previous("iframe").stopObserving("load", this._loadObserver);
		var img = this.htmlelement.previous("img");
		if (!img) {
			var img = new Image();
			$(img);
			this.htmlelement.up().insertBefore(img, this.htmlelement);
			this.htmlelement.up().insertBefore(new Element("br"), this.htmlelement);
		}
		img.src = "/images/thumbs/__lw50h50" + this.getValue();
		img.show();
		try {
			this.htmlelement.value = ""; // clearing the value is allowed in FF
		}
		catch (e) {}
	},

	/** clearImage
	 * 
	 * Clears this WJImage (ie. sets it value to empty
	 *
	 * @since Thu Jan 28 2010
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	clearImage: function(event) {
		this.setValue("");
		this.changed = true;
		this.showThumbnail();
	},

	/**
	 * _activateSelectOptions
	 *
	 * Activates the options to select a file: direct upload, download or select from mediamanager
	 *
	 * @since Mon Sep 28 2009
	 * @access protected
	 * @return void
	 **/
	_activateSelectOptions: function() {
		if ($("clear_" + this.htmlelement.id) ) {
			$("clear_" + this.htmlelement.id).observe("click", this.clearImage.bind(this) );
		}
		if ($("upload_" + this.htmlelement.id) ) {
			// activate direct upload
			$("upload_" + this.htmlelement.id).observe("change", this.directUpload.bind(this) );
		}
		if ($("downloadbutton_" + this.htmlelement.id) ) {
			// activate direct download from URL
			$("downloadbutton_" + this.htmlelement.id).observe("click", this.directDownload.bind(this) );
		}
		
		if (dijit.byId("mediamanager_" + this.htmlelement.id) ) {
			// activate mediamanager
			dijit.byId("mediamanager_" + this.htmlelement.id).setObserver(function() {
				aeroplane.openApplication("Mediamanager", this.selectImage.bind(this), {section: WJMediamanagerInterface.IMAGES, path: "."} );
			}.bind(this) );
		}
	},

	/**
	 * directDownload
	 *
	 * Uses the mediamanager to download a file from a url to the mediamanager's downloads directory and selects it in WJImage
	 *
	 * @since Mon Sep 28 2009
	 * @access public
	 * @return void
	 **/
	directDownload: function() {
		var fileurl = $("download_" + this.htmlelement.id).value;
		if (fileurl == "") {
			WJWindow.alert("Please supply an URL to download the image from.", function() {}, aeroplane.translateGui);
		}
		else {
			var url = new WJUrl({}, "/index.php");
			url.setCt("wmdynamic");
			url.addParameter("module", "Wmmediamanager");
			url.addParameter("type", "json");
			url.addParameter("uploadsection", "2100");
			var spin = new WJSpin();		
			spin.update(url, "Wmmediamanager", "directdownload", {fileurl: fileurl}, [this.directDownloadResponse.bind(this)]);
		}
	},

	/**
	 * directDownloadResponse
	 *
	 * Handles the mediamanager's response to downloading a file
	 *
	 * @since Mon Sep 28 2009
	 * @access public
	 * @return void
	 **/
	directDownloadResponse: function(response) {
		this.setValue(response[0]);
		this.changed = true;
		this.showThumbnail();
		$("frame_" + this.htmlelement.id).stopObserving("load", this._loadObserver);
	},
	
	/**
	 * directUploadResponse
	 *
	 * Handles the mediamanager's response to uploading a file
	 *
	 * @since Mon Sep 28 2009
	 * @access public
	 * @return void
	 **/
	directUploadResponse: function() {
		var iframedoc = ($("frame_" + this.htmlelement.id).contentDocument || $("frame_" + this.htmlelement.id).contentWindow.document);
		if (iframedoc.body.textContent) {
			var data = iframedoc.body.textContent.evalJSON(true);
		}
		else {
			var data = iframedoc.body.innerHTML.stripTags().replace(/\r\n/g, "").evalJSON(true);
		}
		this.setValue(data[0]);
		this.changed = true;
		this.showThumbnail();
	},

	/**
	 * directUpload
	 *
	 * Uploads the selected file to the mediamanager's uploads directory and selects it in the WJImage (upon success)
	 *
	 * @since Mon Sep 28 2009
	 * @access public
	 * @return void
	 **/
	directUpload: function() {
		if ($("frame_" + this.htmlelement.id) ) {
			this._loadObserver = this.directUploadResponse.bindAsEventListener(this);
			$("frame_" + this.htmlelement.id).observe("load", this._loadObserver);
			$("upload_" + this.htmlelement.id).up("form").submit();
		}
	},

	/**
	 * setHtmlelement
	 *
	 * Attaches the open mediamanager event to the wjbutton
	 *
	 * @since Mon Dec 29 2008
	 * @access public
	 * @param element elem
	 * @return void
	 **/
	setHtmlelement: function($super, elem) {
		if (dijit.byId("mediamanager_" + elem.id) ) {
			$super(elem);
			this._activateSelectOptions();
		}
		else if (elem.type == "file") {
			$super(elem);
			this.htmlelement.observe("change", this.uploadSingleFile.bindAsEventListener(this) );
		}
		else {
			this.setHtmlelement.bind(this, elem).delay(0.1);
		}
	}
});
/**
 * WJButtonComponent
 *
 * Not named WJButton as that name was already taken
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJButtonComponent = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJButtonComponent
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJButtonComponent
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJButtonComponent";
	},

	/**
	 * disable
	 *
	 * Disables this button
	 *
	 * @since Fri Feb 13 2009
	 * @access public
	 * @return void
	 **/
	disable: function() {
		if (dijit.byId(this.htmlelement.id) ) {
			dijit.byId(this.htmlelement.id).button.disable();
		}
		else {
			this.disable.bind(this).delay(0.25);
		}
	},
	
	/**
	 * enable
	 *
	 * Enables this button
	 *
	 * @since Fri Feb 13 2009
	 * @access public
	 * @return void
	 **/
	enable: function() {
		if (dijit.byId(this.htmlelement.id) ) {
			dijit.byId(this.htmlelement.id).button.enable();
		}
		else {
			this.disable.bind(this).delay(0.25);
		}
	}
});
/**
 * WJAccordion
 *
 * A accordion group
 *
 * @since Mon Oct 13 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 * @todo change baseclass to something that both WJTab and WJAccordion extend from
 **/
var WJAccordion = Class.create(WJTab, {
	/**
	 * initialize
	 *
	 * Initialize this WJAccordion
	 *
	 * @since Wed Sep 10 2008
	 * @access public
	 * @return WJTab
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJAccordion";
	}
});
/**
 * WJAlbumComponent is a WJComponent to select multiple images / videos and set some properties on them
 *
 * @since Thu Apr 23 2009
 * @author Ron Rademaker
 **/
var WJAlbumComponent = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJAlbumComponent
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJAlbumComponent";
		this.value = {};
		this.counter = 0;
	},

	/**
	 * getValue
	 *
	 * Creates an array with objects describing the entries in the album component
	 *
	 * @since Fri May 01 2009
	 * @access public
	 * @param Array
	 * @return void
	 **/
	getValue: function() {
		var entries = this.htmlelement.select(".wjalbumcomponententry");
		var value = new Array();
		entries.each(function(entry) {
			if (entry.id != "wjalbumcomponententry_template") {
				var inputs = entry.select("input");
				var title = "";
				for (var i = 0; i < inputs.length; i++) {
					if ($(inputs[i]).readAttribute("name") && $(inputs[i]).readAttribute("name").endsWith("title") ) {
						title = $(inputs[i]).getValue();
					}
				}
				var albumimage = {imgsrc: entry.down("img").src, title: title, description: tinymce.EditorManager.get(entry.down("textarea").id).getContent() };
				this.push(albumimage);
			}

		}.bind(value) );

		return value;
	},

	/**
	 * addAlbumImage
	 *
	 * Adds an albumimage (don't let the name albumimage deceive you, it can also be a video) to the component
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @param array components
	 * @return void
	 **/
	addAlbumImage: function(components) {
		var albumimage = []
		components.each(function(component, s) {
			if ( (s.id == "user") || (s.id == "added") ) {
				s.componentType = "WJComponent";
			}
			s.id = s.id + "_" + component.counter;
			this.push(s);
		}.bind(albumimage, this) );
		this.value["albumimage_" + this.counter] = albumimage;
		this.counter++;
	},

	/**
	 * setHtmlelement
	 *
	 * Sets this album component's html element, attaches events to its buttons
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @param htmlelement element
	 * @return void
	 **/
	setHtmlelement: function($super, element) {
		$super(element);
		element.select(".aeroplane_wjalbumcomponent_removebutton").each(function(button) {
			this.addRemoveButtonListener(button);
		}.bind(this) );
		element.select("textarea").each(function(area) {
			tinyMCE.execCommand("mceAddControl", true, area.id);
		});

		this.addAddButtonListener("addbutton_" + element.id);
	},

	/**
	 * addAddButtonListener
	 *
	 * Adds the listener for adding new files to the add button
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @param string buttonid
	 * @return void
	 **/
	addAddButtonListener: function(buttonid) {
		if (dijit.byId(buttonid) ) {
			dijit.byId(buttonid).setObserver(this.openMediamanager.bind(this) );
		}
		else {
			this.addAddButtonListener.bind(this, buttonid).delay(0.1);
		}
	},

	/**
	 * openMediamanager
	 *
	 * Opens the media manager to select multiple files
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @return void
	 **/
	openMediamanager: function() {
		aeroplane.openApplication("Mediamanager", this.selectMedia.bind(this), {multiple: true} );
	},

	/**
	 * selectMedia
	 *
	 * Adds the selected media to the media album
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @param array media
	 * @return void
	 **/
	selectMedia: function(media) {
		var counter = this.htmlelement.select(".wjalbumcomponententry").length + 1;
		for (var i = 0; i < media.length; i++) {
			var mediaElem = $("wjalbumcomponententry_template").cloneNode(true);
			var fileaddition = (WJMediamanagerInterface.getSectionDir(media[i].section) == "videos") ? ".png" : "";
			mediaElem.id = null;
			mediaElem.down("img").src = mediaElem.down("img").src + "/var/mediamanager/" + WJMediamanagerInterface.getSectionDir(media[i].section) + "/" + media[i].path + "/" + media[i].file + fileaddition;
			mediaElem.setStyle({"display": "block"});
			mediaElem.down(".aeroplane_wjalbumcomponent_removebutton").setAttribute("dojoType", "aeroplanedijit.WJButton");
			mediaElem.down(".aeroplane_wjtext").down("input").setAttribute("dojoType", "dijit.form.TextBox");
			mediaElem.down("textarea").addClassName("aeroplane_wjrichtext");
			mediaElem.down("textarea").id = "wjrichtext_" + counter;
			this.htmlelement.insert({top: mediaElem});
			dojo.parser.parse(mediaElem);
			tinymce.EditorManager.execCommand("mceAddControl", false, "wjrichtext_" + counter);
			counter++;
		}
	},

	/**
	 * addRemoveButtonListener
	 *
	 * Adds a listener to the remove button
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @param htmlelement button
	 * @return void
	 **/
	addRemoveButtonListener: function(button) {
		if (dijit.byId(button.id) ) {
			dijit.byId(button.id).setObserver(function(e) {
				button.up(".wjalbumcomponententry").remove();
			}.bindAsEventListener(button) );
		}
		else {
			this.addRemoveButtonListener.bind(this, button).delay(0.1);
		}
	}
});
/**
 * WJCheckbox
 *
 * Represents a group of checkboxes
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJCheckbox = Class.create(WJMultivalueInput, {
	/**
	 * initialize
	 *
	 * Initialize this WJCheckbox
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJCheckbox
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJCheckbox";
	},
	
	/**
	 * getValue
	 *
	 * Returns the value if the htmlelement is checked
	 *
	 * @since Fri Jan 02 2009
	 * @access public
	 * @return string
	 **/
	getValue: function($super) {
		if (this.htmlelement.tagName.toUpperCase() == "INPUT") {
			if (this.htmlelement.checked) {
				return $super();
			}
		}
		else {
			var checkboxes = this.htmlelement.select("input");
			this.value = [];
			for (var i = 0; i < checkboxes.length; i++) {
				if (checkboxes[i].checked) {
					this.value.push(checkboxes[i].value);
				}
			}
			return this.value;
		}
	}
});

/**
 * WJCloseableTab is a WJTab the can be closed 
 *
 * @since Fri Sep 12 2008
 * @author Ron Rademaker
 **/
var WJCloseableTab = Class.create(WJTab, {
	/**
	 * initialize
	 *
	 * Initialize this WJCloseableTab
	 *
	 * @since Fri Sep 12 2008
	 * @access public
	 * @return WJCloseableTab
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJCloseableTab";
	}
});
/**
 * WJConditionalGroup
 *
 * A group of components conditionally shown
 *
 * @since Fri Nov 06 2009
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
WJConditionalGroup = Class.create(WJGroup, {
	/**
	 * initialize
	 *
	 * Initializes a new WJConditionalGroup
	 *
	 * @since Fri Nov 06 2009
	 * @access public
	 * @param string id
	 * @return void
	 **/
	initialize: function($super, id, testid, testvalue) {
		$super(id);
		this.componentType = "WJConditionalGroup";
		this.testId = testid;
		this.testValue = testvalue;
	},

	/**
	 * setHtmlelement
	 *
	 * Sets the html element for this component
	 *
	 * @since Fri Nov 06 2009
	 * @access public
	 * @param htmlelement element
	 * @return void
	 **/
	setHtmlelement: function($super, element, gui) {
		$super(element);
		this.gui = gui;
		this.updateDisplay.bind(this).defer(); // use defer to finish building the gui before hiding the group (without the defer a WJImage - select image from mediamanager button won't render well)
		this.addChangeObserverToTestId.bind(this).defer(); // use defer to let WJGui finish coupling before adding the listener
	},

	/**
	 * addChangeListenerToTestId
	 *
	 * Adds an onchange listener to the component with testid
	 *
	 * @since Fri Nov 06 2009
	 * @access public
	 * @return void
	 **/
	addChangeObserverToTestId: function() {
		this.gui.getComponentForField(this.testId).observe("change", this.updateDisplay.bind(this) );
	},
	
	/**
	 * getValue
	 *
	 * Gets the value indicating if the group's condition is met
	 *
	 * @since Fri Nov 06 2009
	 * @access public
	 * @return void
	 **/
	getValue: function($super) {
		if (this.gui) {
			return this.gui.getComponentForField(this.testId).getValue(true);
		}
		else { 
			return $super();
		}
	},


	/**
	 * updateDisplay
	 *
	 * Updates the display style of the htmlelement
	 *
	 * @since Fri Nov 06 2009
	 * @access public
	 * @return void
	 **/
	updateDisplay: function() {				
		if (this.getValue() == this.testValue) {
			this.htmlelement.setStyle({display: "block"});
		}
		else {
			this.htmlelement.setStyle({display: "none"});
		}
	}
});
/**
 * WJDate
 *
 * Component to enter dates
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJDate = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initializes this WJDate
	 *
	 * @since Tue Jul 29 2008
	 * @access public
	 * @param integer id
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJDate";
	},

	/**
	 * getValue
	 *
	 * Gets the value as a string instead of Date object (which is the dijit widget's default)
	 *
	 * @since Tue Aug 05 2008
	 * @access public
	 * @return void
	 **/
	getValue: function($super) {
		var value = $super();
		if (value) {
			this.value = value.toString();
		}
		else {
			this.value = "";
		}
		return this.value;
	}
});
/**
 * WJDir
 *
 * Component to select a directory from the mediamanager
 *
 * @since Fri Feb 05 2010
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJDir = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJDir
	 *
	 * @since Fri Feb 05 2010
	 * @access public
	 * @return WJFile
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJDir";
		this.changed = false;
	},

	/**
	 * setHtmlelement
	 *
	 * Attaches the open mediamanager event to the wjbutton
	 *
	 * @since Fri Feb 05 2010
	 * @access public
	 * @param element elem
	 * @return void
	 **/
	setHtmlelement: function($super, elem) {
		$super(elem);
		
		if (dijit.byId(this.htmlelement.id) ) {
			// activate mediamanager
			dijit.byId(this.htmlelement.id).setObserver(function() {
				aeroplane.openApplication("Mediamanager", this.selectDir.bind(this), {section: WJMediamanagerInterface.IMAGES, path: ".", type: "dir"} );
			}.bind(this) );
		}
	},
	
	/**
	 * valueChanged
	 *
	 * Returns the changed variable 
	 *
	 * @since Mon Jun 15 2009
	 * @access public
	 * @return boolean
	 **/
	valueChanged: function() {
		return this.changed;
	},

	/**
	 * selectDir
	 *
	 * Sets the value of this component
	 *
	 * @since Fri Feb 05 2010
	 * @access public
	 * @param string dir
	 * @param integer section
	 * @return void
	 **/
	selectDir: function(dir, section) {
		if (section == WJMediamanagerInterface.IMAGES) {
			dir = "/var/mediamanager/images/" + dir;
		}
		else if (section == WJMediamanagerInterface.FILES) {
			dir = "/var/mediamanager/files/" + dir;
		}
		else if (section == WJMediamanagerInterface.VIDEOS) {
			dir = "/var/mediamanager/videos/" + dir;
		}

		this.changed = true;
		this.value = dir;
		this.htmlelement.up().down("label").update(this.value);
		this.htmlelement.value = this.value;
	}
		
});
/**
 * WJExtendibleGroup
 *
 * A group of components that can be cloned
 *
 * @since Fri Apr 24 2009
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Component
 **/
WJExtendibleGroup = Class.create(WJGroup, {
	/**
	 * 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 = "WJExtendibleGroup";
	},
	
	/**
	 * setHtmlelement
	 *
	 * Sets the html element for this component
	 *
	 * @since Thu May 14 2009
	 * @access public
	 * @param htmlelement element
	 * @return void
	 **/
	setHtmlelement: function($super, element) {
		$super(element);
		this._addRemoveListeners();
		this._addAddListener();
		this._toggleRemoveButtons();
	},

	/**
	 * _addRemoveListeners
	 *
	 * Adds listeners to the remove buttons
	 *
	 * @since Thu May 14 2009
	 * @access protected
	 * @return void
	 **/
	_addRemoveListeners: function(removebuttons) {
		var removebuttons = removebuttons || this.htmlelement.select("div.aeroplane_removewjextendiblegroupcomponent");
		removebuttons.invoke("observe", "click", this.removeItem.bindAsEventListener(this) );
	},

	/**
	 * _addAddListener
	 *
	 * Adds listeners to the add button
	 *
	 * @since Thu May 14 2009
	 * @access protected
	 * @return void
	 **/
	_addAddListener: function() {
		this.htmlelement.down("div.aeroplane_addwjextendiblegroupcomponent").observe("click", this.addItem.bindAsEventListener(this) );
	},

	/**
	 * removeItem
	 *
	 * Removes the item the remove button belongs to
	 *
	 * @since Thu May 14 2009
	 * @access public
	 * @param Event event
	 * @return Element
	 **/
	removeItem: function(event) {
		var element = event.element();
		var item = element.up("div.aeroplane_wjextendiblegroup_component").remove();
		this._toggleRemoveButtons();
		return item;
	},

	/**
	 * addItem
	 *
	 * Adds an item
	 *
	 * @since Thu May 14 2009
	 * @access public
	 * @param Event event
	 * @return Element
	 **/
	addItem: function(event) {
		var addbutton = this.htmlelement.down("div.aeroplane_addwjextendiblegroupcomponent");
		var item = addbutton.previous("div.aeroplane_wjextendiblegroup_component");
		var newItem = $(addbutton.up().insertBefore(item.cloneNode(true), addbutton) );
		this._resetFields(newItem);
		this._addRemoveListeners([newItem.down("div.aeroplane_removewjextendiblegroupcomponent") ] );
		this._toggleRemoveButtons();
		return newItem;
	},
	
	/**
	 * _resetFields
	 *
	 * Resets al fields in the given element
	 *
	 * @since Thu May 14 2009
	 * @access protected
	 * @param Element element
	 * @return void
	 **/
	_resetFields: function(element) {
		WJExtendibleGroup.cloneCounter++;
		element.select("label").each(function(label) {
			// TODO: link this to the correct field, instead of unsetting it
			if (label.htmlFor) {
				label.htmlFor = "";
			}
			else if (label["for"] ) {
				label["for"] = "";
			}
		});
		
		element.select("input, select, textarea").each(function(el) {
			el.name = el.name.replace(/^([^\[]*\[)[^\]]*(\].*)$/, "$1" + "new" + WJExtendibleGroup.cloneCounter + "$2");
			el.id += "new" + WJExtendibleGroup.cloneCounter;
			
			switch (el.tagName.toLowerCase() ) {
				case "input":
					switch (el.type.toLowerCase() ) {
						case "text":
							el.clear();
							break;
					}
					break;
				case "select":
					el.selectedIndex = 0;
					break;
				case "textarea":
					el.update("");
					break;
			}
		} );
	},

	/**
	 * _toggleRemoveButtons
	 *
	 * Toggles the visibility of the removebuttons based on the number of buttons available
	 *
	 * @since Thu May 14 2009
	 * @access protected
	 * @return void
	 **/
	_toggleRemoveButtons: function() {
		var removebuttons = this.htmlelement.select("div.aeroplane_removewjextendiblegroupcomponent");
		removebuttons.invoke("setStyle", {"visibility" : (removebuttons.length == 1 ? "hidden":"visible") });
	},

	/**
	 * getValue
	 *
	 * Gets the value of this component (just reads the fields and not the components.getValue, because new elements might be added)
	 *
	 * @since Thu May 14 2009
	 * @access public
	 * @return array
	 **/
	getValue: function() {
		this.value = this.htmlelement.select("input, select, textarea").inject({}, function(result, el) {
			var name = el.name;
			if (name == "") {
				name = el.id;
			}
			if (el.type == "radio") {
				if (!result[name] ) {
					result[name] = null;
				}
				if (el.checked) {
					result[name] = el.value;
				}
			}
			else {
				result[name] = el.getValue();
			}
			return result;
		});
		return this.value;
	}
});

/**
 * Static var to keep track of number of clones added (so unique names can be used)
 *
 * @since Thu May 14 2009
 * @access public
 * @var integer
 **/
WJExtendibleGroup.cloneCounter = 0;
/**
 * WJFileUploader
 *
 * Component to upload files
 *
 * @since Fri Oct 17 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJFileUploader = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJFileUploader
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @return WJFileUploader
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJFileUploader";
		this._totalSize = 0;
	},

	/**
	 * _initUploaderWidget
	 *
	 * Creates the uploader widget and sets it properties
	 *
	 * @since Fri Oct 17 2008
	 * @access protected
	 * @return void
	 **/
	_initUploaderWidget: function() {
		window.uploaderEvent = this.eventHandler.bind(this);

		this.uploader = new SWFObject("uploaderObject", "/lib/plugin/aeroplane/flash/uploader.swf", "100%", "100%", "transparent", {settings: Object.toJSON({eventHandler: "uploaderEvent", allowMultipleFiles: true, buttonImage: "/lib/wjgui/skins/lucid/images/contentbuttons.png", buttonText: "Bestanden zoeken", buttonTextStyle: {font: "Arial", fontSize: "12", color: "F40D75", fontWeight: "bold"} }) });
		swfloader.loadSWFObject(this.htmlelement, this.uploader);
	},

	/**
	 * eventHandler
	 *
	 * Handles the event coming from the uploader SWF
	 *
	 * @since Thu Mar 19 2009
	 * @access public
	 * @param string reference
	 * @param object event
	 * @return void
	 **/
	eventHandler: function(reference, event) {
		switch (event.type) {
			case "resize":
				this.htmlelement.setStyle({width: event.width, height: event.height});
				break;
			case "fileSelect":
				this.fileSelect(event);
				break;
			case "uploadCompleteData":
				this.markUploadComplete(event);
				break;
			case "uploadError":
			case "uploadCancel":
				this.markUploadCancel(event);
				break;
		}
	},

	/**
	 * setNextButton
	 *
	 * Sets the next button, ie the button that one can click to continue after uploading something, this button should remain inactive until the upload is complete
	 *
	 * @since Fri Feb 13 2009
	 * @access public
	 * @param dijit.widget button
	 * @return void
	 **/
	setNextButton: function(button) {
		this.nextbutton = button;
		this.nextbutton.disable();
	},

	/**
	 * markUploadCancel
	 *
	 * Marks an upload as cancelled
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	markUploadCancel: function(e) {
		$(e.id + "_inforow").down(".aeroplane_uploadcancelled").show();
		$(e.id + "_inforow").down(".aeroplane_uploadcancelled").title = "De upload werd geannulleerd";
		$(e.id + "_inforow").down("a.aeroplane_delete").hide();
		this.handleUploadCompleted(e.id);
	},

	/**
	 * handleUploadCompleted
	 *
	 * Handles a completed upload, adds a message when all are finished
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @param string id
	 * @return void
	 **/
	handleUploadCompleted: function(id) {
		delete this.files[id];

		if ($H(this.files).size() == 0) {
			this.componentdiv.down(".aeroplane_fileselector").down("div").hide();
			this.componentdiv.down(".aeroplane_fileselector").insert(new Element("h2").update("De upload is voltooid") );
			this.componentdiv.down("thead").down(".aeroplane_delete").firstDescendant().hide();
			this.uploadbutton.getButton().setStyle({visibility: "hidden"});
			aeroplane.loadingCursor(false);
		}
	},

	/**
	 * markUploadComplete
	 *
	 * Marks an upload as completed
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	markUploadComplete: function(e) {
		$(e.id + "_inforow").down("a.aeroplane_delete").hide();
		var finfo = e.data.evalJSON()[0];
		WJDebugger.log(WJDebugger.NOTICE, "Marking '%s' as uploaded", e.id, finfo);

		if (finfo.error == 0) {
			$(e.id + "_inforow").down(".aeroplane_complete").show();
			if (finfo.errormessage) {
				$(e.id + "_inforow").down(".aeroplane_complete").title = finfo.errormessage;
			}
			if (this.nextbutton) {
				this.nextbutton.enable();
			}
		}
		else {
			$(e.id + "_inforow").down(".aeroplane_error").show();
			if (finfo.errormessage) {
				$(e.id + "_inforow").down(".aeroplane_error").title = finfo.errormessage;
			}
		}
		this.handleUploadCompleted(e.id);
	},

	/**
	 * fileSelect
	 *
	 * Callback for selecting files
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	fileSelect: function(e) {
		this._updateListing(e.fileList);
	},

	/**
	 * _updateListing
	 *
	 * Creates a nice looking listing
	 *
	 * @since Mon Feb 2 2009
	 * @access protected
	 * @param Object files
	 * @return void
	 **/
	_updateListing: function(files) {
		this.componentdiv.down("thead").hide();
		if (this.uploadbutton) {
			this.uploadbutton.getButton().setStyle({visibility: "visible"});
		}
		this.componentdiv.down("tbody").update("");
		this.componentdiv.down(".aeroplane_wjfileuploader_tablewrapper").setStyle({"height": ($(this.componentdiv.parentNode).getHeight() - 100) + "px", "overflow-y": "auto"});
		this.componentdiv.down(".aeroplane_total").update("");
		aeroplane.windowmanager.cleanUpDijitWidgets();
		this._totalSize = 0;
		this.files = files;

		var filescollection = [];
		for (var file in this.files) {
			filescollection.push(file);
		}
		if (filescollection.length === 0) {
			this.componentdiv.down(".aeroplane_wjfileuploader_tablewrapper").setStyle({"height": 0});
		}
		filescollection.reverse().each(function(file) {
			this._handleFileSelect(this.files[file] );
		}.bind(this) );

		if ($H(this.files).size() > 0) {
			this.componentdiv.siblings().invoke("remove");
			this.componentdiv.down(".aeroplane_total").update("Totaal"+ ": <strong>" + this._formatFileSize(this._totalSize) + "</strong>");
			this.componentdiv.down("thead").show();
			$(this.uploader.getAttribute("swfname") ).setTextStyle(Object.toJSON({font: "Arial", fontSize: "12", color: "01A1C1", fontWeight: "normal", textDecoration: "underline", cursor: "pointer"}) );
			$(this.uploader.getAttribute("swfname") ).setText("Bestanden toevoegen");
			$(this.uploader.getAttribute("swfname") ).hideButtonImage();

			this._showUploadButton();
		}
		else {
			if (this.uploader) {
				$(this.uploader.getAttribute("swfname") ).setTextStyle(Object.toJSON({font: "Arial", fontSize: "12", color: "F40D75", fontWeight: "bold", textDecoration: "none", cursor: "default"}) );
				$(this.uploader.getAttribute("swfname") ).setText("Bestanden zoeken");
				$(this.uploader.getAttribute("swfname") ).showButtonImage();
				this.uploadbutton.getButton().setStyle({visibility: "hidden"});
			}
		}
	},

	/**
	 * removeFile
	 *
	 * Removes a file from the uploader and the listing
	 *
	 * @since Mon Feb 2 2009
	 * @access public
	 * @param string fileID
	 * @return void
	 **/
	removeFile: function(fileID) {
		WJDebugger.log(WJDebugger.NOTICE, "Removing file '%s'", fileID);
		var files = {};
		for (var id in this.files) {
			if (id != fileID) {
				files[id] = this.files[id];
			}
		}
		$(this.uploader.getAttribute("swfname") ).removeFile(fileID);
		this._updateListing(files);
	},

	/**
	 * _showUploadButton
	 *
	 * Shows the upload button if there are files selected
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @return void
	 **/
	_showUploadButton: function() {
		if (!this.uploadbutton) {
			this.uploadbutton = new WJButton("Upload starten", this.startUpload.bind(this), false,
			this.componentdiv.down("div.aeroplane_start") );
		}
		this.uploadbutton.getButton().setStyle({visibility: "visible"});
	},

	/**
	 * startUpload
	 *
	 * Uploads all currently selected files
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @return void
	 **/
	startUpload: function() {
		aeroplane.loadingCursor(true);
		var cookie = new WJCookie();
		var sessionid = cookie.get("PHPSESSID", false);
		this.uploadInfo["PHPSESSID"] = sessionid;

		WJDebugger.log(WJDebugger.INFO, "Upload files", this.uploadInfo, this.files);
		$(this.uploader.getAttribute("swfname") ).uploadAll("/index.php", "POST", this.uploadInfo);
	},

	/**
	 * setUploadInformation
	 *
	 * Sets the information the button requires to do the actual upload
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @param object info
	 * @return void
	 **/
	setUploadInformation: function(info) {
		this.uploadInfo = info;
	},

	/**
	 * _handleFileSelect
	 *
	 * Adds a file to the list of selected files
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @param object file
	 * @return void
	 **/
	_handleFileSelect: function(file) {
		WJDebugger.log(WJDebugger.NOTICE, "Selecting file", file);
		if (!this.pastetable) {
			this.pastetable = new Element("table");
		}
		var info = info = {id: file.id, file: file.name, size: this._formatFileSize(file.size) };
		this.pastetable.update(this.rowtemplate.evaluate(info) );
		$A(this.pastetable.rows).each(function(row) {
			this.componentdiv.down("tbody").appendChild(row);
		}, this);

		this.componentdiv.select("a.aeroplane_delete").last().observe("click", function (event, fileID) {
 			this.removeFile(fileID);
		}.bindAsEventListener(this, file.id) );
		dojo.parser.parse(this.componentdiv);

		this._totalSize += file.size;
	},

	/**
	 * _formatFileSize
	 *
	 * Formats the filesize into something with Kb, Mb etc.
	 *
	 * @since Fri Oct 17 2008
	 * @access protected
	 * @param integer bytesize
	 * @return string
	 **/
	_formatFileSize: function(bytesize) {
		if (bytesize >= 1073741824) { // 1024 ^ 3
			bytesize = (Math.round( (bytesize * 100) / 1073741824) / 100 ) + " " + "GB";
		}
		else if (bytesize >= 1048576) { // 1024 ^ 2
			bytesize = (Math.round( (bytesize * 100) / 1048576) / 100) + " " + "MB";
		}
		else if (bytesize >= 1024) {
			bytesize = (Math.round( (bytesize * 100) / 1024) / 100) + " " + "KB";
		}
		else {
			bytesize = bytesize + " " + "b";
		}
		return bytesize;
	},

	/**
	 * alternativeUpload
	 *
	 * Performs an alternative upload from the browse button
	 *
	 * @since Wed Mar 18 2009
	 * @access public
	 * @return void
	 **/
	alternativeUpload: function() {
		this.alternativeUploader.form.submit();
		$(this.alternativeUploader.form.target).observe("load", function() {
			this.nextbutton.enable();
			this.componentdiv.down(".aeroplane_fileselector").down("div").hide();
			this.componentdiv.down(".aeroplane_fileselector").insert(new Element("h2").update("De upload is voltooid") );
		}.bind(this) );
	},

	/**
	 * setHtmlelement
	 *
	 * Sets the html element for this component
	 * Inits the YUI uploader component
	 *
	 * @since Fri Oct 17 2008
	 * @access public
	 * @param htmlelement element
	 * @return void
	 **/
	setHtmlelement: function($super, element) {
		$super(element);

		this.componentdiv = this.htmlelement.up(".aeroplane_wjfileuploader")

		var templaterows = this.componentdiv.select("tr.aeroplane_listingtemplate");
		this.rowtemplate = new Template(templaterows.collect(function(row) {
			return "<tr id='" + row.id + "row' class='" + row.className + "'>" + row.innerHTML + "</tr>";
		} ).join("") );
		templaterows.invoke("remove");

		this._updateListing({});
		this._initUploaderWidget();

		if ($("alternative_" + element.id) ) {
			this.alternativeUploader = $("alternative_" + element.id);
			this.alternativeUploader.observe("change", this.alternativeUpload.bind(this) );
			if ($("alternativelink_" + element.id) ) {
				$("alternativelink_" + element.id).observe("click", function() {
					$("alternativediv_" + element.id).setStyle({"display": "block"});
				});
			}
		}
	}
});
/**
 * WJFlash
 *
 * Component to select a swf (from the mediamanager)
 *
 * @since Wed Feb 18 2009
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJFlash = Class.create(WJFile, {
	/**
	 * initialize
	 *
	 * Initialize this WJFlash
	 *
	 * @since Wed Feb 18 2009
	 * @access public
	 * @return WJImage
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJFlash";
		this.changed = false;
	},

	/**
	 * selectFlash
	 *
	 * Sets the value of this component
	 *
	 * @since Wed Feb 18 2009
	 * @access public
	 * @param string image
	 * @param string path
	 * @param integer section
	 * @return void
	 **/
	selectFlash: function(swf, path, section) {
		this.changed = true;
		this.value = "/var/mediamanager/flash/" + path + "/" + swf;
		if ( $(this.htmlelement.id + "_filename") ) {
			$(this.htmlelement.id + "_filename").update(this.value);
		}
	},

	/**
	 * getValue
	 *
	 * Returns the current flash value
	 *
	 * @since Wed Feb 18 2009
	 * @access public
	 * @return string
	 **/
	getValue: function() {
		if (this.value == null && $(this.htmlelement.id + "_filename") ) {
			this.value = $(this.htmlelement.id + "_filename").getTextContent();
		}
		return this.value;
	},
	
	/**
	 * valueChanged
	 *
	 * Returns the changed variable 
	 *
	 * @since Fri Sep 18 2009
	 * @access public
	 * @return boolean
	 **/
	valueChanged: function() {
		return this.changed;
	},

	/**
	 * setHtmlelement
	 *
	 * Attaches the open mediamanager event to the wjbutton
	 *
	 * @since Wed Feb 18 2009
	 * @access public
	 * @param element elem
	 * @return void
	 **/
	setHtmlelement: function($super, elem) {
		if (dijit.byId(elem.id) ) {
			$super(elem);
			dijit.byId(elem.id).setObserver(function() {
				aeroplane.openApplication("Mediamanager", this.selectFlash.bind(this), {section: 2500, path: "."} );
			}.bind(this) );
		}
		else {
			this.setHtmlelement.bind(this, elem).delay(0.1);
		}
	}
});
/**
 * WJFlashVideo
 *
 * Component to select an video (altered copy of WJImage)
 *
 * @since Fri Mar 19 2010
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Component
 **/
var WJFlashVideo = Class.create(WJFile, {
	/**
	 * initialize
	 *
	 * Initialize this WJFlashVideo
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @return WJFlashVideo
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJFlashVideo";
		this.changed = false;
	},

	/**
	 * valueChanged
	 *
	 * Returns the changed variable 
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @return boolean
	 **/
	valueChanged: function() {
		return this.changed;
	},

	/**
	 * selectVideo
	 *
	 * Sets the value of this component
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @param string video
	 * @param string path
	 * @param integer section
	 * @return void
	 **/
	selectVideo: function(video, path, section) {
		this.changed = true;
		this.setValue("/var/mediamanager/videos/" + path + "/" + video);
		this.showThumbnail();
	},
		
	/**
	 * showThumbnail
	 *
	 * Shows the thumbnail for the current value
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @return void
	 **/
	showThumbnail: function() {
		if ( $(this.htmlelement.id + "_thumbnail") || $(this.htmlelement.id.replace(/(.*)-.*/, "$1") + "_thumbnail") ) {
			var thumb = $(this.htmlelement.id + "_thumbnail") || $(this.htmlelement.id.replace(/(.*)-.*/, "$1") + "_thumbnail");
			var src = thumb.src;
			var basepath = src.match(/(\/images\/thumbs(\/__[^\/]*)*)\/?/);
			if (basepath) {
				thumb.src = basepath[1] + this.value + ".png";
			}
			else {
				 // make a (small) thumbnail anyway
				thumb.src = "/images/thumbs/__lw50h50" + this.value + ".png";
			}

			if (thumb.up(".aeroplane_nothumb") ) {
				thumb.up(".aeroplane_nothumb").removeClassName("aeroplane_nothumb");
		}
		}
	},

	/**
	 * setValue
	 *
	 * Sets the component's value from script
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @param string val
	 * @return void
	 **/
	setValue: function(val) {
		this.value = val;
		if (this.htmlelement.type == "hidden") {
			this.htmlelement.value = this.value;
		}
	},

	/**
	 * getValue
	 *
	 * Returns the current video value
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @return string
	 **/
	getValue: function() {
		if (this.htmlelement) {
			if (this.htmlelement.type == "file") {
				try {
					var body = this.htmlelement.up().previous("iframe").contentWindow.document.body;
					if (!body) {
		return this.value;
					}
					if (body.textContent) {
						var obj = body.textContent.evalJSON(true);
					}
					else {
						var obj = body.innerHTML.stripTags().replace(/\r\n/g, "").evalJSON(true);
					}
					if (obj.newname) {
						this.value = obj.newname;
					}
					else if (obj[0] && obj[0].newname) {
						this.value = "/var/mediamanager/videos/" + document.forms[this.id + "_form"]["path"].value + "/" + obj[0].newname;
					}
				}
				catch (e) {
					if (e.name != "SyntaxError") {  // ignore when badly formed JSON string ends in an Exception, else rethrow
						throw e;
					}
				}
			}
			else if (this.htmlelement.type == "hidden") {
				this.value = this.htmlelement.value;
			}
		}
		return this.value;
	},

	/**
 	 * uploadSingleFile 
 	 * 
 	 * Uploads one file using an iframe 
 	 * 
 	 * @since Fri Mar 19 2010
 	 * @access public 
 	 * @param event event 
 	 * @return void 
 	 **/ 
 	uploadSingleFile: function(event) { 
		if (this.htmlelement.up().previous("iframe") ) {
			this._loadObserver = this.handleResponse.bindAsEventListener(this);
			this.htmlelement.up().previous("iframe").observe("load", this._loadObserver);
		}
 		document.forms[this.id + "_form"].submit(); 
 	},

	/**
	 * handleResponse
	 *
	 * Acts on the response of the iframe
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @param Event event
	 * @return 
	 **/
	handleResponse: function(event) {
		this.changed = true;
		this.htmlelement.up().previous("iframe").stopObserving("load", this._loadObserver);
		var img = this.htmlelement.previous("img");
		if (!img) {
			var img = new Video();
			$(img);
			this.htmlelement.up().insertBefore(img, this.htmlelement);
			this.htmlelement.up().insertBefore(new Element("br"), this.htmlelement);
		}
		img.src = "/images/thumbs/__lw50h50" + this.getValue();
		img.show();
		try {
			this.htmlelement.value = ""; // clearing the value is allowed in FF
		}
		catch (e) {}
	},

	/** clearVideo
	 * 
	 * Clears this WJFlashVideo (ie. sets it value to empty
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	clearVideo: function(event) {
		this.setValue("");
		this.changed = true;
		this.showThumbnail();
	},

	/**
	 * _activateSelectOptions
	 *
	 * Activates the options to select a file: direct upload, download or select from mediamanager
	 *
	 * @since Fri Mar 19 2010
	 * @access protected
	 * @return void
	 **/
	_activateSelectOptions: function() {
		if ($("clear_" + this.htmlelement.id) ) {
			$("clear_" + this.htmlelement.id).observe("click", this.clearVideo.bind(this) );
		}
		if ($("upload_" + this.htmlelement.id) ) {
			// activate direct upload
			$("upload_" + this.htmlelement.id).observe("change", this.directUpload.bind(this) );
		}
		if ($("downloadbutton_" + this.htmlelement.id) ) {
			// activate direct download from URL
			$("downloadbutton_" + this.htmlelement.id).observe("click", this.directDownload.bind(this) );
		}
		
		if (dijit.byId("mediamanager_" + this.htmlelement.id) ) {
			// activate mediamanager
			dijit.byId("mediamanager_" + this.htmlelement.id).setObserver(function() {
				aeroplane.openApplication("Mediamanager", this.selectVideo.bind(this), {section: WJMediamanagerInterface.VIDEOS, path: "."} );
			}.bind(this) );
		}
	},

	/**
	 * directDownload
	 *
	 * Uses the mediamanager to download a file from a url to the mediamanager's downloads directory and selects it in WJFlashVideo
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @return void
	 **/
	directDownload: function() {
		var fileurl = $("download_" + this.htmlelement.id).value;
		if (fileurl == "") {
			WJWindow.alert("Please supply an URL to download the video from.", function() {}, aeroplane.translateGui);
		}
		else {
			var url = new WJUrl({}, "/index.php");
			url.setCt("wmdynamic");
			url.addParameter("module", "Wmmediamanager");
			url.addParameter("type", "json");
			url.addParameter("uploadsection", WJMediamanagerInterface.VIDEOS);
			var spin = new WJSpin();		
			spin.update(url, "Wmmediamanager", "directdownload", {fileurl: fileurl}, [this.directDownloadResponse.bind(this)]);
		}
	},

	/**
	 * directDownloadResponse
	 *
	 * Handles the mediamanager's response to downloading a file
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @return void
	 **/
	directDownloadResponse: function(response) {
		this.setValue(response[0]);
		this.changed = true;
		this.showThumbnail();
		$("frame_" + this.htmlelement.id).stopObserving("load", this._loadObserver);
	},
	
	/**
	 * directUploadResponse
	 *
	 * Handles the mediamanager's response to uploading a file
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @return void
	 **/
	directUploadResponse: function() {
		var iframedoc = ($("frame_" + this.htmlelement.id).contentDocument || $("frame_" + this.htmlelement.id).contentWindow.document);
		if (iframedoc.body.textContent) {
			var data = iframedoc.body.textContent.evalJSON(true);
		}
		else {
			var data = iframedoc.body.innerHTML.stripTags().replace(/\r\n/g, "").evalJSON(true);
		}
		this.setValue(data[0]);
		this.changed = true;
		this.showThumbnail();
	},

	/**
	 * directUpload
	 *
	 * Uploads the selected file to the mediamanager's uploads directory and selects it in the WJFlashVideo (upon success)
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @return void
	 **/
	directUpload: function() {
		if ($("frame_" + this.htmlelement.id) ) {
			this._loadObserver = this.directUploadResponse.bindAsEventListener(this);
			$("frame_" + this.htmlelement.id).observe("load", this._loadObserver);
			$("upload_" + this.htmlelement.id).up("form").submit();
		}
	},

	/**
	 * setHtmlelement
	 *
	 * Attaches the open mediamanager event to the wjbutton
	 *
	 * @since Fri Mar 19 2010
	 * @access public
	 * @param element elem
	 * @return void
	 **/
	setHtmlelement: function($super, elem) {
		if (dijit.byId("mediamanager_" + elem.id) ) {
			$super(elem);
			this._activateSelectOptions();
		}
		else if (elem.type == "file") {
			$super(elem);
			this.htmlelement.observe("change", this.uploadSingleFile.bindAsEventListener(this) );
		}
		else {
			this.setHtmlelement.bind(this, elem).delay(0.1);
		}
	}
});
/**
 * 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.
	}
});
/**
 * WJLinkButton
 *
 * Button component that should be displayed as a link
 *
 * @since Tue Dec 23 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJLinkButton = Class.create(WJButtonComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJLinkButton
	 *
	 * @since Tue Dec 23 2008
	 * @access public
	 * @return WJLinkButton
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJLinkButton";
	}
});
/**
 * WJLink
 *
 * A link component
 *
 * @since Mon Oct 13 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJLink = Class.create(WJGroup, {
	/**
	 * initialize
	 *
	 * Initialize this WJLink
	 *
	 * @since Mon Oct 13 2008
	 * @access public
	 * @return WJLink
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJLink";
		this.region = "top";
		this.group = 0; // WJLink has a lot of group properties but it isn't really a group. This makes sure it isn't treated as a group on the XSL level.
	}
});
/**
 * WJListbox
 *
 * A listbox is component to select multiple options from an option group. (A list where you use CTRL+click to add items to your selection)
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJListbox = Class.create(WJMultivalueInput, {
	/**
	 * initialize
	 *
	 * Initialize this WJListbox
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJListbox
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJListbox";
	}
});
/**
 * WJList
 *
 * Javascript component to sort data with (so it's an ordered list)
 *
 * @since Mon Sep 01 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJList = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJList
	 *
	 * @since Mon Sep 01 2008
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJList";
	}
});
/**
 * WJLongText
 *
 * Textarea component
 *
 * @since Mon Feb 01 2010
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJLongText = Class.create(WJText, {
	/**
	 * initialize
	 *
	 * Initialize this WJLongText
	 *
	 * @since Mon Feb 01 2010
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJLongText";
	}
});
/**
 * WJNormalButton
 *
 * Button component that should be displayed as an ordinary <button>
 *
 * @since Mon Dec 29 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJNormalButton = Class.create(WJButtonComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJNormalButton
	 *
	 * @since Mon Dec 29 2008
	 * @access public
	 * @return WJNormalButton
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJNormalButton";
	}
});
/**
 * WJOptionGroup
 *
 * Collection of options
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJOptionGroup = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJOptionGroup
	 *
	 * @since Mon Jul 21 2008
	 * @access public
	 * @return void
	 **/
	initialize: function() {
		this.options = new Array();
		this.componentType = "WJOptionGroup";
	},

	/**
	 * getOptions
	 *
	 * Gets the options in this group
	 *
	 * @since 
	 * @access public
	 * @return 
	 **/
	getOptions: function() {
		return this.options;
	},

	/**
	 * addOption
	 *
	 * Adds a WJOption
	 *
	 * @since 
	 * @access public
	 * @param WJOption option
	 * @return 
	 **/
	addOption: function(option) {
		this.options.push(option);
	}
});
/**
 * WJOption
 *
 * An option (for restricted input)
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJOption = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJOption
	 *
	 * @since Mon Jul 21 2008
	 * @access public
	 * @param string value
	 * @param WJLabel label
	 * @return WJOption
	 **/
	initialize: function(value, label) {
		this.label = label;
		this.value = value;
		this.componentType = "WJOption";
	}
});
/**
 * 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";
	}
});
/**
 * WJRadio
 *
 * Represents a group of radio buttons
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJRadio = Class.create(WJSinglevalueInput, {
	/**
	 * initialize
	 *
	 * Initialize this WJRadio
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJRadio
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJRadio";
		this.htmlelement = [];
	},

	/**
	 * setHtmlelement
	 *
	 * Sets the html element for this component, radio components are likely to have multiple elements
	 *
	 * @since Thu Oct 16 2008
	 * @access public
	 * @param htmlelement element
	 * @return void
	 **/
	setHtmlelement: function(element) {
		this.htmlelement.push(element);
	},
	
	/**
	 * getValue
	 *
	 * Gets the value of this component
	 *
	 * @since Thu Oct 16 2008
	 * @access public
	 * @return string
	 **/
	getValue: function(nochange) {
		var nochange = nochange || false;		  
		var oldvalue = this.value;
		for (var i = 0; i < this.htmlelement.length; i++) {
			if (this.htmlelement[i] && dijit.byId(this.htmlelement[i].id) && dijit.byId(this.htmlelement[i].id).getValue) {
				if (dijit.byId(this.htmlelement[i].id).checked) {
					this.value = dijit.byId(this.htmlelement[i].id).getValue();
				}
			}
			else if (this.htmlelement[i] ) {
				if (this.htmlelement[i].checked) {
					this.value = this.htmlelement[i].getAttribute("value");
				}
			}
		}
		if (nochange) {
			var newval = this.value;
			this.value = oldvalue;
			return newval;
		}
		else {
			return this.value;
		}
	}
});
/**
 * WJReview
 *
 * Review component
 *
 * @since Thu Jun 25 2009
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJReview = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJReview
	 *
	 * @since Thu Jun 25 2009
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJReview";
	}
});
/**
 * WJRichtext
 *
 * Rich text component
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJRichtext = Class.create(WJText, {
	/**
	 * initialize
	 *
	 * Initialize this WJRichtext
	 *
	 * @since Mon Jul 28 2008
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJRichtext";
	},

	/**
	 * setHtmlelement
	 *
	 * Sets the html element for this component and converts it to a tiny instance
	 *
	 * @since Mon May 18 2009
	 * @access public
	 * @param htmlelement element
	 * @return void
	 **/
	setHtmlelement: function($super, element) {
		$super(element);
		if (tinyMCE.editors[this.htmlelement.id]) { // remoove tiny if already loaded at this id (prevents tiny from disappearing when switching layouts and a rich text field gets an id that was already a tinymce field
			tinyMCE.execCommand("mceRemoveControl", false, this.htmlelement.id);
		}
		tinyMCE.execCommand("mceAddControl", true, this.htmlelement.id);
		this.originalContent = null;
		tinymce.EditorManager.get(this.htmlelement.id).onInit.add(function() {
			if (!this.originalContent) {
				this.originalContent = tinymce.EditorManager.get(this.htmlelement.id).getContent();
			}
		}.bind(this) );
	},

	/**
	 * valueChanged
	 *
	 * Checks if the current value of this richtext differs from the original value, considers an enter a whitespace (HTML does that too)
	 *
	 * @since Thu Aug 27 2009
	 * @access public
	 * @return boolean
	 **/
	valueChanged: function($super) {
		var usesTinyMCE = (typeof(tinyMCE) != "undefined") ? true : false;
		
		if (this.htmlelement && usesTinyMCE && tinymce.EditorManager.get(this.htmlelement.id) && tinymce.EditorManager.get(this.htmlelement.id).getContent) {
			if (this.originalContent == null) {
				return false;
			}
			else {
				return tinymce.EditorManager.get(this.htmlelement.id).getContent() != this.originalContent;
			}
		}
		else {
			return $super();
		}
  	},

	/**
	 * getValue
	 *
	 * Gets the value of this component
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @return string
	 **/
	getValue: function($super) {
		var usesTinyMCE = (typeof(tinyMCE) != "undefined") ? true : false;
		
		if (this.htmlelement && usesTinyMCE && tinymce.EditorManager.get(this.htmlelement.id) && tinymce.EditorManager.get(this.htmlelement.id).getContent) {
			this.value = tinymce.EditorManager.get(this.htmlelement.id).getContent();
		}
		else {
			this.value = $super();
		}
		return this.value;
	}
});
/**
 * WJRichtextLabel
 *
 * A label, the text that is usually found near an input component, with rich markup
 *
 * @since Thu Mar 5 2009
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Component
 **/
WJRichtextLabel = Class.create(WJLabel, {
	/**
	 * initialize
	 *
	 * Creates a new WJRichtextLabel
	 *
	 * @since Thu Mar 5 2009
	 * @access public
	 * @param string id
	 * @param string html (default: null)
	 * @return void
	 **/
	initialize: function($super, id, html) {
		$super(id);
		this.componentType = "WJRichtextLabel",
		this.html = html;
	},

	/**
	 * setLabel
	 *
	 * Setting a label on a label is unwanted in most cases, so this is overwritten to do nothing expect when really wanted 
	 *
	 * @since Wed Apr 01 2009
	 * @access public
	 * @param WJLabel label
	 * @param boolean force
	 * @return void
	 **/
	setLabel: function($super, label, force) {
		var force = force || false;
		if (force) {
			$super(label);
		}
	}
});
/**
 * WJSelect
 *
 * Select, dropdown, combobox component
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJSelect = Class.create(WJSinglevalueInput, {
	/**
	 * initialize
	 *
	 * Initialize this WJSelect
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJSelect
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJSelect";
	},

	/**
	 * getValue
	 *
	 * Returns the value of this component
	 *
	 * @since Wed Apr 29 2009
	 * @access public
	 * @return string
	 **/
	getValue: function($super, nochange) {
		var nochange = nochange || false;		  
		var oldvalue = this.value;
		if (this.htmlelement.options) {
			this.value = this.htmlelement.options[this.htmlelement.selectedIndex].value;
			this.htmlelement.value = this.value;
			if (nochange) {
				var newval = this.value;
				this.value = oldvalue;
				return newval;
			}
			else {
				return this.value;
			}
		}
		return $super(nochange);
	}
});
/**
 * WJSlider
 *
 * Slider component to select a value from an option group
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJSlider = Class.create(WJSinglevalueInput, {
	
	/**
	 * initialize
	 *
	 * Initialize this WJSlider
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJSlider
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJSlider";
	}
});
/**
 * WJTabGroup
 *
 * A group of components to display as tabs
 *
 * @since Wed Nov 19 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
WJTabGroup = Class.create(WJGroup, {
	/**
	 * initialize
	 *
	 * Initializes a new WJTabGroup
	 *
	 * @since Wed Nov 19 2008
	 * @access public
	 * @param string id
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJTabGroup";
	}
});
/**
 * WJTagCloud
 *
 * Tagcloud input component
 *
 * @since Tue Oct 21 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJTagCloud = Class.create(WJText, {
	/**
	 * initialize
	 *
	 * Initialize this WJTagCloud
	 *
	 * @since Tue Oct 21 2008
	 * @access public
	 * @return void
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJTagCloud";
	}
});
/**
 * WJThumbnail
 *
 * Component to show a thumbnail
 *
 * @since Tue Oct 21 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJThumbnail = Class.create(WJImage, {
	/**
	 * initialize
	 *
	 * Initialize this WJThumbnail
	 *
	 * @since Tue Oct 21 2008
	 * @access public
	 * @return WJThumbnail
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJThumbnail";
	}
});
/**
 * WJTime
 *
 * Time select component
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJTime = Class.create(WJComponent, {
	/**
	 * initialize
	 *
	 * Initialize this WJTime
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJTime
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJTime";
	}
});
/**
 * WJTree is a tree component. Used to organize other components in an explicit tree (they already are in a implicit tree!)
 *
 * @since Tue Aug 12 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJTree = Class.create(WJLabel, {
	/**
	 * initialize
	 *
	 * Creates a new WJTree
	 *
	 * @since 
	 * @access public
	 * @param string id
	 * @return 
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJTree";
	}
});
/**
 * WJUrltext
 *
 * Textbox for URL / link selection 
 *
 * @since Wed Jul 16 2008
 * @revision $Revision$
 * @author Ron Rademaker
 * @package Windmill.Javascript.Component
 **/
var WJUrltext = Class.create(WJText, {
	
	/**
	 * initialize
	 *
	 * Initialize this WJUrltext
	 *
	 * @since Fri Aug 22 2008
	 * @access public
	 * @return WJUrltext
	 **/
	initialize: function($super, id) {
		$super(id);
		this.componentType = "WJUrltext";
	}
});
/**
 * WJCBAction describes a CB functionality action
 *
 * @since Mon Dec 15 2008
 * @author Ron Rademaker
 **/
var WJCBAction = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJCBAction
	 *
	 * @since Mon Dec 15 2008
	 * @access public
	 * @param string name
	 * @return void
	 **/
	initialize: function(name, func, properties) {
		this.name = name;
		this.cbfunc = func;
		this.components = [];
		this.properties = properties || {update: true};
	},

	/**
	 * execute
	 *
	 * Collects all information from the action's components and does the right ajax call to perform the action
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @return void
	 **/
	execute: function(data) {
		var spin = new WJSpin();
		var url = new WJUrl({ct: "wmdynamic", dt: "wmcontentblock", module: "Wmcb", id: this.cbfunc.getId(), action: this.name, "wmtrigger[]": ["requestufts"], "uft[]": ["contentblock"]});
		this.components.each(function(s) {
			this.addParameter("args[" + s.getId() + "]", s.getValue() );
		}.bind(url) );
		for (var key in data) {
			url.addParameter(key, data[key]);
		}
		$(document.body).setStyle({"cursor": "wait"});
		if (this.properties.update) {
			var elem = this.cbfunc.getElement();
			elem.ajaxUpdateType = this.cbfunc.getUpdateMethod();
			spin.content(url, [elem, function() {$(document.body).setStyle({"cursor": "default"}); }], {"406": this.handleNotAcceptable.bind(this)});
		}
		else {
			spin.content(url, [function() {$(document.body).setStyle({"cursor": "default"}); }], {"406": this.handleNotAcceptable.bind(this)});
		}
	},

	/**
	 * handleNotAcceptable
	 *
	 * Handles a Not Acceptable response, usually required fields will be missing
	 *
	 * @since Thu Jan 29 2009
	 * @access public
	 * @return void
	 **/
	handleNotAcceptable: function() {
		$(document.body).setStyle({"cursor": "auto"});
		if (this.cbfunc.specifics) {
			this.cbfunc.specifics.handleNotAcceptable();
		}
	},

	/**
	 * addComponent
	 *
	 * Adds a component to this action
	 *
	 * @since Mon Dec 15 2008
	 * @access public
	 * @param string compname
	 * @param string comptype
	 * @param string compgroup
	 * @return void
	 **/
	addComponent: function(comptype, compname, compgroup) {
		comptype = comptype.substr(0, 3).toUpperCase() + comptype.substr(3);
		if (!window[comptype]) {
			comptype = "WJComponent";
		}

		var id = compname;
		if (!$(id) ) {
			id = compname + "_" + this.cbfunc.getId();
		}
		if (!$(id) && compgroup != undefined) {
			id = compgroup + "_" + compname + "_" + this.cbfunc.getId();
			compname = compgroup + "_" + compname;
		}

		if ($(id) ) {
			var comp = new window[comptype](compname);
			comp.setHtmlelement($(id) );
			this.components.push(comp);
		}
	}
});

/**
 * WJCBFunctionalityHistoryManager is a base class to base cb functionality specifics on when actions should be kept in a history manager to get back / forward / reload / bookmarks to work.
 *
 * @since Thu Apr 23 2009
 * @author Ron Rademaker
 **/
var WJCBFunctionalityHistoryManager = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJCBFunctionalityHistoryManager for func
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @param WJCBFunctionality func
	 * @return void
	 **/
	initialize: function(func) {
		this.func = func;

		if (!ProtoHistoryManager) {
			WJDebugger.log(WJDebugger.WARNING, "Unable to use history manager functionality without prototype.historyManager, will continue without");
		}
		else {
			this.historyKey = this.func.cb + "-" + this.func.id;
			this.historyManager = new ProtoHistoryManager();
			this.history  = this.historyManager.register(
				this.historyKey,
				[""],
				function(values) {
					this.recallAction(values[0]);
				}.bind(this),
				function(values) {
					if (Object.isString(values[0]) ) {
						return this.historyKey + "-" + values[0];
					}
					else {
						return this.historyKey + "-" + $H(values[0]).toQueryString();
					}
				}.bind(this),
				this.historyKey + '-(.+)');
			this.historyManager.start.bind(this.historyManager).delay(1.5); // todo move this (and the new on ProtoHistoryManager) to some other place so multiple cb functionalities can be registered
		}
	},

	/**
	 * recallAction
	 *
	 * Recalls the action described by query
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @param string query
	 * @return void
	 **/
	recallAction: function(query) {
		if (query == "") {
			return;
		}

		var query = query.toQueryParams();
		var action = query.action;
		delete query.action;
		this.func.callAction(action, query);
	},

	/**
	 * callAction
	 *
	 * Registers the action with the history manager
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @param string action
	 * @param object data
	 * @return void
	 **/
	callAction: function(action, data) {
		if (this.history) {
			data.action = action;
			this.history.setValue(0, data);
		}
	}
});
/**
 * WJCBFunctionality is the javascript class that implements the 'glue' that keeps everything concerning CB Functionality together
 *
 * @since Mon Dec 15 2008
 * @author Ron Rademaker
 **/
var WJCBFunctionality = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJCBFunctionality
	 *
	 * @since Mon Dec 15 2008
	 * @access public
	 * @param integer id
	 * @param string cb
	 * @param string htmlid
	 * @param object cbdata
	 * @return void
	 **/
	initialize: function(id, cb, htmlid, cbdata, collection) {
		this.id = id;
		this.element = $(htmlid);
		this.cb = cb;
		this.actions = {};
		this.cbdata = cbdata;
		this.collection = collection;
		this.updateMethod = "replaceElement";
	},

	/**
	 * createSpecifics
	 *
	 * Creates specific CB functionality, call this after registering actions, otherwise the historymanager will try to call actions before they're known (and that won't work)
	 *
	 * @since Thu Apr 23 2009
	 * @access public
	 * @return void
	 **/
	createSpecifics: function() {
		if (window["WJCBFunctionality" + this.cb]) {
			this.specifics = new window["WJCBFunctionality" + this.cb](this);
		}
	},

	/**
	 * getUpdateMethod
	 *
	 * Gets the update method to use, defaults to replaceElement
	 *
	 * @since Tue Apr 21 2009
	 * @access public
	 * @return string
	 **/
	getUpdateMethod: function() {
		return this.updateMethod;
	},

	/**
	 * getData
	 *
	 * Gets some data from the cbdata
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @param string key
	 * @return mixed
	 **/
	getData: function(key) {
		return this.cbdata[key];
	},

	/**
	 * setData
	 *
	 * Sets data in cbdata
	 * NOTE: this does not save ANY data
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @param string key
	 * @param mixed value
	 * @return void
	 **/
	setData: function(key, value) {
		this.cbdata[key] = value;
	},

	/**
	 * callAction
	 *
	 * Finds the requested action and calls its execute function
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @param string action
	 * @param object data
	 * @return void
	 **/
	callAction: function(action, data) {
		var data = data || {};
		if (data.isTrusted || Object.isFunction(data.stop) ) { // check to see if this is an event, for now
			data = {};
		}
		if (this.actions[action]) {
			this.actions[action].execute(data);
		}
		if (this.specifics && this.specifics.callAction) {
			this.specifics.callAction(action, data);
		}
	},

	/**
	 * getId
	 *
	 * Getter for the id
	 *
	 * @since Mon Dec 15 2008
	 * @access public
	 * @return integer
	 **/
	getId: function() {
		return this.id;
	},

	/**
	 * getElement
	 *
	 * Getter for the HTML element
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @return integer
	 **/
	getElement: function() {
		return this.element;
	},

	/**
	 * addAction
	 *
	 * Adds an action to the CB functionality
	 *
	 * @since Mon Dec 15 2008
	 * @access public
	 * @param WJCBAction action
	 * @return void
	 **/
	addAction: function(name, action) {
		this.actions[name] = action;
	}
});
/**
 * WJAlbumCB is the javascript that handles transition effects for the album Contentblock
 *
 * @since Mon Apr 20 2009
 * @author Ron Rademaker
 **/
var WJAlbumCB = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJAlbumCB
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param WJCBFunctionality func
	 * @return void
	 **/
	initialize: function(func, properties) {
		this.albumimages = new Array();
		this.current = "";
		this.effect = null;
		this.func = func;
		this.properties = properties || {};
		if (this.properties.slide) {
			this.viewNextAlbumImage.bind(this).repeat(properties.slide);
		}
	},

	/**
	 * setTransitionEffect
	 *
	 * Sets the transition effect to use
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param WJTransition effect
	 * @return void
	 **/
	setTransitionEffect: function(effect, properties) {
		var properties = properties || {};
		this.effect = effect;
		this.mediaTransition = new WJMediaTransition(properties);
		this.mediaTransition.setFromElement($(document.body).down(".contentblockalbum_view").down() );
		this.effect.init(this.mediaTransition, {});
	},

	/**
	 * registerImage
	 *
	 * Adds an image to the list of albumimages
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param string url
	 * @param string title
	 * @param string user
	 * @param string extrainfo
	 * @return void
	 **/
	registerImage: function(url, title, user, extrainfo) {
		this.albumimages.push({type: "image", url: url, title: title, user: user, extrainfo: extrainfo} );
	},

	/**
	 * registerVideo
	 *
	 * Adds an video to the list of albumimages
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param string url
	 * @param string title
	 * @param string user
	 * @return void
	 **/
	registerVideo: function(url, title, user, extrainfo) {
		this.albumimages.push({type: "video", url: url, title: title, user: user, extrainfo: extrainfo} );
	},

	/**
	 * setCurrentAlbumimage
	 *
	 * Sets the current albumimage url
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param string url
	 * @return void
	 **/
	setCurrentAlbumimage: function(url) {
		this.current = url;
		for (var i = 0; i < this.albumimages.length; i++) {
			if (this.albumimages[i].url == this.current) {
				for (var id in this.properties.update) {
					if ($(id) ) {
						$(id).update(this.albumimages[i][this.properties.update[id] ] );
					}
				}
			}
		}
	},

	/**
	 * viewAlbumimage
	 *
	 * Displays the albumimage with url
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param string url
	 * @return void
	 **/
	viewAlbumimage: function(url) {
		this.mediaTransition.setToImage(url);
		this.effect.transit();
		this.setCurrentAlbumimage(url);
		if (this.func.specifics.history) {
			this.func.specifics.history.setValue(0, {action: "details", "args[thumbsrc]": url});
		}
	},

	/**
	 * viewNextAlbumImage
	 *
	 * Displays the next albumimage
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @return void
	 **/
	viewNextAlbumImage: function() {
		if (this.effect) {
			for (var i = 0; i < this.albumimages.length - 1; i++) {
				if (this.albumimages[i].url == this.current) {
					this.viewAlbumimage(this.albumimages[i + 1].url);
					return;
				}
			}
			this.viewAlbumimage(this.albumimages[0].url);
		}
	},

	/**
	 * viewPreviousAlbumImage
	 *
	 * Displays the previous albumimage
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @return void
	 **/
	viewPreviousAlbumImage: function() {
		for (var i = 1; i < this.albumimages.length; i++) {
			if (this.albumimages[i].url == this.current) {
				this.viewAlbumimage(this.albumimages[i - 1].url);
				return;
			}
		}
		this.viewAlbumimage(this.albumimages[this.albumimages.length - 1].url);
	}
});
/**
 * WJCBFunctionalityalbum implements album specific functionality, ie. replacing the entire container div instead of just the contentblock div
 *
 * @since Tue Apr 21 2009
 * @author Ron Rademaker
 **/
var WJCBFunctionalityalbum = Class.create(WJCBFunctionalityHistoryManager, { 
	/**
	 * initialize
	 *
	 * Creates a new album specific cb functionality
	 *
	 * @since Tue Apr 21 2009
	 * @access public
	 * @param WJCBFunctionality func
	 * @return void
	 **/
	initialize: function($super, func) {
		func.element = $(func.collection + "_container");
		func.updateMethod = "replaceContent";
		$super(func);
	}
});
/**
 * Form specific functionality (copied from WJCBFunctionalityreview)
 *
 * @since Fri Feb 13 2009
 * @author Niels Nijens
 **/
var WJCBFunctionalityform = Class.create({
	
	/**
	 * initialize
	 *
	 * Creates a new review specific cb functionality
	 *
	 * @since initial
	 * @access public
	 * @param WJCBFunctionality func
	 * @return void
	 **/
	initialize: function(func) {
		this.func = func;
	},
	
	/**
	 * handleNotAcceptable
	 *
	 * Handles Not Acceptable error
	 *
	 * @since initial
	 * @access public
	 * @return void
	 **/
	handleNotAcceptable: function() {
		if ($(this.func.id + "_missing") ) {
			$(this.func.id + "_missing").setStyle({"display": "block"});
		}
	}
});
/**
 * Newsletter specific functionality (copied from WJCBFunctionalityform)
 *
 * @since Fri Jun 12 2009
 * @author Ron Rademaker
 **/
var WJCBFunctionalitynewsletter = Class.create({
	
	/**
	 * initialize
	 *
	 * Creates a new review specific cb functionality
	 *
	 * @since initial
	 * @access public
	 * @param WJCBFunctionality func
	 * @return void
	 **/
	initialize: function(func) {
		this.func = func;
	},
	
	/**
	 * handleNotAcceptable
	 *
	 * Handles Not Acceptable error
	 *
	 * @since initial
	 * @access public
	 * @return void
	 **/
	handleNotAcceptable: function() {
		if ($(this.func.id + "_missing") ) {
			$(this.func.id + "_missing").setStyle({"display": "block"});
		}
	}
});
/**
 * Rating specific functionality
 *
 * @since Tue Dec 16 2008
 * @author Ron Rademaker
 **/
var WJCBFunctionalityrating = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new rating specific cb functionality
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @param WJCBFunctionality func
	 * @return void
	 **/
	initialize: function(func) {
		this.func = func;
		if (this.func.getElement() ) {
			this._attachObservers();
			this._setDefaults();
		}
	},

	/**
	 * _attachObservers
	 * 
	 * Attaches observers to the rating blok to allow voting
	 * Note: this function is written for direct rating voting, ie. clicking on the fourth star immediately votes 4. Other requirements require overriding this function by reimplementing WJCBFunctionalityrating, just make sure the javascript parser meets it later that this class and it'll work (you might even be able to extend this class).
	 *
	 * @since Tue Dec 16 2008
	 * @access protected
	 * @return void
	 **/
	_attachObservers: function() {
		var fullbar = this._getBar("fullbar")
		this._voteObserver = this.vote.bindAsEventListener(this);
		fullbar.observe("click", this._voteObserver);
		this._moveObserver = this.mouseMove.bindAsEventListener(this);
		fullbar.observe("mousemove", this._moveObserver);
		this._outObserver = this.restoreWidth.bindAsEventListener(this);
		fullbar.observe("mouseout", this._outObserver);
	},

	/**
	 * _getBar
	 *
	 * Returns a bar identified by className
	 *
	 * @since Mon Jan 12 2009
	 * @access protected
	 * @param string className
	 * @return Element
	 **/
	_getBar: function(className) {
		return this.func.getElement().select("." + className).first();
	},

	/**
	 * mouseMove
	 *
	 * Handles the mousemove event
	 *
	 * @since Mon Jan 12 2009
	 * @access public
	 * @param Event e
	 * @return void
	 **/
	mouseMove: function(e) {
		var bar = this._getBar("percentage");
		if (bar) {
			if (this._originalWidth == null) {
				this._originalWidth = bar.getStyle("width");
			}
			var elemloc = bar.cumulativeOffset();
			var relmouseloc = {x: e.pointerX() - elemloc[0], y: e.pointerY() - elemloc[1]};
			if (relmouseloc.x < this._getBar("fullbar").getWidth() ) {
				bar.setStyle({width: relmouseloc.x + "px"});
			}
		}
	},

	/**
	 * restoreWidth
	 *
	 * Restores the original width of the bar
	 *
	 * @since Mon Jan 12 2009
	 * @access public
	 * @param Event e
	 * @return void
	 **/
	restoreWidth: function(e) {
		var bar = this._getBar("percentage");
		if (bar && this._originalWidth != null) {
			bar.setStyle({width: this._originalWidth});
		}
	},

	/**
	 * _setDefaults
	 *
	 * Sets some default data in func (this will allow you to override defaults by using the func's setData function from the uft)
	 *
	 * @since Tue Dec 16 2008
	 * @access protected
	 * @return void
	 **/
	_setDefaults: function() {
		this.func.setData("boundaries", {x: 0, y: 0, width: this._getBar("fullbar").getWidth(), height: this._getBar("fullbar").getHeight()});
	},

	/**
	 * vote
	 *
	 * Handles a vote click
	 *
	 * @since Tue Dec 16 2008
	 * @access public
	 * @param event e
	 * @return void
	 **/
	vote: function(e) {
		var elemloc = this._getBar("fullbar").cumulativeOffset();
		var relclickloc = {x: e.pointerX() - elemloc[0], y: e.pointerY() - elemloc[1]};
		var boundaries = this.func.getData("boundaries");
		if ( (relclickloc.x > boundaries.x) && (relclickloc.x < (boundaries.x + boundaries.width) )
			&&	(relclickloc.y > boundaries.y) && (relclickloc.y < (boundaries.y + boundaries.height) )  ) {
			var limits = {min: parseInt(this.func.getData("min") ), max: parseInt(this.func.getData("max") )};
			var stepwidth = boundaries.width / (1 + limits.max - limits.min);
			var vote = Math.ceil( (relclickloc.x - boundaries.x) / stepwidth);
			$("vote_" + this.func.getId() ).value = vote;
			this.func.callAction("vote");
			this._getBar("fullbar").stopObserving("click", this._voteObserver);
			this._getBar("fullbar").stopObserving("mousemove", this._moveObserver);
			this._getBar("fullbar").stopObserving("mouseout", this._outObserver);
		}
	}
});
/**
 * Review specific functionality
 *
 * @since Thu Jan 29 2009
 * @author Ron Rademaker
 **/
var WJCBFunctionalityreview = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new review specific cb functionality
	 *
	 * @since Thu Jan 29 2009
	 * @access public
	 * @param WJCBFunctionality func
	 * @return void
	 **/
	initialize: function(func) {
		this.func = func;
	},

	/**
	 * handleNotAcceptable
	 *
	 * Shows the info on the fact fields are missing, NOTE: does not show which fields are missing
	 *
	 * @since Tue Mar 17 2009
	 * @access public
	 * @return void
	 **/
	handleNotAcceptable: function() {
		if ($(this.func.id + "_missing") ) {
			$(this.func.id + "_missing").setStyle({"display": "block"});
		}
	}
});
/**
 * Send specific functionality (copied from WJCBFunctionalityform)
 *
 * @since Fri Jul 03 2009
 * @author Niels Nijens
 **/
var WJCBFunctionalitysend = Class.create({
	
	/**
	 * initialize
	 *
	 * Creates a new send specific cb functionality
	 *
	 * @since initial
	 * @access public
	 * @param WJCBFunctionality func
	 * @return void
	 **/
	initialize: function(func) {
		this.func = func;
	},
	
	/**
	 * handleNotAcceptable
	 *
	 * Handles Not Acceptable error
	 *
	 * @since initial
	 * @access public
	 * @return void
	 **/
	handleNotAcceptable: function() {
		if ($(this.func.id + "_missing") ) {
			$(this.func.id + "_missing").setStyle({"display": "block"});
		}
	}
});
/**
 * WJCBGooglemapsdirections
 *
 * WJCBGooglemapsdirections handles a google map directions CB
 *
 * @since Wed Oct 07 2009
 * @revision $Revision$
 * @author Giso Stallenberg
 * @package Windmill.Javascript.Contentblock
 **/
var WJCBGooglemapsdirections = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJCBGooglemapsdirections
	 *
	 * @since Wed Oct 07 2009
	 * @access public
	 * @param string elementid
	 * @param Object settings
	 * @return WJCBGooglemapsdirections
	 **/
	initialize: function(elementid, settings) {
		this._settings = new Hash(settings);
		
		$(this._settings.get("from") ).hide();
		$(this._settings.get("submit") ).up().hide();
		
		this._routeelement = $(elementid);
		this._mapcanvas = this._routeelement.down(".googlemapscanvas");
		this._maperrors = this._routeelement.appendChild(new Element("div", {"class": "googlemapsdirectionserror"} ) ).hide();
		this._printbutton = this._routeelement.appendChild(new Element("a", {"href": "#"} ) ).hide().update(this._settings.get("printlabel") ).observe("click", this.print.bindAsEventListener(this) );
		this._mapdirections = this._routeelement.appendChild(new Element("div", {"class": "googlemapsdirections"} ) ).hide();
		this._map = new GMap2(this._mapcanvas);
		this._geocoder = new GClientGeocoder();
		this._geocoder.getLatLng(this._settings.get("to"), this.centerMap.bind(this) );
		this._directions = new GDirections(this._map, this._mapdirections);
		GEvent.addListener(this._directions, "error", this._displayError.bindAsEventListener(this) );
		GEvent.addListener(this._directions, "load", this._displayDirections.bindAsEventListener(this) );
		this._addControls();
		this._addPlanObserver();
	},

	/**
	 * _displayError
	 *
	 * Displays the error text
	 *
	 * @since Thu Oct 8 2009
	 * @access protected
	 * @return void
	 **/
	_displayError: function() {
		this._maperrors.update(this._settings.get("errortext") ).show();
		this._mapdirections.hide();
		this._printbutton.hide();
	},

	/**
	 * _addPlanObserver
	 *
	 * Adds a click observer to the submit button
	 *
	 * @since Thu Oct 8 2009
	 * @access protected
	 * @return void
	 **/
	_addPlanObserver: function() {
		$(this._settings.get("submit") ).observe("click", this.planRoute.bindAsEventListener(this) );
	},

	/**
	 * planRoute
	 *
	 * Gets the directions
	 *
	 * @since Thu Oct 8 2009
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	planRoute: function(event) {
		this._maperrors.update("");
		var from = $(this._settings.get("from") ).down("input").value;
		this._directions.load("from: " + from + " to: " + this._glatlng.lat() + "," + this._glatlng.lng(), this._mapdirections);
	},

	/**
	 * _displayDirections
	 *
	 * Displays the directions
	 *
	 * @since Thu Oct 8 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_displayDirections: function(event) {
		this._mapdirections.show();
		// this._printbutton.show(); does not work in IE for now, so just don't show it
	},
	
	/**
	 * centerMap
	 *
	 * Centers the map to glatlng
	 *
	 * @since Thu Oct 8 2009
	 * @access public
	 * @param GLatLng glatlng
	 * @return boolean
	 **/
	centerMap: function(glatlng) {
		if (glatlng) {
			this._glatlng = glatlng;
			this._map.setCenter(this._glatlng, parseInt(this._settings.get("zoom") ) );
			this._tomarker = new GMarker(this._glatlng);
			this._map.addOverlay(this._tomarker);
			this._tomarker.openInfoWindowHtml(this._settings.get("text") + "<div id='searchfields' class='searchfields'>&#160;</div>");
			GEvent.addListener(this._tomarker, "infowindowopen", function () {
				$("searchfields").appendChild($(this._settings.get("from") ) ).show();
				$("searchfields").appendChild($(this._settings.get("submit") ).up() ).show();
				$("searchfields").id = null;
			}.bindAsEventListener(this) );
			return true;
		}
		return false;
	},

	/**
	 * addControls
	 *
	 * Adds the control buttons to the map
	 *
	 * @since Thu Oct 8 2009
	 * @access protected
	 * @return void
	 **/
	_addControls: function() {
		this._map.addControl(new GSmallZoomControl() );
		this._map.addControl(new GMapTypeControl() );
	},

	/**
	 * print
	 *
	 * Opens a window and executes the print command
	 *
	 * @since Thu Oct 8 2009
	 * @access public
	 * @param Event event
	 * @return void
	 **/
	print: function(event) {
		var from = $(this._settings.get("from") ).down("input").value;
		var directions = "from: " + from + " to: " + this._glatlng.lat() + "," + this._glatlng.lng();

		var scriptsrc = "<script language='JavaScript' type='text/javascript' src='" + this._settings.get("jssrc") + "'> </script>";
		var src = "<html><head><title>" + this._settings.get("to") + "</title>" + scriptsrc + "</head><body><div id='canvas' style='height: " + this._mapcanvas.getHeight() + "px; width: " + this._mapcanvas.getWidth() + "px;'>&#160;</div><div id='directions'>&#160;</div></body></html>";

		var script = "<script language='JavaScript' type='text/javascript'>";
		script += "map = new GMap2(document.getElementById('canvas') );";
		script += "map.setCenter(new GLatLng(" + this._glatlng.lat() + "," + this._glatlng.lng() + "), " + parseInt(this._settings.get("zoom") ) + " );";
		script += "directions = new GDirections(map, document.getElementById('directions') );";
		script += "GEvent.addListener(directions, 'load', function() {window.print();});";
		script += "directions.load('" + directions + "');";
		script += "</script>";

		var printwindow = window.open("about:blank", "printdialog", "scrollbars=yes,height=" + (this._mapcanvas.getHeight() + 150) + ",width=" + (this._mapcanvas.getWidth() + 40) );
		printwindow.document.write(src);
		printwindow.document.close()
	}
});/**
 * WJCBGooglemaps
 *
 * WJCBGooglemaps handles a google map CB
 *
 * @since Tue Dec 30 2008
 * @revision $Revision$
 * @author Reyo Stallenberg
 * @package Windmill.Javascript.Contentblock
 **/
var WJCBGooglemaps = Class.create({
	/**
	 * Properties of WJCBGooglemaps
	 *
	 * Hash _overlays
	 * Hash _forms
	 **/
	_overlays: new Hash(),
	_forms: new Hash(),
	_filterlists: new Hash(),
	_selectobservers: new Hash(),
	_loader: null,

	/**
	 * initialize
	 *
	 * Creates a new WJCBGooglemaps
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param string id
	 * @return new WJCBGooglemaps
	 **/
	initialize: function(elementid, latitude, longitude, zoom, sitename) {
		this._mapelement = $(elementid);
		this._mapelement.style.backgroundImage = "";

		this._center = new GLatLng(latitude, longitude);
		this._zoom = zoom;
		this._sitename = sitename;

		this._zoomtobounds = false;
		this._bounds = new GLatLngBounds();

		this._addpositioncontrol = true;
		this._positioncontrol = (this._mapelement.getHeight() < 300) ? new GSmallMapControl : new GLargeMapControl();

		this._map = new GMap2(this._mapelement);

		this._map.addMapType(G_PHYSICAL_MAP);
		var hierarchy = new GHierarchicalMapTypeControl();
		hierarchy.addRelationship(G_SATELLITE_MAP, G_HYBRID_MAP, null, true);

		this._addtypecontrol = true;
		this._typecontrol = hierarchy;

		this._enabledoubleclickzoom = true;
		this._enablecontinuouszoom = true;

		this.currentrequests = 0;
		this.requests = new Array();
	},

	/**
	 * setLoader
	 *
	 * Gives possibility to add a loader
	 *
	 * @since Tue Apr 7 2009
	 * @access public
	 * @param element
	 * @return void
	 **/
	setLoader: function(element) {
		this._loader = element;
	},


	/**
	 * getLoader
	 *
	 * Get the loader
	 *
	 * @since Tue Apr 7 2009
	 * @access public
	 * @return element
	 **/
	getLoader: function() {
		return this._loader;
	},

	/**
	 * loaderShowHide
	 *
	 * Checks if it should show or hide the loader (only adds classname)
	 *
	 * @since Tue Apr 7 2009
	 * @access public
	 * @return void
	 **/
	 loaderShowHide: function() {
		if (this.getLoader() && Object.isElement(this.getLoader() ) ) {
			if (this.currentrequests > 0) {
				this.getLoader().addClassName("maploadershow");
			}
			else {
				this.getLoader().removeClassName.bind(this.getLoader() ).defer("maploadershow");
			}
		}
	 },

	/**
	 * changeSettings
	 *
	 * Gives possibility to overwrite some settings
	 *
	 * @since Tue Feb 10 2009
	 * @access
	 * @param
	 * @return
	 **/
	changeSettings: function(settings) {
		for (key in settings) {
			this[key] = settings[key];
		}
	},

	/**
	 * applySettings
	 *
	 * Applies the settings for the map
	 *
	 * @since Tue Feb 10 2009
	 * @access public
	 * @param
	 * @return
	 **/
	applySettings: function() {
		this._map.setCenter(this._center, this._zoom);

		if (this._enabledoubleclickzoom) {
			this._map.enableDoubleClickZoom();
		}
		if (this._enablecontinuouszoom) {
			this._map.enableContinuousZoom();
		}
		if (this._addpositioncontrol) {
			this._map.addControl(this._positioncontrol);
		}
		if (this._addtypecontrol) {
			this._map.addControl(this._typecontrol);
		}

		this._addZoomListener();
	},

	/**
	 * addZoomListener
	 *
	 * Adds a listener to be able to reposition the custom infowindows
	 *
	 * @since Tue Feb 10 2009
	 * @access protected
	 * @return void
	 **/
	_addZoomListener: function() {
		GEvent.addListener(this._map, "zoomend", function() {
			this._overlays.findAll(function(overlay) {
				return (typeof(overlay.value.infowindow) != "undefined" && overlay.value.infowindow.isVisible() );
			} ).each(function(overlay) {
				this.positionInfowindowRelativeToMarker(overlay.value);
			}.bind(this) );
		}.bind(this) );
	},

	/**
	 * pushOverlay
	 *
	 * Stacks the settings of an overlay in this._overlays
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param String key
	 * @param String overlay
	 * @param Boolean active
	 * @param Boolean filterable
	 * @param Boolean checkable
	 * @return void
	 **/
	pushOverlay: function(key, overlay, active, parser, filterable, checkable) {
		var active = (active == 1) ? true : false;
		this.setOverlay(key, {
			"baseurl": overlay,
			"url": overlay,
			"active": active,
			"parser": parser,
			"filterable": filterable,
			"checkable": checkable
		});
	},

	/**
	 * setOverlay
	 *
	 * Adds an item to this._overlays
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @param object value
	 * @return void
	 **/
	setOverlay: function(key, value) {
		this._overlays.set(key, value);
	},

	/**
	 * getOverlay
	 *
	 * Gets an item from this._overlays
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @return object
	 **/
	getOverlay: function(key) {
		return this._overlays.get(key);
	},

	/**
	 * addOverlays
	 *
	 * Executes addOverlay for the active overlays on this map
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param boolean readform
	 * @return void
	 **/
	addOverlays: function(readform) {
		var readform = (readform !== false);
		if (readform) {
			this._fillFormsValues();
		}
		this._overlays.each(function(overlay) {
			if (typeof(overlay.value.geoxml) == "undefined") {
				this.getNewGeoXml(overlay);
			}
			else if (overlay.value.active) {
				this.addOverlay(overlay)
			}
		}.bind(this) );
	},

	/**
	 * addOverlay
	 *
	 * Adds the parsed overlay to the map
	 *
	 * @since Fri Jan 30 2009
	 * @access public
	 * @param Hash overlay
	 * @return void
	 **/
	addOverlay: function(overlay) {
		this._map.addOverlay(overlay.value.geoxml);
		overlay.value.lasturl = overlay.value.url;
	},

	/**
	 * getNewGeoXml
	 *
	 * Adds the overlay with the correct parser
	 *
	 * @since Thu Jan 29 2009
	 * @access public
	 * @param Hash overlay
	 * @return void
	 **/
	getNewGeoXml: function(overlay) {
		if (overlay.value.active) {
			if (overlay.value.parser == "GGeoXml") {
				overlay.value.geoxml = new GGeoXml(overlay.value.url);
				this.addOverlay(overlay);
			}
			else {
				this.parseOverlay(overlay);
				var handle = GEvent.addListener(overlay.value.geoxml, "parsed", function (overlay) {
					if (overlay.value.active) {
						this.addOverlay(overlay);
					}
					this.currentrequests--;
					this.loaderShowHide();
					this.zoomToBounds();
					GEvent.removeListener(handle);
				}.bind(this, overlay) );
			}
		}
	},

	/**
	 * hideInfoWindows
	 *
	 * Hides all visible infowindows
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @return void
	 **/
	hideInfoWindows: function() {
		this._overlays.findAll(function(overlay) {
			return (typeof(overlay.value.infowindow) != "undefined");
		} ).each(function(overlay) {
			overlay.value.infowindow.hide();
		});
	},

	/**
	 * createInfoWindow
	 *
	 * Creates an infowindow for the overlay on map
	 *
	 * @since Mon Feb 2 2009
	 * @access public
	 * @return WJWindowGooglemaps
	 **/
	createInfoWindow: function(overlay, map) {
		var callback = function(wjwindow, event, answer) {
			wjwindow.hide();
			event.stop();
		};

		var infowindow = new WJWindowGooglemaps(new WJWindow(callback, $(map.getPane(G_MAP_FLOAT_PANE) ) ), overlay.key, this._sitename);

		var infowindowelement = infowindow.getWindowElement();

		var stopInfowindowEvents = function(event, evtype) {
			var eventelem = Event.findElement(event);
			if (evtype == "mousedown") {
				switch (eventelem.tagName.toLowerCase() ) {
					case "input":
						eventelem.focus();
					case "a":
						break;
					default:
						Event.stop(event);
						break;
				}
			}
			else {
				Event.stop(event);
			}
		}
		GEvent.bindDom(infowindowelement, "mousedown", this, stopInfowindowEvents.bindAsEventListener(infowindowelement, "mousedown") );
		GEvent.bindDom(infowindowelement, "dblclick", this, stopInfowindowEvents.bindAsEventListener(infowindowelement, "dblclick") );
		return infowindow;
	},

	/**
	 * removeOverlays
	 *
	 * Executes removeOverlay for the non active overlays on this map
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @return void
	 **/
	removeOverlays: function() {
		this._overlays.each(function(overlay) {
			if (typeof(overlay.value.geoxml) != "undefined" && overlay.value.filterable) {
				if (typeof(overlay.value.infowindow) != "undefined") {
					overlay.value.infowindow.hide();
				}
				this._map.removeOverlay(overlay.value.geoxml);
			}
			if (overlay.value.lasturl != overlay.value.url && overlay.value.filterable) {
				this.getNewGeoXml(overlay);
			}
		}.bind(this) );
	},

	/**
	 * removeOverlay
	 *
	 * Removes a single overlay
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @return void
	 **/
	removeOverlay: function(key) {
		var overlay = this._overlays.get(key);
		if (overlay.active) {
			overlay.active = false;
			this._map.removeOverlay(overlay.geoxml);
		}
	},

	/**
	 * zoomToBounds
	 *
	 * Resets the zoomlevel and lat lon depending on the content of the map
	 *
	 * @since Tue Jun 23 2009
	 * @access public
	 * @return void
	 **/
	zoomToBounds: function() {
		if (this._zoomtobounds && !(this.currentrequests > 0) ) {
			this._bounds = new GLatLngBounds();
			this._overlays.each(function(overlay) {
				if (overlay.value.active == true) {
					if (typeof(overlay.value.data.gmarkers) != "undefined") {
						overlay.value.data.gmarkers.each(function(gmarker) {
							this._bounds.extend(gmarker.getLatLng() );
						}.bind(this) );
					}
					if (typeof(overlay.value.data.gpolygons) != "undefined") {
						overlay.value.data.gpolygons.each(function(gpolygon) {
							var bounds = gpolygon.getBounds();
							this._bounds.extend(bounds.getNorthEast() );
							this._bounds.extend(bounds.getSouthWest() );
						}.bind(this) );
					}
				}
			}.bind(this) );
			if (!this._bounds.isEmpty() ) {
				this._map.setZoom(this._map.getBoundsZoomLevel(this._bounds) );
				this._map.setCenter(this._bounds.getCenter() );
			}
		}
	},

	/**
	 * addMainFormObserver
	 *
	 * Adds a form observer for the element retrieved by elementid
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string elementid
	 * @return void
	 **/
	addMainFormObserver: function(elementid) {
		var mainform = $(elementid);
		mainform.getInputs().each(function(element) {
			$(element).observe("click", function(event) {
				this._handleMainFormChange(event);
			}.bind(this) );
		}.bind(this) );
		this._mainform = mainform;
	},

	/**
	 * addFormObserver
	 *
	 * Adds a form observer for the given form identified by name
	 *
	 * @since Tue Jan 13 2009
	 * @access public
	 * @param string formname
	 * @return void
	 **/
	addFormObserver: function(formname) {
		var additionalform = $(document.forms[formname] );
		if (Object.isElement(additionalform) ) {
			additionalform.getElements().each(function(element) {
				if (element.tagName.toLowerCase() == "select") {
					this._selectobservers.set(element.name, Form.Element.getValue(element) );
				}
				$(element).observe("click", function(event) {
					var eventelement = Event.findElement(event, "select");
					if (eventelement != document && eventelement) {
						var value =  Form.Element.getValue(eventelement);
						if (value != this._selectobservers.get(eventelement.name) ) {
							this._selectobservers.set(eventelement.name, value);
							this._handleChange(event);
						}
					}
					else {
						this._handleChange(event);
					}
				}.bind(this) );
			}.bind(this) );
			Event.observe(additionalform, "submit", this._handleSubmit.bind(this) );
			this.setForm(additionalform.name, additionalform);
		}
	},

	/**
	 * addAddFilterObserver
	 *
	 * Adds an observer for each <a> that is an "add filter" link
	 *
	 * @since Fri Jul 24 2009
	 * @access public
	 * @param string filterlist
	 * @return void
	 **/
	addFilterlistObservers: function(filterlistwrappersid, id, module) {
		var filterwrapper = $(filterlistwrappersid + "_filters");
		if (filterwrapper) {
			filterwrapper.observe("click", this._handleFilterChange.bindAsEventListener(this) );
		}
		var activefilterwrapper = $(filterlistwrappersid + "_activefilters");
		if (activefilterwrapper) {
			activefilterwrapper.observe("click", this._handleFilterChange.bindAsEventListener(this) );
		}

		this._filterlists.set(id, new Hash({filterwrapper: filterwrapper, activefilterwrapper: activefilterwrapper, module: module} ) );
	},

	/**
	 * _handleFilterChange
	 *
	 * Handles the event where a filter link is clicked
	 *
	 * @since Fri Jul 24 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 * @fires filter:changed
	 **/
	_handleFilterChange: function(event) {
		if (this.currentrequests > 0) {
			// Delay if there are requests pending, otherwise some functionality will break
			this._handleFilterChange.bind(this, event).delay(1);
			event.stop();
		}
		else {
			var link = event.findElement("a");
			if (link) {
				var list = [link.up("ul.filter_filter"), link.up("ul.filter_activefilters")].compact().first();
				if (list) {
					var filter = Object.toQueryString(link.href.toQueryParams() );
					this._applyFilter(filter);
					this.redrawOverlays(false);
					this._updateFilterLists(filter);
					event.stop();
					link.fire("filter:changed", {cbgooglemaps: this});
				}
			}
		}
	},

	/**
	 * _updateFilterLists
	 *
	 * Retrieves the new filterlist's using WJSpin
	 *
	 * @since Fri Jul 24 2009
	 * @access protected
	 * @param Hash filter
	 * @return void
	 **/
	_updateFilterLists: function(filter) {
		this._filterlists.values().each(function(filter, filterinfo) {
			var query = Object.extend({ct: "wmdynamic", dt: "filter", contenttype: "xml", "__type": "xml", module: filterinfo.get("module"), "wmtrigger[]": "requestufts", savecriteria: "true", "count": 1}, filter.toQueryParams() );

			new WJSpin().content(new WJUrl(query, "/index.php"), [this._updateFilterListElements.bind(this, filterinfo) ] );
		}.curry(filter), this);
	},

	/**
	 * _updateFilterListElements
	 *
	 * Updates the divs containing the filter lists
	 *
	 * @since Fri Jul 24 2009
	 * @access protected
	 * @param Hash filterinfo
	 * @param Document response
	 * @return void
	 **/
	_updateFilterListElements: function(filterinfo, response) {
		filterinfo.get("activefilterwrapper").update("");
		filterinfo.get("filterwrapper").update("");

		$A(response.documentElement.childNodes).each(function(listwrapper) {
			if (listwrapper.getAttribute("id") == "activefilters") {
				if (listwrapper.childNodes[0] ) {
					$A(listwrapper.childNodes[0].childNodes).each(function(filtercontent) {
						if (filterinfo.get("activefilterwrapper").ownerDocument.importNode) {
							filtercontent = filterinfo.get("activefilterwrapper").ownerDocument.importNode(filtercontent, true);
							filterinfo.get("activefilterwrapper").appendChild(filtercontent);
						}
						else if (filtercontent.xml) {
							filterinfo.get("activefilterwrapper").update(filterinfo.get("activefilterwrapper").innerHTML + filtercontent.xml);
						}
						else {
							filterinfo.get("activefilterwrapper").appendChild(filtercontent);
						}
					} );
				}
			}
			else if (listwrapper.getAttribute("id") == "filters") {
				if (listwrapper.childNodes[0] ) {
					$A(listwrapper.childNodes[0].childNodes).each(function(filtercontent) {
						if (filterinfo.get("filterwrapper").ownerDocument.importNode) {
							filtercontent = filterinfo.get("filterwrapper").ownerDocument.importNode(filtercontent, true);
							filterinfo.get("filterwrapper").appendChild(filtercontent);
						}
						else if (filtercontent.xml) {
							filterinfo.get("filterwrapper").update(filterinfo.get("filterwrapper").innerHTML + filtercontent.xml);
						}
						else {
							filterinfo.get("filterwrapper").appendChild(filtercontent);
						}
					} );
				}
			}
		} );

		filterinfo.get("filterwrapper").innerHTML = filterinfo.get("filterwrapper").innerHTML;
		filterinfo.get("activefilterwrapper").innerHTML = filterinfo.get("activefilterwrapper").innerHTML;
	},

	/**
	 * _handleSubmit
	 *
	 * Handles a form submit
	 *
	 * @since Thu Jan 15 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_handleSubmit: function(event) {
		this._handleChange(event);
		Event.stop(event);
	},

	/**
	 * _handleChange
	 *
	 * Handles the change to the form
	 *
	 * @since Fri Jan 2 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_handleChange: function(event) {
		if (this.currentrequests > 0) {
			// Delay if there are requests pending, otherwise some functionality will break
			this._handleChange.bind(this).delay(1, event);
		}
		else {
			this._fillFormsValues();
			this.redrawOverlays();
		}
	},

	/**
	 * _fillFormsValues
	 *
	 * Changes the urls of the overlays depending on the this._forms values
	 *
	 * @since Wed Jan 21 2009
	 * @access protected
	 * @return void
	 **/
	_fillFormsValues: function() {
		var filter = this._forms.values().invoke("serialize").join("&");
		this._applyFilter(filter);
	},

	/**
	 * _applyFilter
	 *
	 * Applies the given filter to all filterable overlays
	 *
	 * @since Fri Jul 24 2009
	 * @access protected
	 * @param string filter
	 * @return void
	 **/
	_applyFilter: function(filter) {
		this._overlays.values().each(function(filter, overlay) {
			if (overlay.filterable && filter != "") {
				overlay.url = overlay.baseurl + ( (overlay.baseurl.indexOf("?") == -1) ? "?" : "&") + filter;
			}
			else {
				overlay.url = overlay.baseurl;
			}
		}.bind(this, filter) );
	},

	/**
	 * redrawOverlays
	 *
	 * Removes and adds all overlays
	 *
	 * @since Thu Jan 15 2009
	 * @access public
	 * @param boolean readform
	 * @return void
	 **/
	redrawOverlays: function(readform) {
		this.removeOverlays();
		this.addOverlays(readform);
	},

	/**
	 * _handleMainFormChange
	 *
	 * Handles the change to the main form
	 *
	 * @since Thu Jan 15 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_handleMainFormChange: function(event) {
		var checkbox = Event.element(event);
		this.hideInfoWindows();

		this._overlays.each(function(checkbox, overlay) {
			if (checkbox.name == overlay.key) {
				if (checkbox.checked) {
					overlay.value.active = true;
					if (typeof(overlay.value.geoxml) == "undefined") {
						this.getNewGeoXml(overlay);
					}
					else if ( (overlay.value.url != overlay.value.lasturl) && overlay.value.filterable) {
						this.removeOverlay(checkbox.name);
						this.getNewGeoXml(overlay);
					}
					else {
						this.addOverlay(overlay);
					}
				}
				else {
					this.removeOverlay(checkbox.name);
					overlay.value.active = false;
				}
			}
		}.bind(this, checkbox) );
	},

	/**
	 * setForm
	 *
	 * Adds an item to this._forms
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @param object value
	 * @return void
	 **/
	setForm: function(key, value) {
		this._forms.set(key, value);
	},

	/**
	 * getForm
	 *
	 * Gets an item from this._forms
	 *
	 * @since Fri Jan 2 2009
	 * @access public
	 * @param string key
	 * @return element
	 **/
	getForm: function(key) {
		return this._forms.get(key);
	},

	/**
	 * parseOverlay
	 *
	 * Creates a new GOverlay and downloads kml
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @param Hash overlay
	 * @return GOverlay
	 **/
	parseOverlay: function(overlay) {
		overlay.value.data = {};
		overlay.value.styles = {};
		overlay.value.geoxml = {};
		overlay.value.geoxml.prototype = new GOverlay();
		overlay.value.geoxml.initialize = this.addOverlayToMap.bind(this, overlay);
		overlay.value.geoxml.remove = this.removeOverlayFromMap.bind(this, overlay);
		overlay.value.geoxml.redraw = this.redrawOverlayOnMap.bind(this, overlay);
		overlay.value.geoxml.copy = this.copyOverlayOfMap.bind(this, overlay);

		this.currentrequests++;
		this.loaderShowHide();
		this.zoomToBounds();

		this.stackRequest(overlay);
		if (this.currentrequests == 1) {
			this.downloadKML();
		}
	},

	/**
	 * stackRequest
	 *
	 * Adds the overlay to the stack of to do requests
	 *
	 * @since Fri Jun 26 2009
	 * @access public
	 * @param Hash overlay
	 * @return void
	 **/
	stackRequest: function(overlay) {
		this.requests.push(overlay);
	},

	/**
	 * downloadKML
	 *
	 * Download the first added overlay of the requests stack
	 *
	 * @since Fri Jun 26 2009
	 * @access public
	 * @return void
	 **/
	downloadKML: function() {
		var overlay = this.requests.shift();
		if (typeof(overlay) != "undefined") {
			GDownloadUrl(overlay.value.url, this.handleKMLRequest.bind(this, overlay) );
		}
	},

	/**
	 * showInfowindow
	 *
	 * Handles the showing of a custom infowindow
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param GMarker m
	 * @param Object overlayvalue
	 * @param String name
	 * @param Element desc
	 * @param String url
	 * @return void
	 **/
	showInfowindow: function(m, overlayvalue, name, desc, url) {
		overlayvalue.infowindow.setContent(desc);
		if (m.chkmlClassName != "") {
			overlayvalue.infowindow.setTheme(m.chkmlClassName);
		}
		overlayvalue.lastmarker = m;
		if (m.chkmlDirections != false) {
			this.handleDirections(m, overlayvalue);
		}
		if (typeof(url) != "undefined") {
			var spincomplete = function(overlayvalue, content) {
				overlayvalue.infowindow.setContent(content);
				overlayvalue.infowindow.setLoading(false);
				overlayvalue.infowindow.setHeight(450);
				var point = this.positionInfowindowRelativeToMarker(overlayvalue);
				this._map.panTo(this._map.fromDivPixelToLatLng(point) );
			};
			var spin = new WJSpin();
			overlayvalue.infowindow.setLoading(true);
			spin.content(url, [spincomplete.bind(this, overlayvalue)]);
		}
		overlayvalue.infowindow.setHeight("auto");
		overlayvalue.infowindow.show();
		var point = this.positionInfowindowRelativeToMarker(overlayvalue);
		if (typeof(url) == "undefined") {
			this._map.panTo(this._map.fromDivPixelToLatLng(point) );
		}
	},

	/**
	 * handleDirections
	 *
	 * If a marker has "directions" this method will handle it
	 *
	 * @since Tue Dec 8 2009
	 * @access public
	 * @param GMarker m
	 * @param Object overlayvalue
	 * @return void
	 **/
	handleDirections: function(m, overlayvalue) {
		if (!this._directions) {
			this.createDirections();
		}

		if (typeof(overlayvalue.infowindow.getContentElement("directions") ) != "undefined") {
			overlayvalue.infowindow.removeWindowRow("directions");
		}
		if (m.chkmlDirections != false) {
			var directionsrow = overlayvalue.infowindow.insertWindowRowBefore("bottom", "directions");
			var directionsrowcontent = overlayvalue.infowindow.getContentElement("directions");
			directionsrowcontent.update(m.chkmlDirections.getHtml() );
			var formelement = directionsrowcontent.down("form");
			if (m.chkmlDirections.addTo === false && m.chkmlDirections.addFrom === true) {
				this._directionsdirection = "from";
			}
			else {
				this._directionsdirection = "to";
			}
			formelement.addClassName("infowindowdirection_" + this._directionsdirection);
			formelement.addClassName("infowindowdirection_hidden");
			formelement.observe("submit", function(event, formelement, overlayvalue) {
				var formvalues = formelement.serialize(true);
				var latlng = overlayvalue.lastmarker.getLatLng().toUrlValue();
				if (this._directionsdirection == "to") {
					var searchstring = "from: " + formvalues.address + " to: " + latlng;
				}
				else {
					var searchstring = "from: " + latlng + " to: " + formvalues.address;
				}
				this._directions.load(searchstring, this._mapdirections);
				Event.stop(event);
			}.bindAsEventListener(this, formelement, overlayvalue) );
			formelement.select("a").each(function(formelement, element, key) {
				element.observe("click", function(event, formelement) {
					var eventelem = Event.element(event);
					if (eventelem.hasClassName("back") ) {
						formelement.addClassName("infowindowdirection_hidden");
						var point = this.positionInfowindowRelativeToMarker(overlayvalue);
						this._map.panTo(this._map.fromDivPixelToLatLng(point) );
					}
					else if (eventelem.hasClassName("link_to") ) {
						this._directionsdirection = "to";
						formelement.removeClassName("infowindowdirection_from");
						formelement.addClassName("infowindowdirection_to");
					}
					else if (eventelem.hasClassName("link_from") ) {
						this._directionsdirection = "from";
						formelement.removeClassName("infowindowdirection_to");
						formelement.addClassName("infowindowdirection_from");
					}
					else if (eventelem.hasClassName("directionsactivator") ) {
						formelement.removeClassName("infowindowdirection_hidden");
						var point = this.positionInfowindowRelativeToMarker(overlayvalue);
						this._map.panTo(this._map.fromDivPixelToLatLng(point) );
					}
				}.bindAsEventListener(this, formelement) );
			}.bind(this, formelement) );
		}
	},

	/**
	 * createDirections
	 *
	 * Creates a direction object
	 *
	 * @since Tue Dec 8 2009
	 * @access public
	 * @return void
	 **/
	createDirections: function() {
		this._mapdirections = this._mapelement.up().appendChild(new Element("div", {"class": "googlemapsdirections"} ) ).hide();
		if (Object.isElement($("googlemapsdirectionserror") ) ) {
			this._directionerrors = $("googlemapsdirectionserror");
		}
		else {
			this._directionerrors = this._mapelement.up().appendChild(new Element("div", {"class": "googlemapsdirectionserror"} ) );
		}
		this._directionerrors.hide();
		this._directions = new GDirections(this._map, this._mapdirections);
		GEvent.addListener(this._directions, "error", this._displayDirectionsError.bindAsEventListener(this) );
		GEvent.addListener(this._directions, "load", this._displayDirections.bindAsEventListener(this) );
	},

	/**
	 * _displayDirections
	 *
	 * Displays the directions
	 *
	 * @since Thu Oct 8 2009
	 * @access protected
	 * @param Event event
	 * @return void
	 **/
	_displayDirections: function(event) {
		if (Object.isElement($("googlemapsdirectionserror") ) ) {
			$("googlemapsdirectionserror").hide();
		}
		else {
			this._directionerrors.update("");
		}
		this.hideInfoWindows();
		this._mapdirections.show();
	},

	/**
	 * _displayDirectionsError
	 *
	 * Displays an error for directions
	 *
	 * @since Tue Dec 8 2009
	 * @access protected
	 * @return void
	 **/
	_displayDirectionsError: function() {
		if (Object.isElement($("googlemapsdirectionserror") ) ) {
			$("googlemapsdirectionserror").show();
		}
		else {
			this._directionerrors.update("Route could not be found").show();
		}
		this._mapdirections.hide();
	},

	/**
	 * addOverlayToMap
	 *
	 * Creates items to put on the map
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Hash overlay
	 * @param GMap2 map
	 * @return void
	 **/
	addOverlayToMap: function(overlay, map) {
		WJDebugger.log(WJDebugger.NOTICE, "Adding overlay " + overlay[0], overlay[1]);
		var data = overlay.value.data;

		if (typeof(overlay.value.infowindow) == "undefined") {
			overlay.value.infowindow = this.createInfoWindow(overlay, map);
		}

		// This shouldn't be needed, check why the overlays get duplicated
		this.removeOverlayFromMap(overlay);

		data.gpolygons = [];
		data.gpolylines = [];
		data.gmarkers = [];

		if (typeof(data.polylines) != "undefined") {
			data.polylines.each(function(map, overlay, polyline, key) {
				this.createPolyline(polyline, overlay);
			}.bind(this, map, overlay) );
		}

		if (typeof(data.polygons) != "undefined") {
			data.polygons.each(function(map, overlay, polygon, key) {
				this.createPolygon(polygon, overlay);
			}.bind(this, map, overlay) );
		}

		if (typeof(data.markers) != "undefined") {
			data.markers.each(function(map, overlay, marker, key) {
				this.createMarker(marker, overlay);
			}.bind(this, map, overlay) );
		}
	},

	/**
	 * removeOverlayFromMap
	 *
	 * Removes items of overlay from the map
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Hash overlay
	 * @return void
	 **/
	removeOverlayFromMap: function(overlay) {
		var data = overlay.value.data;

		if (typeof(data.gpolylines) != "undefined") {
			data.gpolylines.each(function(polyline, key){
				this._map.removeOverlay(polyline);
			}.bind(this) );
		}

		if (typeof(data.gpolygons) != "undefined") {
			data.gpolygons.each(function(polygon, key){
				this._map.removeOverlay(polygon);
			}.bind(this) );
		}

		if (typeof(data.gmarkers) != "undefined") {
			data.gmarkers.each(function(marker, key){
				this._map.removeOverlay(marker);
			}.bind(this) );
		}
	},

	/**
	 * redrawOverlayOnMap
	 *
	 * Redraws the overlay on the map (not implemented, but required in GOverlay, so therefore here)
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Hash overlay
	 * @param Boolean force
	 * @return void
	 * @see WJCBGooglemaps.parseOverlay
	 **/
	redrawOverlayOnMap: function(overlay, force) {
	},

	/**
	 * copyOverlayOfMap
	 *
	 * Copies an empty version of the overlay (not implemented, but required in GOverlay, so therefore here)
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @return void
	 * @see WJCBGooglemaps.parseOverlay
	 **/
	copyOverlayOfMap: function () {
	},

	/**
	 * createMarker
	 *
	 * Creates a single marker
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Object data
	 * @param Hash overlay
	 * @return void
	 **/
	createMarker: function(data, overlay) {
		var overlayvalue = overlay.value;
		var point = data.point;
		var name = data.name;
		var desc = data.desc;
		var style = data.styleInfo;
		var url;
		if (data.spin != "") {
			url = new WJUrl(data.spin.toQueryParams() , "/index.php");
		}

		var icon = overlayvalue.styles[style];

		var marker = new GMarker(point, icon);

		marker.chkmlClassName = data.chkmlClassName;
		marker.chkmlDirections = data.chkmlDirections;

		if (name != "" && desc != "") {
			GEvent.addListener(marker, "click", function(m, overlayvalue, name, desc, url) {
				if (m == overlayvalue.lastmarker) {
					overlayvalue.infowindow.show();
					var point = this.positionInfowindowRelativeToMarker(overlayvalue);
					this._map.panTo(this._map.fromDivPixelToLatLng(point) );
				}
				else {
					this.hideInfoWindows();
					overlayvalue.infowindow.setBaseTitle("");
					overlayvalue.infowindow.setContent("");

					overlayvalue.infowindow.setBaseTitle(name);

					var wrapper = new Element("div").update(desc);
					var contentimages = wrapper.select("img");
					overlayvalue.infowindow.contentimagescounter = contentimages.size();
					if (overlayvalue.infowindow.contentimagescounter > 0) {
						var countdown = function(event, m, html, iwoptions, name, desc, url) {
							overlayvalue.infowindow.contentimagescounter--;
							if (overlayvalue.infowindow.contentimagescounter == 0) {
								this.showInfowindow(m, overlayvalue, name, wrapper, url);
							}
						}.bindAsEventListener(this, m, overlayvalue, name, wrapper, url);

						contentimages.invoke("observe", "load", countdown).invoke("observe", "error", countdown);
					}
					else {
						this.showInfowindow(m, overlayvalue, name, desc, url);
					}
				}
			}.bind(this, marker, overlayvalue, name, desc, url) );
		}
		this._map.addOverlay(marker);
		overlayvalue.data.gmarkers.push(marker);
	},

	/**
	 * createPolyline
	 *
	 * Creates a single polyline
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Object data
	 * @param Hash overlay
	 * @return void
	 **/
	createPolyline: function(data, overlay) {
		var overlayvalue = overlay.value;
		var points = data.points;
		var color = data.color;
		var width = data.width;
		var opacity = data.opacity;
		var pbounds = data.pbounds;
		var name = data.name;
		var desc = data.desc;
		var p = new GPolyline(points, color, width, opacity);
		this._map.addOverlay(p);
		overlayvalue.data.gpolylines.push(p);
	},

	/**
	 * createPolygon
	 *
	 * Creates a single polygon
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Object data
	 * @param Hash overlay
	 * @return void
	 **/
	createPolygon: function(data, overlay) {
		var overlayvalue = overlay.value;
		var pbounds = data.pbounds;
		var name = data.name;
		var desc = data.desc;

		var p = new GPolygon(data.points, data.color, data.width, data.opacity, data.fillcolor, data.fillopacity);

		if (name != "" && desc != "") {
			GEvent.addListener(p, "click", function(p, pbounds, overlayvalue, name, desc, clicked) {
				this.hideInfoWindows();
				overlayvalue.infowindow.setBaseTitle("");
				overlayvalue.infowindow.setContent("");

				overlayvalue.infowindow.setBaseTitle(name);

				var wrapper = new Element("div").update(desc);
				var contentimages = wrapper.select("img");
				overlayvalue.infowindow.contentimagescounter = contentimages.size();
				if (overlayvalue.infowindow.contentimagescounter > 0) {
					var countdown = function(event, p, pbounds, overlayvalue, name, desc) {
						overlayvalue.infowindow.contentimagescounter--;
						if (overlayvalue.infowindow.contentimagescounter == 0) {
							this.positionInfowindowRelativeToPoint(overlayvalue, clicked);
							overlayvalue.infowindow.setContent(wrapper);
							overlayvalue.infowindow.show();
							this._map.panTo(clicked);
						}
					}.bindAsEventListener(this, p, pbounds, overlayvalue, name, wrapper);

					contentimages.invoke("observe", "load", countdown).invoke("observe", "error", countdown);
				}
				else {
					this.positionInfowindowRelativeToPoint(overlayvalue, clicked);
					overlayvalue.infowindow.setContent(wrapper);
					overlayvalue.infowindow.show();
					this._map.panTo(clicked);
				}
			}.bind(this, p, pbounds, overlayvalue, name, desc) );
		}

		overlayvalue.data.gpolygons.push(p);
		this._map.addOverlay(p);
	},

	/**
	 * handleKMLRequest
	 *
	 * Handles the response of GDownloadUrl
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Hash overlay
	 * @param String doc
	 * @param Integer responseCode
	 * @return void
	 **/
	handleKMLRequest: function(overlay, doc, responseCode) {
		if(responseCode == 200) {
			this.processKML(GXml.parse(doc), overlay);
		} else if(responseCode == -1) {
			WJDebugger.log(WJDebugger.ERROR, "Data request timed out. Please try later.");
		} else {
			WJDebugger.log(WJDebugger.ERROR, "Request resulted in error. Check XML file is retrievable.");
		}
		this.downloadKML();
	},

	/**
	 * processKML
	 *
	 * Processes the downloaded kml
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param Document xmlDoc
	 * @param Hash overlay
	 * @return void
	 **/
	processKML: function(xmlDoc, overlay) {
		if (xmlDoc.text != "") {
			WJDebugger.log(WJDebugger.NOTICE, "Processing " + overlay[0] + " overlay");
			overlay.value.styles = this.readStyles(xmlDoc.documentElement.getElementsByTagName("Style") );
			overlay.value.data = this.readData(xmlDoc.documentElement.getElementsByTagName("Placemark"), overlay.value.styles);
			overlay.value.groundoverlays = this.readGroundOverlays(xmlDoc.documentElement.getElementsByTagName("GroundOverlay") );
			GEvent.trigger(overlay.value.geoxml, "parsed");
			WJDebugger.log(WJDebugger.NOTICE, "Processed " + overlay[0] + " overlay");
		}
	},

	/**
	 * readData
	 *
	 * Reads out placemarks (polygons, polylines and markers)
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param DomNode placemarks
	 * @param Object styles
	 * @return Object
	 * @todo Refactoring and extraction
	 **/
	readData: function(placemarks, styles) {
		var data = {};
		data.polylines = [];
		data.polygons = [];
		data.markers = [];

		// Read through the Placemarks
		for (var i = 0; i < placemarks.length; i++) {
			var chkmlClassName = "";
			var name = this.readKMLValue(placemarks[i].getElementsByTagName("name")[0] );
			var desc = this.readKMLValue(placemarks[i].getElementsByTagName("description")[0] );
			if (typeof(placemarks[i].getElementsByTagNameNS ) != "undefined") {
				var spin = this.readKMLValue(placemarks[i].getElementsByTagNameNS("http://www.connectholland.nl/kml/", "spin")[0] );
			}
			else {
				var spin = this.readKMLValue(placemarks[i].getElementsByTagName("chkml:spin").item(0) );
			}
			var style = this.readKMLValue(placemarks[i].getElementsByTagName("styleUrl")[0] );
			var coords = GXml.value(placemarks[i].getElementsByTagName("coordinates")[0]);
			coords = coords.replace(/\s+/g," "); // tidy the whitespace
			coords = coords.replace(/^ /,"");    // remove possible leading whitespace
			coords = coords.replace(/ $/,"");    // remove possible trailing whitespace
			coords = coords.replace(/, /,",");   // tidy the commas

			var path = coords.split(" ");
			// Is this a polyline/polygon?
			if (path.length > 1) {
				// Build the list of points
				var points = [];
				var pbounds = new GLatLngBounds();
				for (var p = 0; p < path.length - 1; p++) {
					var bits = path[p].split(",");
					if (bits[1] != "") {
						var point = new GLatLng(parseFloat(bits[1] ), parseFloat(bits[0] ) );
						points.push(point);
						this._bounds.extend(point);
						pbounds.extend(point);
					}
				}

				var linestring = placemarks[i].getElementsByTagName("LineString");
				if (linestring.length) {
					// it's a polyline grab the info from the style
					if (!!styles[style]) {
						var chkmlClassName = styles[style].chkmlClassName;
						var width = styles[style].width;
						var color = styles[style].color;
						var opacity = styles[style].opacity;
					} else {
						var width = 5;
						var color = "#0000ff";
						var opacity = 0.45;
					}
					data.polylines.push( {
						points: points,
						width: width,
						color: color,
						opacity: opacity,
						pbounds: pbounds,
						name: name,
	 					desc: desc,
						chkmlClassName: chkmlClassName
					} );
				}

				var polygons = placemarks[i].getElementsByTagName("Polygon");
				if (polygons.length) {
					// it's a polygon grab the info from the style
					if (!!styles[style]) {
						var chkmlClassName = styles[style].chkmlClassName;
						var width = styles[style].width;
						var color = styles[style].color;
						var opacity = styles[style].opacity;
						var fillopacity = styles[style].fillopacity;
						var fillcolor = styles[style].fillcolor;
					} else {
						var width = 5;
						var color = "#0000ff";
						var opacity = 0.45;
						var fillopacity = 0.25;
						var fillcolor = "#0055ff";
					}

					data.polygons.push( {
						points: points,
						color: color,
						width: width,
						opacity: opacity,
						fillcolor: fillcolor,
						fillopacity: fillopacity,
						pbounds: pbounds,
						name: name,
						desc: desc,
						chkmlClassName: chkmlClassName
					} );
				}
			} else {
				// It's not a poly, so I guess it must be a marker
				var bits = path[0].split(",");
				if (!!styles[style] ) {
					var chkmlClassName = styles[style].chkmlClassName;
				}

				var chkmlDirections = this.readDirections(placemarks[i] );
				if (bits[1] != "" && typeof(bits[1]) != "undefined") {
					var point = new GLatLng(parseFloat(bits[1]),parseFloat(bits[0]));
					this._bounds.extend(point);
					data.markers.push( {
						point: point,
						name: name,
						desc: desc,
						styleInfo: style,
						spin: spin,
						chkmlClassName: chkmlClassName,
						chkmlDirections: chkmlDirections
					} );
				}
			}
		}
		return data;
	},

	/**
	 * readDirections
	 *
	 *
	 *
	 * @since Tue Dec 8 2009
	 * @access public
	 * @param DomNode placemark
	 * @return Object
	 **/
	readDirections: function(placemark) {
		if (typeof(placemark.getElementsByTagNameNS ) != "undefined") {
			var directionsnode = placemark.getElementsByTagNameNS("http://www.connectholland.nl/kml/", "directions")[0];
		}
		else {
			var directionsnode = placemark.getElementsByTagName("chkml:directions").item(0);
		}
		if (!directionsnode) {
			return false;
		}
		var html = this.readKMLValue(directionsnode);
		var addTo = (directionsnode.getAttribute("to") == "true");
		var addFrom = (directionsnode.getAttribute("from") == "true");
		return {
			getHtml: function() {
				return html;
			},
			addTo: addTo,
			addFrom: addFrom
		}
	},

	/**
	 * readData
	 *
	 * Reads out groundoverlays and adds them to the map
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param DomNode grounds
	 * @return Array
	 * @todo Refactoring
	 **/
	readGroundOverlays: function(grounds) {
		var parsedGroundOverlays = [];
		// Scan through the Ground Overlays
		for (var i = 0; i < grounds.length; i++) {
			var url = this.readKMLValue(grounds[i].getElementsByTagName("href")[0] );
			var north = parseFloat(this.readKMLValue(grounds[i].getElementsByTagName("north")[0] ) );
			var south = parseFloat(this.readKMLValue(grounds[i].getElementsByTagName("south")[0] ) );
			var east = parseFloat(this.readKMLValue(grounds[i].getElementsByTagName("east")[0] ) );
			var west = parseFloat(this.readKMLValue(grounds[i].getElementsByTagName("west")[0] ) );
			var sw = new GLatLng(south, west);
			var ne = new GLatLng(north, east);
			var ground = new GGroundOverlay(url, new GLatLngBounds(sw, ne) );
			this._bounds.extend(sw);
			this._bounds.extend(ne);
			parsedGroundOverlays.push(ground);
		}
		return parsedGroundOverlays;
	},

	/**
	 * readStyles
	 *
	 * Reads out styles
	 *
	 * @since Mon Feb 09 2009
	 * @access public
	 * @param DomNode styles
	 * @return Object
	 * @todo Refactoring and extraction
	 **/
	readStyles: function(styles) {
		var parsedStyles = {};
		for (var i = 0; i <styles.length; i++) {
			var styleID = styles[i].getAttribute("id");
			var iconstyle = styles[i].getElementsByTagName("IconStyle");
			var iconsizeX = 32;
			var iconsizeY = 32;
			var shadowWidth = 59;
			var scale = 1;

			if (iconstyle.length > 0) {
				var scalestyle = iconstyle[0].getElementsByTagName("scale");
				if (scalestyle.length > 0) {
					var scale = this.readKMLValue(scalestyle[0] );
					var iconsizeX = iconsizeX * scale;
					var iconsizeY = iconsizeY * scale;
					var shadowWidth = shadowWidth * scale;
				}

				var anchorX = iconsizeX * 0.5;
				var anchorY = iconsizeY;
				var iwanchorX = iconsizeX * 0.5;
				var iwanchorY = 0;

				var hotspot = iconstyle[0].getElementsByTagName("hotSpot");
				if (hotspot.length > 0) {
					var hotspotpos = this.calculateHotSpot(hotspot[0], iconsizeX, iconsizeY, anchorX, anchorY);
					anchorX = hotspotpos.x;
					anchorY = hotspotpos.y;
				}

				if (typeof(iconstyle[0].getElementsByTagNameNS ) != "undefined") {
					var iwhotspot = iconstyle[0].getElementsByTagNameNS("http://www.connectholland.nl/kml/", "infowindowHotSpot");
					var chkmlClassName = this.readKMLValue(iconstyle[0].getElementsByTagNameNS("http://www.connectholland.nl/kml/", "infowindowClassName")[0] );
					var imagemapnode = iconstyle[0].getElementsByTagNameNS("http://www.connectholland.nl/kml/", "area");
				}
				else {
					var iwhotspot = iconstyle[0].getElementsByTagName("chkml:infowindowHotSpot");
					var chkmlClassName = this.readKMLValue(iconstyle[0].getElementsByTagName("chkml:infowindowClassName").item(0) );
					//image map is not working in that program
				}

				if (iwhotspot.length > 0) {
					var iwhotspotpos = this.calculateHotSpot(iwhotspot[0], iconsizeX, iconsizeY, iwanchorX, iwanchorY);
					iwanchorX = iwhotspotpos.x;
					iwanchorY = iwhotspotpos.y;
				}

				if ( (typeof(imagemapnode) != "undefined") && (imagemapnode.length > 0) ) {
					var imageMapType = imagemapnode.item(0).getAttribute("shape");
					var imageMap = imagemapnode.item(0).getAttribute("coords").split(",");
				}
			}

			var icons = styles[i].getElementsByTagName("Icon");
			// This might not be an icon style
			if (icons.length > 0) {
				var href = this.readKMLValue(icons[0].getElementsByTagName("href")[0]);
				if (!!href) {
					var basehref = href.replace(/\.(png|gif)$/i, "");

					parsedStyles["#" + styleID] = new GIcon(G_DEFAULT_ICON, href);
					parsedStyles["#" + styleID].iconSize = new GSize(iconsizeX, iconsizeY);
					parsedStyles["#" + styleID].shadowSize = new GSize(shadowWidth, iconsizeY);
					parsedStyles["#" + styleID].iconAnchor = new GPoint(anchorX,anchorY);
					parsedStyles["#" + styleID].dragCrossAnchor = new GPoint(2,8);
					parsedStyles["#" + styleID].infoWindowAnchor = new GPoint(iwanchorX,iwanchorY);
					parsedStyles["#" + styleID].shadow = basehref + "-shadow.png";
// 					parsedStyles["#" + styleID].shadow = "";
					parsedStyles["#" + styleID].printImage = basehref + ".gif";
					parsedStyles["#" + styleID].mozPrintImage = basehref + ".gif";
					if (typeof(imagemapnode) != "undefined") {
						parsedStyles["#" + styleID].imageMapType = imageMapType;
						parsedStyles["#" + styleID].imageMap = imageMap;
					}
				}
			}

			// is it a LineStyle ?
			var linestyles = styles[i].getElementsByTagName("LineStyle");
			if (linestyles.length > 0) {
				var width = parseInt(this.readKMLValue(linestyles[0].getElementsByTagName("width")[0] ) );
				if (width < 1) {
					width = 5;
				}
				var color = this.readKMLValue(linestyles[0].getElementsByTagName("color")[0] );
				var opacity = parseInt(color.substr(0,2), 16) / 256;
				color = "#" + color.substr(6,2) + color.substr(4,2) + color.substr(2,2);
				if (!parsedStyles["#" + styleID]) {
					parsedStyles["#" + styleID] = {};
				}
				parsedStyles["#" + styleID].color = color;
				parsedStyles["#" + styleID].width = width;
				parsedStyles["#" + styleID].opacity = opacity;
			}

			// is it a PolyStyle ?
			var polystyles=styles[i].getElementsByTagName("PolyStyle");
			if (polystyles.length > 0) {
				if (!parsedStyles["#" + styleID]) {
					parsedStyles["#" + styleID] = {};
				}

				var fill = parseInt(GXml.value(polystyles[0].getElementsByTagName("fill")[0] ) );
				if (polystyles[0].getElementsByTagName("fill").length == 0) {
					fill = 1;
				}

				var outline = parseInt(GXml.value(polystyles[0].getElementsByTagName("outline")[0] ) );
				if (polystyles[0].getElementsByTagName("outline").length == 0) {
					outline = 1;
				}

				var color = this.readKMLValue(polystyles[0].getElementsByTagName("color")[0] );
				var opacity = parseInt(color.substr(0,2), 16 ) / 256;
				color = "#" + color.substr(6,2) + color.substr(4,2) + color.substr(2,2);

				parsedStyles["#" + styleID].fillcolor = color;
				parsedStyles["#" + styleID].fillopacity = opacity;
				if (!fill) {
					parsedStyles["#" + styleID].fillopacity = 0;
				}
				if (!outline) {
					parsedStyles["#" + styleID].opacity = 0;
				}
			}
			parsedStyles["#" + styleID].chkmlClassName = chkmlClassName;
		}
		return parsedStyles;
	},

	/**
	 * calculateHotSpot
	 *
	 * Calculates the x and y of a hotspot
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @param DomNode hotspot
	 * @param Integer iconsizeX
	 * @param Integer iconsizeY
	 * @param Integer anchorX
	 * @param Integer iconsizeY
	 * @return Object
	 **/
	calculateHotSpot: function(hotspot, iconsizeX, iconsizeY, anchorX, anchorY) {
		var xunits = hotspot.getAttribute("xunits");
		var yunits = hotspot.getAttribute("yunits");
		var xval = hotspot.getAttribute("x");
		var yval = hotspot.getAttribute("y");

		var anchorX = (xunits == "fraction") ? (iconsizeX * xval) : anchorX;
		var anchorX = (xunits == "pixels") ? xval : anchorX;
		var anchorX = (xunits == "insetPixels") ? (iconsizeX - xval) : anchorX;

		var anchorY = (yunits == "fraction") ? (iconsizeY * yval) : anchorY;
		var anchorY = (yunits == "pixels") ? yval : anchorY;
		var anchorY = (yunits == "insetPixels") ? (iconsizeY - yval) : anchorY;
		return {x: anchorX, y: anchorY};
	},

	/**
	 * readKMLValue
	 *
	 * Reads string value of node (uses GXml.value, then removes leading and trailing whitespace)
	 *
	 * @since Mon Feb 9 2009
	 * @access public
	 * @param DomNode node
	 * @return String
	 **/
	readKMLValue: function(node) {
		return GXml.value(node).replace(/^\s*/,"").replace(/\s*$/,"");
	},

	/**
	 * positionInfowindowRelativeToMarker
	 *
	 * Positions the window relative to the last clicked marker
	 *
	 * @since Mon Feb 2 2009
	 * @access public
	 * @param Object overlayvalue
	 * @return GPoint
	 **/
	positionInfowindowRelativeToMarker: function(overlayvalue) {
		if (typeof(overlayvalue.lastmarker) == "undefined") {
			return;
		}
		var fullheight = overlayvalue.infowindow.getFullHeight();
		if (fullheight > this._map.getContainer().getHeight() ) {
			var newheight = overlayvalue.infowindow.getContentHeight() - (fullheight - this._map.getContainer().getHeight() );
			fullheight = overlayvalue.infowindow.setHeight(newheight, null, false).getFullHeight();
		}

		var p = this._map.fromLatLngToDivPixel(overlayvalue.lastmarker.getLatLng() );
		var icon = overlayvalue.lastmarker.getIcon();
		var halfwidth = (overlayvalue.infowindow.getWidth() / 2);
		var offsety = (icon.iconAnchor.y - icon.infoWindowAnchor.y);

		p.y -= offsety;
		p.x -= (icon.iconAnchor.x - icon.infoWindowAnchor.x);
		p.y -= fullheight;
		p.x -= halfwidth;
		overlayvalue.infowindow.setX(p.x);
		overlayvalue.infowindow.setY(p.y);

		overlayvalue.infowindow.getPushpinWindowConnectorElement().setStyle( {"left": ( (halfwidth + overlayvalue.infowindow.getStyleSetting("pushpinwindowconnector-left") ) - offsety) + "px" } );

		var newy = p.y + ( (overlayvalue.infowindow.getFullHeight() + icon.iconSize.height) / 2);
		var newx = p.x + halfwidth;
		return new GPoint(newx, newy);
	},

	/**
	 * positionInfowindowRelativeToPoint
	 *
	 * Positions the window relative to the given point
	 *
	 * @since Mon Feb 2 2009
	 * @access public
	 * @param Object overlayvalue
	 * @return GPoint
	 **/
	positionInfowindowRelativeToPoint: function(overlayvalue, point) {
		var p = this._map.fromLatLngToDivPixel(point);
		var halfwidth = (overlayvalue.infowindow.getWidth() / 2);
		p.y -= overlayvalue.infowindow.getFullHeight();
		p.x -= halfwidth;
		overlayvalue.infowindow.setX(p.x);
		overlayvalue.infowindow.setY(p.y);

		overlayvalue.infowindow.getPushpinWindowConnectorElement().setStyle( {"left": (halfwidth + overlayvalue.infowindow.getStyleSetting("pushpinwindowconnector-left") ) + "px" } );

		var newy = p.y;
		var newx = p.x + halfwidth;
		return new GPoint(newx, newy);
	}
});/**
 * WJChapCB loads a Chap Contentblock
 *
 * @since Tue Dec 30 2008
 * @author Ron Rademaker
 **/
var WJChapCB = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJChapCB
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param string chapid
	 * @param Element chaptarget
	 * @return void
	 **/
	initialize: function(chapid, chaptarget) {
		this._chapid = chapid;
		this._target = $(chaptarget);
		this._load();
	},

	/**
	 * load
	 *
	 * Actualy loads the chap blocks data
	 *
	 * @since Tue Dec 30 2008
	 * @access protected
	 * @return void
	 **/
	_load: function() {
		var spin = new WJSpin();
		// NOTE: The dt is determined by Wmchapblock, it will do this automatically because the rssfeed argument is unknown
		var url = new WJUrl({
			"ct": "wmdynamic",
			"blockid": this._chapid,
			"module": "Wmchapblock",
			"wmtrigger[]": ["requestufts"],
			"uft[]": ["sideblock"]
		});
		spin.content(url, [this._target, this.updateBlock.bind(this)]);
	},

	/**
	 * updateBlock
	 *
	 * Updates the title and icon of the block
	 *
	 * @since Tue Dec 30 2008
	 * @access public
	 * @param
	 * @return void
	 **/
	updateBlock: function(response) {
		if (this._target) {
			var title = this._target.select(".chap_rsstitle").first();
			if (title) {
				this._target.up(".chap_block").select(".chap_title .chap_content h1").first().update(title.innerHTML);
			}
			var icon = this._target.select(".chap_rssicon").first();
			if (icon) {
				this._target.up(".chap_block").select(".chap_title .chap_content .chap_icon").first().update(icon.innerHTML);
			}
		}
	}
});

/**
 * WJImageBannerCB can swap image src's
 *
 * @since Fri Mar 27 2009
 * @author Giso Stallenberg
 **/
var WJImageBannerCB = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJImageBannerCB
	 *
	 * @since Fri Mar 27 2009
	 * @access public
	 * @param string chapid
	 * @param Element chaptarget
	 * @return void
	 **/
	initialize: function(img, newsrc) {
		img = $(img);
		if (img.tagName.toLowerCase() != "img") {
			if (img.down("img") ) {
				img = img.down("img");
			}
			else {
				img = false;
			}
		}
		if (!img) {
			return; // appearently nothing to swap
		}
		
		this._img = img;
		var src = this._img.src;
		src = src.replace(/(^[^\/]+\/\/[^\/]+\/)/, "/"); // cuts away http_host
		this._basepath = "";
		var basepath = src.match(/(\/images\/thumbs(\/__[^\/]*)*)\/?/); // saves thumb operation
		if (basepath) {
			this._basepath = basepath[1];
			src = src.replace(/(^([^\/]+\/\/[^\/]+)?\/images\/thumbs(\/+__[^\/]*)*)\/*/, "/"); // cuts away thumb operations so a clean mediamanager path remains
		}
		this._src = src;
		this._newsrc = newsrc;
	
		// preload the replacement image and only try the swapping if it is a loadeble image
		this._newimg = new Image();
		Event.observe(this._newimg, "load", function() {
		this._img.observe("mouseover", this.swap.bindAsEventListener(this, this._newsrc) );
		this._img.observe("mouseout", this.swap.bindAsEventListener(this, this._src) );

		}.bind(this) );
		this._newimg.src = this._basepath + this._newsrc;
	},
	
	/**
	 * swap
	 *
	 * Actually swaps the src
	 *
	 * @since Fri Mar 27 2009
	 * @access public
	 * @param Event event
	 * @param string src
	 * @return void
	 **/
	swap: function(event, src) {
		this._img.src = this._basepath + src;
	}
 });/**
 * WJTransition is the base class for transition API classes that allow you to transform some HTML element into another. The WJTransition is the base decorator for transitions.
 *
 * @since Mon Apr 20 2009
 * @author Ron Rademaker
 **/
var WJTransition = Class.create({
	/**
	 * initialize
	 *
	 * Creates the new WJTransition
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param WJElementTransition elementTransition
	 * @param object properties
	 * @return void
	 **/
	initialize: function() { 
	},
	
	/**
	 * init
	 *
	 * Real init (because it can't easily be done at create time)
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param WJElementTransition elementTransition
	 * @param object properties
	 * @return void
	 **/
	init: function(elementTransition, properties) {
		this.elementTransition = elementTransition;
		this.properties = this._getProperties(properties);
	},

	/**
	 * _getProperties
	 *
	 * Function stub to allow setting default in subclasses
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @param object properties
	 * @return object
	 **/
	_getProperties: function(properties) {
		return properties;
	},

	/**
	 * transit
	 *
	 * Transits the elementTransition
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @return void
	 **/
	transit: function() {
		var from = this.elementTransition.getFromElement();
		var to = this.elementTransition.getToElement();
		if (from && to) {
			this._doTransition(from, to);
		}
		else {
			WJDebugger.log(WJDebugger.ERROR, "Transition not ready, to and from should both be set but at least one isn't, logging both, to first then from", to, from);
		}
		this.elementTransition.notifyDone();
	},

	/**
	 * _doTransition
	 *
	 * Performs the action transition from from to to
	 * Overwrite this function to create transitions
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @param htmlelement from
	 * @param htmlelement to
	 * @return void
	 **/
	_doTransition: function(from, to) { 
		from.replace(to);
	}
});
/**
 * WJElementTransition is the base class for transitions between two html elements, this is the decoratable
 *
 * @since Mon Apr 20 2009
 * @author Ron Rademaker
 **/
var WJElementTransition = Class.create({
	/**
	 * initialize
	 *
	 * Creates a new WJElementTransition 
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param object properties
	 * @return void
	 **/
	initialize: function(properties) {
		var properties = properties || {};
		this.properties = this._getProperties(properties);
	},
	
	/**
	 * _getProperties
	 *
	 * Function stub to allow setting default in subclasses
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @param object properties
	 * @return object
	 **/
	_getProperties: function(properties) {
		return properties;
	},
	
	/**
	 * setFromElement
	 *
	 * Sets the element to transite from
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param htmlelement from
	 * @return void
	 **/
	setFromElement: function(from) {
		this.from = from;
	},

	/**
	 * setToElement
	 *
	 * Sets the element to transite to
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param htmlelement to
	 * @return void
	 **/
	setToElement: function(to) {
		this.to = to;
	},

	/**
	 * getFromElement
	 *
	 * Gets the element to transite from
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @return void
	 **/
	getFromElement: function(from) {
		return this.from
	},

	/**
	 * getToElement
	 *
	 * Gets the element to transite to
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @return void
	 **/
	getToElement: function(to) {
		return this.to;
	},

	/**
	 * notifyDone
	 *
	 * Callback for the transition to notify the elemen transition that the transition is complete. Makes the to the from to allow continuous transitions
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @return void
	 **/
	notifyDone: function() {
		this.from = this.to;
	}

});

var WJBackgroundImage = Class.create();

/**
 * WJBackgroundImage
 *
 * WJBackgroundImage is the javascript class for automated backgroundimages used by backgroundimage uft
 *
 * @since Fri May 08 2009
 * @author Reyo Stallenberg (reyo@connectholland.nl)
 **/
WJBackgroundImage.prototype = {
	/**
	 * Is the backgroundimage a string
	 *
	 * @since Fri May 8 2009
	 * @access protected
	 * @var boolean
	 **/
	_started: false,

	/**
	 * initialize
	 *
	 * Initialize a new WJBackgroundImage
	 *
	 * @since Fri May 8 2009
	 * @access public
	 * @param integer backgroundimage
	 * @param Element element
	 * @return void
	 **/
	initialize: function(backgroundimage, element) {
		if (Object.isString(backgroundimage) && !backgroundimage.strip().empty() ) {
			this.backgroundimage = backgroundimage;
			this.element = element || document.body;
			this.sizeelement = element || document.viewport;
			Element.extend(this.element);
			Event.observe(window, "resize", this.resizeImage.bindAsEventListener(this), false);
			this._started = true;
		}
		else {
			WJDebugger.log(WJDebugger.ERROR, "Trying to run", this, "without a source, you have to overwrite the backgroundImageImgSrc template in your UFT");
		}
	},

	/**
	 * toString
	 *
	 * The name of this script
	 *
	 * @since Mon May 11 2009
	 * @access public
	 * @return string
	 **/
	toString: function() {
		return "WJBackgroundImage";
	},

	/**
	 * resizeImage
	 *
	 * Resize the backgroudimage
	 *
	 * @since Fri May 8 2009
	 * @access public
	 * @param boolean resize_x
	 * @param boolean resize_y
	 * @return void
	 **/
	resizeImage: function(resize_x, resize_y) {
		if (this._started) {
			var sizeelement = this.sizeelement.getDimensions();
			// Round sizes to 25px to prevent too much images will be created and add a margin to the height for better fitting
			var height = (sizeelement.height - (sizeelement.height % 25) ) + 25 + 100;
			var width = (sizeelement.width - (sizeelement.width % 25) ) + 25;
			var image = new Image();
			Event.observe(image, "load", this.setBackgroundImage.bindAsEventListener(this, image) );
			Event.observe(image, "error", this.noBackgroundImage.bindAsEventListener(this, image) );
			if (resize_x && resize_y) {
				image.src = "/images/thumbs/__rw" + width + "h" + height + this.backgroundimage;
			}
			else if (resize_x && !resize_y) {
				image.src = "/images/thumbs/__lw" + width + "h" + (height + 3000) + this.backgroundimage;
			}
			else if (!resize_x && resize_y) {
				image.src = "/images/thumbs/__lw" + (width + 5000) + "h" + height + this.backgroundimage;
			}
			else {
				image.src = "/images/thumbs/" + this.backgroundimage;
			}
		}
	},

	/**
	 * setBackgroundImage
	 *
	 * Sets the background image
	 *
	 * @since Fri May 8 2009
	 * @access public
	 * @param integer event
	 * @param integer image
	 * @return void
	 **/
	setBackgroundImage: function(event, image) {
		WJDebugger.log(WJDebugger.INFO, "Adding a backgroundimage", image, "to", this.element);
		this.element.setStyle({backgroundImage: "url(\"" + image.src + "\")"});
	},

	/**
	 * noBackgroundImage
	 *
	 * Handles an error on the backgroundimage
	 *
	 * @since Fri May 8 2009
	 * @access public
	 * @param integer event
	 * @param integer image
	 * @return void
	 **/
	noBackgroundImage: function(event, image) {
		WJDebugger.log(WJDebugger.ERROR, "Trying to add a backgroundimage that doesn't exist", image, event);
	}
};/**
 * WJMediaTransition is the base class for transitions between two media elements, ie. images or videos
 *
 * @since Mon Apr 20 2009
 * @author Ron Rademaker
 **/
var WJMediaTransition = Class.create(WJElementTransition, {
	/**
	 * initialize
	 *
	 * Creates a new WJMediaTransition 
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param object properties
	 * @return void
	 **/
	initialize: function($super, properties) {
		$super(properties);
	},
	
	/**
	 * _getProperties
	 *
	 * Mixes in defaults for the media transition
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @param object properties
	 * @return object
	 **/
	_getProperties: function(properties) {
		var defaults = {
			width: 500,
			height: 500,
			background: "ffffff",
			backgroundResize: true,
			crop: false
		};

		for (var key in defaults) {
			var properties_keys = Object.keys(properties);
			if (properties_keys.include(key) ) {
				defaults[key] = properties[key];
			}
		}
		return defaults;
	},

	/**
	 * _getOperations
	 *
	 * Gets the mediamanager operations query for this transition's properties
	 *
	 * @since Mon Apr 20 2009
	 * @access protected
	 * @return string
	 **/
	_getOperations: function() {
		var sizeinfo = "w" + this.properties.width + "h" + this.properties.height;
		if (this.properties.backgroundResize) {
			return "__bc" + this.properties.background + sizeinfo;
		}
		else if (this.properties.crop) {
			return "__r" + sizeinfo + "/__c" + sizeinfo;
		}
		else {
			return "__l" + sizeinfo;
		}
	},

	/**
	 * setToImage
	 *
	 * Creates and sets a to html element for image
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param string image
	 * @return void
	 **/
	setToImage: function(image) {
		this.setToElement(new Element("img", {src: "/images/thumbs/" + this._getOperations() + image}));
	},
	
	/**
	 * setToVideo
	 *
	 * Creates a to html element for video
	 *
	 * @since Mon Apr 20 2009
	 * @access public
	 * @param string video
	 * @return void
	 * @todo implement flowplayer
	 **/
	setToVideo: function(video) {
		this.setToElement(new Element("img", {src: "/images/thumbs/" + this._getOperations() + video + ".png"}));
	}
});

/**
 * WJTransitionAppear implements a fade / appear transition, ie. the element closes by fading away and the new one opens by fading in
 *
 * @since Wed Apr 22 2009
 * @author Ron Rademaker
 **/
var WJTransitionAppear = Class.create(WJTransition, {
	/**
	 * initialize
	 *
	 * Initializes the transition
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @return void
	 **/
	initialize: function() {},

	/**
	 * _doTransition
	 *
	 * Implements the transition from from to to
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param htmlelement from
	 * @param htmlelement to
	 * @return void
	 **/
	_doTransition: function(from, to) { 
		to.setStyle({"display": "none"});
		from.up().insert(to);
		from.up().setStyle({"height": from.getHeight() + "px"});
		Effect.Fade(from, {duration: 1});
		Effect.Appear.bind(Effect, to).delay(1);
		from.remove.bind(from).delay(1.5);
	}
});
/**
 * WJTransitionBlind implements a blinding transition, ie. the element closes by sliding its bottom towards its top and then the new element opens by starting to slide its bottom away from the top
 *
 * @since Wed Apr 22 2009
 * @author Ron Rademaker
 **/
var WJTransitionBlind = Class.create(WJTransition, {
	/**
	 * initialize
	 *
	 * Initializes the transition
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @return void
	 **/
	initialize: function() {},

	/**
	 * _doTransition
	 *
	 * Implements the transition from from to to
	 *
	 * @since Wed Apr 22 2009
	 * @access public
	 * @param htmlelement from
	 * @param htmlelement to
	 * @return void
	 **/
	_doTransition: function(from, to) { 
		to.setStyle({"display": "none"});
		from.up().insert(to);
		Effect.BlindUp(from, {duration: 1});
		Effect.BlindDown.bind(Effect, to).delay(1);
		from.remove.bind(from).delay(1);
	}
});
