

/*
Class: 	Ev
	I have created this namespace for functions - but we should put classes in here eventually
*/
var Ev	= {
	version		: 0.1
};


function ArrayOverride(arr1, arr2) {
	for(p in arr2) {
		arr1[p] = arr2[p];
	};
	return arr1;
}



// REMOVE THIS??
if((typeof isset)=='undefined') {
    function isset(variable){
    	return (typeof variable)!='undefined';
    }    
}

// utilities
if(typeof(getCookie)=='undefined') {
    function getCookie(name) {
	Cookie.get(name);
	/*
    	var nameEQ = name + "=";
    	var ca = document.cookie.split(';');
    	for(var i=0;i < ca.length;i++) {
    		var c = ca[i];
    		while (c.charAt(0)==' ') c = c.substring(1,c.length);
    		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    	}
    	return null;*/
    }
}

/*
//if(typeof(setCookie)!='undefined') {
//	setCookie = null;
//}

//if(typeof(setCookie)=='undefined' || setCookie==null) {
    //function setCookie(cookieName, cookieValue) {
    //    document.cookie = cookieName + '='+cookieValue+'; expires=Thu, 2 Aug 2099 20:47:11 UTC; path=/';
    //}
    function setCookie(name, value) {
	Cookie.set(name, value, 999999);
    }
//}

setCookie = function(name, value) {
//	alert(name + ' / ' + value);
	Cookie.set(name, value, 999999);
}
*/



var Cookie = {
	set: function(name,value,seconds){
		if(seconds){
			d = new Date();
			d.setTime(d.getTime() + (seconds * 1000));
			expiry = '; expires=' + d.toGMTString();
		}else
			expiry = '';
		document.cookie = name + "=" + value + expiry + "; path=/";
	},
	get: function(name){
		nameEQ = name + "=";
		ca = document.cookie.split(';');
		for(i = 0; i < ca.length; i++){
			c = ca[i];
			while(c.charAt(0) == ' ')
				c = c.substring(1,c.length);
			if(c.indexOf(nameEQ) == 0)
				return c.substring(nameEQ.length,c.length);
		}
		return null;
	},
	unset: function(name){
		Cookie.set(name,'',-1);
	}
}

var setCookie = function(name, value) {
	Cookie.set(name, value, 99999);
};

var getCookie = function(name) {
	return Cookie.get(name);
}; 


/**
 * Loads JavaScriptFile and evaluates it. Use this to include JavaScript files at any point.
 */
function JSLoad(JavaScriptFile, options) {
	try {
		if(!options) var options = {};
	
		if(!options.onComplete) options.onComplete = function() { log("EMPTTY FUNCTION"); }
		//'/s/AssetRendering/edit/AssetEditing.js'
		new Ajax.Request(JavaScriptFile , { 
			onComplete : function(r) { 
				try {
					top.eval(r.responseText);
				} catch(e) {
					logError(e, 'Error including file: "' + JavaScriptFile + '" ');
				}
			}
		});
	} catch(e) {
		logError(e, 'Error including file: "' + JavaScriptFile + '" ');
	}
}

String.prototype.replaceAll=function(s1, s2) {return this.split(s1).join(s2)}



function EvRefreshElementById(id, message) {
	return;
	if(!$(id)) {
		throw new Error("Cannot update non-existant element: '"+id+"'");
	}
	if(typeof message=='undefined') {
		message = 'Reloading page - please wait';
	}
	//     ** Dim the page **
	top._refreshOverlay = new OverlayPage(
		'<div style="background-color : black; color : white; font-family : verdana, arial, sans-serif; font-size : 12pt;"><p>'+message+'<br /><img src="/s/vendor/icons/loading.gif" /></div>'
	, {
		backgroundColor : '#c0c0c0',
		opacity : 0.9
	});

	new Ajax.Request(
		document.location,  {
			onComplete : function(r) {
				try {
					var n = Builder.node('DIV');
					n.innerHTML = r.responseText;
					//alert(document.getElementsBySelector);
					// We have to do all this rubbish because IE is special
					/*                      n.id = 'tmp-node-remove';
					n.style.display = 'none';
					document.body.appendChild(n);*/

					//var m = $('tmp-node-remove');
					alert('test1');
					var b = n.getElementsBySelector('#dh-page-wrapper');
					// alert(b); alert(b[0]);
					//      $('dh-page-wrapper').innerHTML = b[0].innerHTML;
					alert('test2');
					$('dh-page-wrapper').replaceChild(b[0], $('dh-page-wrapper'));
					//              m.remove();
					//              n.remove();

				} catch(e) {
					alert('Problem updating page: ' + e.message);
				}

				try {
					setTimeout(function() {
						top._refreshOverlay.fadeDestroy();
						}, 500);
					} catch(e) {
						alert('Problem removing overlay: ' + e.message);
					}
				}
			}
		);
//     ** Bring up the page (after a second) **
}


// generic node searching function. TODO: Move to generic 'element helpers' or 'DOM helpers' or similar file.
function findAncestor(startNode, callback) {
	var searchLimit = 20;
	var notFound = true;
	var testNode = startNode;
	while(notFound && searchLimit>0) {
		if(callback(testNode)) {
			return testNode;
		}
		testNode = testNode.parentNode;
		searchLimit--;
	}
	return false;
}

/*
Class: 	OverlayPage
	Overlays the page with some content (like light-box??)
	The offset is positioned relative to the *window*, not the page, so the user should see the overlay appear in 
	the same place on the screen no matter where it's called from.

Usage:
>	new OverlayContent('overlay html content', { options... });
or
>	new OverlayContent('overlay html content', { options... });

Options:
	zIndex			- z-index of the overlay. Everything the overlay builds will be higher than this
	backgroundColor	- background colour
	onComplete		- callback. Called when overlay is done.
	beforeDestroy	- callback.
	onDestory		- callback.
	ClassName		- classname used for the overlay
	opacity			- Opacity of the overlay
	draggable		- if false, overlay contents will not be draggable. Default=true.
	allowClickAway	- If true, clicking on the background destroys the entire overlay. Otherwise, the overlay is modal. (Should rename?)
*/
var OverlayPage = new Class({
	//target		: null,
	overlayContent	: 'No overlay set',
	options 	: {
		zIndex	: 10,
		backgroundColor : 'transparent',//'#c0c0c0;',
		opacity	: 0.6,
		onComplete	: function() {},
		beforeDestroy : function() {},
		onDestroy	: function() {},
		ClassName	: 'OverlayContent',
		draggable	: true,
		allowClickAway	: false,	// If true, clicking on the background destroys the entire overlay
		backgroundClickCloses : false
	},
	
	overlayNode	: null,

	/**
	 *
	 */	
	initialize : function(overlayContent, options) {
		this.overlayContent = overlayContent;
		this.setOptions(options);

		// make the overlay wrapper
		var overlay = document.createElement('DIV');
		overlay.id = 'overlayId';

		//this.cloneSize(this.target, overlay);
		/*overlay.setStyle({
			height : document.body.getDimensions().height + 'px',
			width : document.body.getDimensions().width + 'px',
			position : 'absolute',
			backgroundColor : 'none',
			zIndex : this.options.zIndex
		});
		overlay.setOpacity(1);
		overlay.addClassName(this.options.ClassName);*/
		overlay.style.height = Element.getSize(document.body).y + 'px';
		overlay.style.width = Element.getSize(document.body).x + 'px';
		overlay.style.position = 'absolute';
		overlay.style.backgroundColor = 'transparent';
		overlay.style.zIndex = this.options.zIndex;
		overlay.style.opacity = 1;
		
		// make the overlay background 
		var background = document.createElement('DIV');
		/*background.setStyle({
			height : document.body.getDimensions().height + 'px',
			width : document.body.getDimensions().width + 'px',
			position : 'absolute',
			backgroundColor : this.options.backgroundColor,
			zIndex : this.options.zIndex + 3
		});*/
		// background.setOpacity(this.options.opacity);
		// background.setOpacity(0);
		background.style.height = Element.getSize(document.body).y + 'px';
		background.style.width = Element.getSize(document.body).x + 'px';
		background.style.position = 'absolute';
		background.style.backgroundColor = this.options.backgroundColor;// 'transparent';
		background.style.zIndex = this.options.zIndex + 3;
		background.style.opacity = 0;
		
		if(this.options['backgroundClickCloses']) {
			/*new Event.observe(
				background,
				'click',
				function (evt) {
					var n = Event.element(evt);
					OverlayContent.destroyOverlay(n);
				}
			);*/
		}
		
		// for the overlay itself
		var foreground = document.createElement('DIV');
		foreground.style.marginLeft = '25%';
		// This makes the offset relative to the scroll and size of window

		// Workaround for IE -- should push this into a single function 
		var windowHeight = (window.innerHeight) ? window.innerHeight : document.documentElement.clientHeight;
		var pageYOffset = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;
		var topOffset = (((100/800)*windowHeight) + pageYOffset);
		foreground.style.marginTop = (topOffset + 'px');
		
		foreground.style.position = 'absolute';
		foreground.style.zIndex = (this.options.zIndex + 5);
		foreground.innerHTML = overlayContent;
		//	foreground.setOpacity(0);
		//	background.setOpacity(0);
		foreground.style.opacity = 0;
		background.style.opacity = 0;
		overlay.appendChild(background);
		overlay.appendChild(foreground);
		
		try{	
			// target.parentNode.insertBefore(overlay, target);
			document.body.insertBefore(overlay, document.body.firstChild);
			//new Effect.Appear(foreground, { duration : 0.5 });
			foreground.morph({
				opacity : 1
			});
			background.morph({
				opacity : this.options.opacity
			});
			//new Effect.Opacity(background, { from : 0, to : this.options.opacity, duration : 0.5 });
		} catch(e) {
			logError(e, 'Failed to create overlay');
		}
		
		this.onComplete = this.options.onComplete;
		
		overlay.OverlayContent = this;
		this.overlayNode = overlay;
		
		if(this.options.draggable) {
			new Draggable(foreground);
		}
		//removeOverlay = this.destroy;
		if(overlayContent.activate) overlayContent.activate(this);
	},
	
	destroy : function() {
		this.options.beforeDestroy();
		// this.overlayNode.remove();
		$(this.overlayNode.id).remove();
		this.options.onDestroy();
	},
	
	fadeDestroy : function() {
		this.overlayNode.tween({
			opacity : [this.options.opacity, 0]
		});
		
		top._destroyOverlayPage = this;
		setTimeout(function() {
			top._destroyOverlayPage.destroy();
			top._destroyOverlayPage = null;
		}, 550);
	},
	
	setOptions : function(options) {
		for(p in options) {
			this.options[p] = options[p];
		}
	}
});


// TODO: Move overlay stuff to a 'visual widgets'file. 
/*
Class: 	OverlayContent
	Overlays a particular node with some HTML 
todo:
	add ability to resize the overlay or underlay nodes so they're the same size.
Usage:
>	new OverlayContent('someId', 'overlay html content', { options... });
or
>	new OverlayContent(someNode, 'overlay html content', { options... });

Options:
	zIndex			- z-index of the overlay. Everything the overlay builds will be higher than this
	backgroundColor	- background colour
	onComplete		- callback. Called when overlay is done.
	beforeDestroy	- callback.
	onDestory		- callback.
	ClassName		- classname used for the overlay
	opacity			- Opacity of the overlay
	destroyOnMouseout - if true, mouse out triggers destroying the overlay
*/
var OverlayContent = new Class({
	
	/*
	Constructor: initialize
		See usage, above.
	*/	
	initialize : function(target, overlayContent, options) {
		//if((typeof target)=='string') target = $(target);
		//log('setting up with: ' + target + '/' + overlayContent);
		this.target		= null;
		this.overlayContent	= 'No overlay set';
		this.options 	= Object.extend({
			zIndex	: 10,
			backgroundColor : 'blue',
			onComplete	: function() {},
			beforeDestroy : function() {},
			onDestroy	: function() {},
			ClassName	: 'OverlayContent',
			opacity 	: 0.4,
			destroyOnMouseout	: false,
			width		: null
			// TODO: Add matchHeight option
		}, options);

		this.overlayNode = null;

		this.target = $(target);
		
		if(this.target==null) {
			throw new Error("this.target is undefined - possibly trying to put overlay on non-existant node: " + target);
		}
		
		this.overlayContent = overlayContent;
		this.setOptions(options);
		
		this._removalInProgress	= false;
		
		// make the overlay wrapper
		var overlay = document.createElement('DIV');
		overlay.id = 'overlayId';
		
		// BUG: Overlay problem - use Position.clone if overlay is sibling of targetNode; use this.cloneSize if overlay
		// is child of targetNode
		this.cloneSize(this.target, overlay);
		
		// TODO: The clone() function here seems to fix the overlay moving around the page - but I'm not 100% sure why. Test in some other browsers.
		/*try {
			clone =  function(source, target) {
			    source = $(source);
			    target = $(target);
			    target.style.position = 'absolute';
			    // var offsets = Position.realOffset(source);
			    var offsets = Position.cumulativeOffset(source);
			    target.style.top    = offsets[1] + 'px';
			    target.style.left   = offsets[0] + 'px';
			    target.style.width  = source.offsetWidth + 'px';
			    target.style.height = source.offsetHeight + 'px';
			  }
			//Position.clone(target, overlay);
			clone(target, overlay);
		} catch(e) {
			logError(e, 'asdasdsa');
		}***/
		
		/*overlay.setStyle({
			position 	: 'absolute',
			backgroundColor : 'none',
			zIndex : this.options.zIndex,
			height : 'auto'
		});*/
		// TODO: Make a nice setStyle function to make this look nicer.
		overlay.style.position = 'absolute';
		overlay.style.backgroundColor = 'transparent';
		overlay.style.zIndex = this.options.zIndex;
		overlay.style.height = 'auto';
		overlay.opacity = 0;
		//overlay.setOpacity(1);
		// set above overlay.setOpacity(0);
		// MOVED overlay.addClassName(this.options.ClassName);
		
		// make the overlay background 
		var background = document.createElement('DIV');
		this.cloneSize(this.target, background);
		if(this.options.width!=null) {
			background.style.width = (this.options.width + 'px');
		}
		/*background.setStyle({
			position	: 'absolute',
			backgroundColor	: this.options.backgroundColor,
			zIndex	: this.options.zIndex + 3
		});*/
		background.style.position = 'absolute';
		background.style.backgroundColor = this.options.backgroundColor;
		background.style.zIndex = this.options.zIndex + 3;
		
		//MOVED	background.setOpacity(this.options.opacity);
		
		// for the overlay itself
		var foreground = document.createElement('DIV');
		//this.cloneSize(this.target, foreground);
		this.cloneWidth(this.target, foreground);
		if(this.options.width!=null) {
			foreground.style.width = (this.options.width + 'px');
		}
		/*foreground.setStyle({
			position	: 'absolute',
			zIndex	: this.options.zIndex + 5
		});*/
		foreground.style.position = 'absolute';
		foreground.style.zIndex = this.options.zIndex + 5;

		foreground.innerHTML = overlayContent;

		overlay.appendChild(background);
		overlay.appendChild(foreground);

		//log(target);
		try{
			// BUG: targetNode isn't draggable if overlay is actually on TOP 	
			target.insertBefore(overlay, target.firstChild);
			//target.parentNode.insertBefore(overlay, target);
			new Effect.Appear(overlay, { duration : 0.3 });
		} catch(e) {
			logError(e, 'Failed to create overlay');
		}
		
		/*if(this.target.getHeight() < foreground.getHeight()) {
			this.target.setStyle({height : foreground.getHeight() + 'px'});
		}*/
		var dimensions = Element.getSize(foreground);
		if(this.target.getHeight() < dimensions.y) {
			this.target.style.height = dimensions.y + 'px';
		}
		
		this.onComplete = this.options.onComplete;
		
		overlay.OverlayContent = this;
		
		this.overlayNode = overlay;
		//removeOverlay = this.destroy;
		
		// BEGIN: Destroy / trigger mouse out
		
		if(this.options.destroyOnMouseout) {
			var _this = this;
			Event.observe(
				overlay,
				'mouseout', 
				function(evt) {
					var ancestors;
					if((typeof evt.relatedTarget)!='undefined') {
						aL = evt.relatedTarget.ancestors();
					} else {
						aL = evt.fromElement;	//IE FIX
					}
					var l = aL.length;
					var i = 0;
					var foundOverlay	= false;
					//log('Checking');
					for(i=0;i<l;i++) {
						//log(' = ' + aL[i]);
						if((typeof aL[i].OverlayContent)!='undefined') {
							foundOverlay = true;
						}
					}
					if(!foundOverlay) {
						_this.destroy();
					}
				}
				);
		}
		// END: //
		
		//// *** MOVED TO HERE *** \\\\
		//background.setOpacity(this.options.opacity);
		background.style.opacity = this.options.opacity;
		overlay.addClass(this.options.ClassName);
	},
	
	// Remove the overlay. If the overlay isn't there any more, we do nothing (No errors thrown).
	destroy : function() {
		try {
			this.options.beforeDestroy();
			// this.target.setStyle({height : 'auto'});
			// Changed for browser compat. Was: this.overlayNode.remove();
			$(this.overlayNode).remove(); // This was: $(this.overlayNode.id) - but this caused errors.
			this.options.onDestroy();
		} catch(e) {
			logError(e, 'Problem removing overlay - this usually means the overhas already been removed.', logger.DETAIL);
		}
	},
	
	setOptions : function(options) {
		for(p in options) {
			this.options[p] = options[p];
		}
	},
	
	cloneSize : function(sourceNode, targetNode) {
		targetNode.style.width=sourceNode.getSize().y + 'px';
		targetNode.style.height=sourceNode.getSize().x + 'px';
	},
	
	cloneWidth : function(sourceNode, targetNode) {
		targetNode.style.width=sourceNode.getSize().y + 'px';
	}
});



/*
Function: OverlayContent.destroyOverlay
	Statically called function which find the nearest overlay wrapping "node" and destroys it
*/
OverlayContent.destroyOverlay = function(startNode) {
	var n = findAncestor(startNode, function(testNode) {
		if((typeof testNode.OverlayContent)!='undefined') {
			return true;
		} else {
			return false;
		}
	});
	try {
		n.OverlayContent.destroy();
	} catch(e) {
		logError(e, 'Failed to remove overlay');
	}
}



/*
Class: 	SortableList
	Makes lists (li tags) sortable and editable. Keeps the sorted items in a hidden HTML field with name=htmlFieldName
	
Parameters:
	targetNode	 	- id or node
	htmlFieldname	- HTML field name to keep the sorted items in
	options			- see below
	
Options:
	editable		- boolean. If true, each item can be clicked to edit.
	addItems		- boolean. if true, user can add items to the list.
	deleteItems		- boolean. If true, user can remove items from the list. Default: true.
	false			- boolena. If true, the values are stored in a single field in CSV format.
	
Usage:
>	new SortableList(targetListNode, 'html_field')
*/
var SortableList = {};
SortableList = Class.create();
SortableList.prototype = {

	initialize : function(id, htmlFieldName, options) {
		this.targetNode = $(id);
		this.htmlFieldName = htmlFieldName;
		this.options = Object.extend({
			editable : true,
			addItems : false,
			deleteItems : true,
			csv	: false
		}, options);
		
		
		// Make the hidden fields etc
		this._hiddenFieldNode = Builder.node(
			'div', 
			{ id : 'hidden-fields' },
			'Hidden fields' );
		this.targetNode.parentNode.insertBefore(this._hiddenFieldNode, this.targetNode);
		
		this.initializeChildNodes();
		
		this.makeSortable();
		
		if(options.addItems) {
			this.createAddItemButton();
		}
		
		this.serializeToHiddenFields();
	},
	
	createAddItemButton : function() {
		var addButton = Builder.node('DIV',{
			 'class' : 'sortableItem'
		}, 'Add item');
		
		// We need to position the 'add' button directly below the list, but *not* in the list
		this.targetNode.parentNode.appendChild(
			addButton, this.targetNode
			);
		
		var _this = this;
		new EditableText(addButton, {
			onSubmit : function(result) {
				_this.addItem(result);
			}
		});
	},
	
	newItemCount : 0,
	addItem : function(newItemValue, newItemId) {
		this.newItemCount++
		if(!newItemId) newItemId = 'NEW_' + (this.newItemCount);
		var newItem = Builder.node('LI',{
			id : newItemId,
			'class' : 'sortableItem'
		}, newItemValue);
		
		this.targetNode.appendChild(newItem);
		this.initializeNode(newItem);
		new Effect.Highlight(newItem);
		this.makeSortable();
		this.serializeToHiddenFields();
	},
	
	// Add a handle to the item. Put the text in a wrapper. Make it editable
	initializeChildNodes : function() {
		var children = $(this.targetNode).childNodes;
		if(typeof children=='undefined') {
			alert('Failed to serialize sortable.');
		}
		var _this = this;
		for(var i=0;i<children.length;i++) {
			var cNode = children[i];
			this.initializeNode(cNode);
		}
	},
	
	initializeNode : function(node) {
		var _this = this;
		
		node._SortableList = this;
		
		node._originalValue = node.innerHTML;
		node.innerHTML = '';
		
		if(this.options.deleteItems) {
			node.innerHTML = '<a class="delete" title="Delete item" style="margin-right : 10px;" href="#" onClick="var p = this.parentNode; this.parentNode.remove(); p._SortableList.serializeToHiddenFields(); return false;"><img src="/s/vendor/icons/bin.jpg" alt="Delete item" title="Delete item" /></a>DELETE THIS &nbsp;&nbsp;';
		}
		
		node.innerHTML += '<span class="handle" style="margin-right : 10px;"><img src="/s/vendor/icons/arrows.jpg" alt="Drag item up or down" title="Drag item up or down" /></span>&nbsp;&nbsp;'+
			'<span class="sortable-value">' + node._originalValue + '</span>';
		
		if(this.options.editable) {
			var l = node.getElementsByClassName('sortable-value');
			var editableNode = l[0];
			
			//new EditableText( node, {
			new EditableText( editableNode, {
				onSubmit : function(value) {
					this.targetNode.innerHTML = value;
					this.targetNode._originalValue = value;
					//_this.initializeNode(node);
					_this.makeSortable();
					_this.serializeToHiddenFields();
				},
				editValue : this.getValueOfListItem(node)
			});
		}
	},
	
	makeSortable : function() {
		var _this = this;
		
		Sortable.create($(this.targetNode), {
			onUpdate : function() {
				try {
					_this.serializeToHiddenFields();
				} catch(e) {
					alert(e.message);
				}
			},
			handle : 'handle'
		});
	},
	
	// copied the id/innerHTML values into name/values of hidden fields
	serializeToHiddenFields : function() {
		this._hiddenFieldNode.innerHTML = 'setting up';
		var children = $(this.targetNode).childNodes;
		if(typeof children=='undefined') {
			alert('Failed to serialize sortable.');
		}		
		this._hiddenFieldNode.innerHTML = '';//'l='+children.length+Math.random();
		if(this.options.csv) {
			
			// ** New method - store in CSV
			var first = true;
			var str = '';
			for(var i=0;i<children.length;i++) {
				var fldName = children[i].id;
				fldName = fldName.replace(/value_/, "");
				
				if(!first) {
					str += ',';
				} else {
					first = false;
				}
				
				str += fldName;
				
			}
			
			var n = Builder.node(
				'input', {
					type : 'hidden',
					name : this.htmlFieldName,
					value : str
				} );
			this._hiddenFieldNode.appendChild(n);
			
		} else {
			// ** Standard method -- stored in an Array
			for(var i=0;i<children.length;i++) {
			
				var fldName = children[i].id;
				fldName = fldName.replace(/value_/, "");
				//alert(fldName);
				var n = Builder.node(
					'input', {
						type : 'hidden',
						name : this.htmlFieldName + '['+fldName+']',
						value : this.getValueOfListItem(children[i])
					} );
				this._hiddenFieldNode.appendChild(n);
			}
		}
	},
	
	getValueOfListItem : function(node) {
		var nL = node.getElementsByClassName('sortable-value');
		labelNode = nL[0];
		return labelNode.innerHTML;//+'*VALUE*';
	}
}

/*
Class: 	SimpleModal
	Simple modal forms. 
	This requires ExtJS.
	
	Note: [DF] I created SimpleModal because we need to load in content over AJAX and have it evaluated. Extjs didn't seem to do this natively.



Usage:
	new SimpleModal('this is some content...', {...options...});
	
Options:
	title	- title of the modal
	height	- integer
	width 	- integer
	scroll	- boolean
*/
SimpleModal = {}
SimpleModal = new Class();//Class.create();

/*
Function: SimpleModal.close()
	Call this to close the most recently openned modal
*/
SimpleModal.close = function() {
//alert(SimpleModal.openModals);
/*
	if(SimpleModal.openModals<=1) {
		setTimeout(function() {
alert('fixing');
document.body.style.overflow = 'scroll';
document.body.style.height = 'auto';
}, 1000);
	}
*/	
	try {
		var s = SimpleModal.openModals.pop();
		s.close();
	}catch(e) {
		alert('SimpleModal.close: ' + e.message);
	}
}

/*
Function: SimpleModal.reload
	Reloads the top-most modal
*/
SimpleModal.reloadCaller = function() {
//	var s = SimpleModal.openModals[SimpleModal.openModals.length-2]);
}


SimpleModal.openModals	= [];

SimpleModal.prototype = {
	
	initialize : function(HTML, options) {



function getViewportDimensions() {
  var myWidth = 0, myHeight = 0;
  if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myWidth = window.innerWidth;
    myHeight = window.innerHeight;
  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
    myWidth = document.documentElement.clientWidth;
    myHeight = document.documentElement.clientHeight;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
    myHeight = document.body.clientHeight;
  }
  return {
    height : myHeight,
    width : myWidth
  };
//  window.alert( 'Width = ' + myWidth );
//  window.alert( 'Height = ' + myHeight );
}
//alertSize();		
var windim = getViewportDimensions();

	/*
		document.body.style.overflow = 'hidden';
//		if(!SimpleModal._windim) SimpleModal._windim = windim;
		document.body.style.height = ((windim['height']-10) + 'px')
		*/
//alert(document.body.getDimensions());
		/*
		Property: isClosed
			Set to true once the modal is removed. Ensures we don't try to remove 'this' from .openModals
		*/
		this.isClosed = false;
		
		this.options = ArrayOverride({
			onClose : function() {},
			title	: 'Dialog title',
			height	: 300,
			width 	: 300,
			scroll	: true,
			resizable:true,
			// Not a very nice way of doing this -- should replace??
			reloadCallerOnClose	: false
		}, options);
		
		var n = new Element('DIV', {
		//	'style'	: 'clear : both;',
			'class' : 'x-hidden'
		});
		document.body.appendChild(n);
		
		this._wrapperId	= 'simplemodal-' + (SimpleModal.openModals.length+1);
		
		
		HTML = '<div id="' + this._wrapperId + '">' + HTML + '</div>';
		
		//n.id = 'dialogWrapper';
		n.innerHTML = '<div class="x-window-header">'+this.options.title+'</div><div id="dialogInner" class="x-window-body"">'+HTML+'</div>';
		//'<img src="/s/vendor/icons/loading.gif" alt="Loading" /></div>';
		
		this.dialogInner	= n;
		
		// TODO: Doesn't work -- can't get the modal to resize.
		//var dim	= this.dialogInner.getDimensions();
		
		this.dialog = new Ext.Window({
				applyTo : this.dialogInner,
				
				//title : this.options.title,
				shim 	: false,
				width	: this.options.width,
				height	: this.options.height,
				
				minHeight:300,
				minimizable : false,
				shadow	: false,
				modal	: true,
				overflow:'auto',
				autoScroll: (this.options.scroll),
				bufferResize : true,
				resizable : this.options.resizable,
				
				plain	: true
			});
 		
		// Callback for onClose
		var _this = this;
		this.dialog.addListener('close', function() {
			// Let onClose callback run first
			_this.options.onClose();
			
			// Remove 'this' from the openModals
			if(!_this.isClosed) {
				SimpleModal.openModals.pop();
				_this.isClosed = true;
			}
			
			/*
			if(SimpleModal.openModals<=1) {
				document.body.style.overflow = 'scroll';
				document.body.style.height = 'auto';
			}*/
				
			// If we need to refresh the caller
			/* *** Shouldn't call this, because 
			if(_this.options.reloadCallerOnClose) {
				var s = SimpleModal.openModals[SimpleModal.openModals.length-1];
				if(s) {
					s.loadFromURL();
				}
			}*/
		});
		this.dialog.show();
		
		var pos = this.dialog.getPosition();
		var y = pos[1];
		if(y<30) {
			y = 30;
			this.dialog.setPosition(pos[0], y);
		}
		
		this.dialog.setPosition(pos[0], 100);
		// load from url if required
		if(this.options.url) {
			this.loadFromURL();
		}
		this.dialog.setPosition(pos[0], 100);
		SimpleModal.openModals.push(this);
	},
	
	/*
	Function: loadFromURL
		Loads the url (options.url) into the modal. Replaces whatever HTML the modal was created with.
	*/
	loadFromURL	: function() {
		if(typeof this.options.url=='undefined') {
			return;
		}
		try {
			new Request.HTML({
				update	: $(this._wrapperId),
				url 	: this.options.url,
				onSuccess : function() { }
			}).send();
			
		} catch(e) {
			logError(e, ('Error loading from URL to ' + this._wrapperId));
		}
	},
	
	/*
	Function : resize 
		Resize the dialog based on the content of the div we setup at the beginning
	*/
	resize : function() {
		var d = $(this._wrapperId).getSize();
		if(d.y>100) {
			this.dialog.setHeight((d.y+40));
		} else {
		//	alert('NOT resizing...');
		}
	},
	
	/*
	Function: close
		Close the dialog. 
	*/
	close	: function() {
		if(this.options.reloadCallerOnClose) {
			// We expect that 'this' has already been removed from .openModals so get the top-most modal
			var s = SimpleModal.openModals[SimpleModal.openModals.length-1];
			if(s) {
				s.loadFromURL();
			}
		} else {
			//
		}
		
		this.isClosed = true;
		
		this.dialog.close();
	}
};



/*
Class: Rocker
	Simple rocker switch

Usage:
>	new Rocker($('rocker'), {
>		on 	: '>>>',
>		off	:'<<<',
>		callback : function() {
>			//...
>		}
>	});


*/		
Rocker = new Class();
Rocker.ON	= 1;
Rocker.OFF	= -1;
Rocker.count= 0;
Rocker.prototype	= {
		
	initialize : function(targetNode, options) {
		this.targetNode = $(targetNode);
		this.position = Rocker.OFF;
		
		this.options = ArrayOverride({
			value	: null,
			off 	: 'Off',
			on 		: 'On',
			offStyle: {
				/*backgroundColor : 'red'*/
				//marginLeft	: 0
			},
			onStyle : {
				/*backgroundColor : 'blue'*/
				//marginLeft : 27
			},
			callback:function() {
			//	alert('Toggle!');
			}
		}, options);
		
		this.togglers = {};
		this.togglers[Rocker.ON] =  options.on;
		this.togglers[Rocker.OFF] = options.off;
		
		this.rocker_id = 'rocker-id-' + Rocker.count;
		//var sw = Builder.node('DIV', {
		var sw = new Element('DIV', {
			style : 'width 10px;',
			id	  : this.rocker_id
		}, '!!'); 
		this.targetNode.appendChild(sw);
		
		var _this = this;
		
		//Event.observe($(this.targetNode), 'click', function() {
		$(this.targetNode).addEvent('click', function() {
			_this.toggle();
		});
		
		if(this.options.value==Rocker.ON) {
			this.position = this.options.value;
			$(this.rocker_id).innerHTML = this.togglers[this.position];
			//$(this.targetNode).setStyles(this.options.onStyle);
			
			/*
			var n = $('rocker-id-0');

			var effect = new Fx.Morph(n);

			effect.start({
			 'marginLeft' : [0,37]
			});*/
			
			// $(this.targetNode).tween(this.options.onStyle, {
			// 				duration : 0.5
			// 			});
			//
			
			var moveby = 27 * this.position;
			$(this.rocker_id).setStyles({
				marginLeft : moveby
			});
			
			/*Effect.MoveBy($(this.rocker_id),
				0,
				moveby,
				{ duration : 0.2 } );
			*/
		} else
		if(this.options.value==Rocker.OFF) {
			this.position = this.options.value;
			$(this.rocker_id).innerHTML = this.togglers[this.position];
			$(this.targetNode).setStyles(this.options.offStyle);
		}
	},
	
	setPosition : function(rockerPosition) {
		if(rockerPosition==this.position) return;
		
		if(rockerPosition==Rocker.ON) {
			this.position = rockerPosition;
			$(this.rocker_id).innerHTML = this.togglers[this.position];
			
			$(this.targetNode).setStyles(this.options.onStyle);
			
			/*Effect.MoveBy($(this.rocker_id),
				0,
				moveby,
				{ duration : 0.2 } );
			*/
		} else
		if(rockerPosition==Rocker.OFF) {
			this.position = rockerPosition;
			$(this.rocker_id).innerHTML = this.togglers[this.position];
			$(this.targetNode).setStyles(this.options.offStyle);
		}
		
		var moveby = 27 * this.position;
		try {
			var effect = new Fx.Morph($(this.rocker_id), {
				
			});
		
			if(this.position==1) {
				effect.start({
					'marginLeft' : [0,27]
				});
			} else {
				effect.start({
					'marginLeft' : [27,0]
				});
			}
		} catch(e) {alert(e.message);}
		
	},

	toggle : function() {
		try {
			this.position = -1 * this.position;
			var moveby = 27 * this.position;
		
			var bgColor;
			if(this.position==1) {
				$(this.targetNode).setStyles(this.options.onStyle);
			} else 
			if(this.position==-1) {
				$(this.targetNode).setStyles(this.options.offStyle);
			}
			
			try {
				var effect = new Fx.Morph($(this.rocker_id), {
					
				});
			
				if(this.position==1) {
					effect.start({
						'marginLeft' : [0,27]
					});
				} else {
					effect.start({
						'marginLeft' : [27,0]
					});
				}
			} catch(e) {alert(e.message);}
			
			this.options.callback(this.position);
			
			var rocker = $(this.rocker_id)
			var updateInner = this.togglers[this.position];
			setTimeout(function() {
				$(rocker).innerHTML = updateInner;
			}, 400);
			
		} catch(e) {
			alert(e.message);
		}
	}
}


evDD = new Class({
	
	initialize : function(targetNode, options) {
		this.targetNode = $(targetNode);
		
		/*this.options = Object.extend({
			menuWidth : 80,
			items : []
		}, options);
		*/
		this.options = ArrayOverride({
			menuWidth : 80,
			items : [],
			selected_item : null
		}, options);
		
		// set up observe on target node
		var _this = this;
		try {
			//Event.observe(_this.targetNode,'click', function(e) {
			$(_this.targetNode).addEvent('click', function(e) {
				_this.showMenu();
				Event.stop(e);
			});
			
			//Event.observe(document, 'click', function() {
			$(document).addEvent('click', function(e) {
				try {
					_this.hideMenu();
				} catch(e) {}
			});
			
		} catch(e) {
			alert(e.message);
		}
	},
	menuNode : null,
	
	showMenu : function() {
		//this.targetNode
		log('showing...');
		if(this.menuNode!==null) {
			$(this.menuNode).setStyles({
				display : 'block'
			});
			return;
		}
		
		this.menuNode = new Element("DIV", {'class' : 'inv-dd-menu-wrapper'}); //Builder.node("DIV", {'class' : 'inv-dd-menu-wrapper'});
		
		try {
			this.menuNode.setStyles({
				width : (this.options.menuWidth + 'px')
			});
		} catch(e) {
			this.menuNode.style.width = (this.options.menuWidth + 'px');
		}
		this.targetNode.appendChild(this.menuNode);
		
		// this.options.items
		//top.lookatme = this;
		
		var i = 0;
		var addHTML = '';
		for(i=0;i<this.options.items.length;i++) {
			//var item = Builder.node('DIV', {
			var item = new Element('DIV', {
				'class' : 'inv-add-menu-item-wrapper'
			});
			
			var selected_class = '';
			if(this.options.selected_item==this.options.items[i].text) {
				selected_class = ' inv-add-menu-item-content-selected ';
			}
			
			item.innerHTML = '<div class="inv-add-menu-item-content'+selected_class+'"><a href="#" class="inv-add-menu-link" onclick="return false;">'+this.options.items[i].text+'</a></div>';
			// DF. I put the callback inside the menu node because of a scope problem passing 'callback' to the event, below.
			item._callback = this.options.items[i].callback;
			///alert(item._callback);
			
			this.menuNode.appendChild(item);
			
			var _this = this;
			//Event.observe(item, 'click', function(e) {
			$(item).addEvent('click', function(e) {
				try {
					top.thise = e;
					
					var cb = findAncestor(e.target, function(elmnt) {
						return (typeof elmnt._callback!='undefined');
					});
					
					_this.hideMenu();
					//Event.stop(e);
					e.stopPropagation();
					
					cb._callback();
					//alert(cb._callback);
					
				} catch(e) { alert(e.message); }
				
			});
		}
	},
	
	hideMenu : function() {
	
		try {
			if(this.menuNode!==null) {
				$(this.menuNode).setStyles({
					display : 'none'
				});
			}
		} catch(e) { alert(e.message); }
	}
	
});


/*
Class: 	UndoDropdown
	
*/
UndoDropdown = {}
UndoDropdown = Class.create();

UndoDropdown.prototype = {
	
	initialize : function(target, options) {
		
		target = $(target);
		
		options = Object.extend({
			undos : [{
					title	: 'undo 1',
					description : 'Description 1',
					callback : function() {
						alert('Undo 1')
					}
				},{
					title	: 'undo 2',
					description : 'Description 2',
					callback : function() {
						alert('Undo 2')
					}
				},{
					title	: 'undo 1',
					description : 'Description 1',
					callback : function() {
						alert('Undo 1')
					}
				},{
					title	: 'undo 2',
					description : 'Description 2',
					callback : function() {
						alert('Undo 2')
					}
				}
			]
		}, options);
		
		this.dd = Builder.node('DIV', {
			'class'  : 'inv-dd-menu-wrapper'
		});
		target.appendChild(this.dd);
		
		this.headerNode = Builder.node('DIV', {
			style : 'background-color : #404040; color : white; display : none;'// [DF - this isn't finsihed. It should show the last change]
		}, 'testing...');
		this.dd.appendChild(this.headerNode);
		
		this.undoList = Builder.node('DIV', {
			'class'  : 'inv-dd-menu-wrapper',
			'style'	 : 'width : 200px; display:none;'
		});
		this.dd.appendChild(this.undoList);
		
		for (var i=0; i < options.undos.length; i++) {
			this.addItemToList(
				options.undos[i].title, 
				options.undos[i].description,
				options.undos[i].callback
			);
		};
		
		var _this = this;
		try {
			Event.observe(target,'click', function(e) {
				_this.showMenu();
				Event.stop(e);
			});
		
			Event.observe(document, 'click', function() {
				_this.hideMenu();
			});
			
		} catch(e) {
			alert(e.message);
		}
	},
	
	showMenu : function() {
		// unhide
	//	this.undoList.setStyle({'display : block'});
	this.undoList.setStyle({'display' : 'block'});
	},
	
	hideMenu : function() {
		//hide
		this.undoList.setStyle({'display' : 'none'});
	},
	
	listCounter : 0,
	
	addItemToList : function(title, description, callback) {
		this.listCounter++;
		var bgClass = (this.listCounter%2==0) ? 'UndoDropdown-even' : 'UndoDropdown-odd';
		
		var undoLink = '<div class="inv-undo-link"><img src="/s/vendor/icons/involve/undo-on.png" /></div>';
		
		var uitem = Builder.node('DIV', {
			'class' : bgClass + ' inv-add-menu-item-wrapper'
		});
		uitem.innerHTML = 
			'<div class="inv-add-menu-item-content">' + 
			'<div style="float : left; width : 130px;"><a style="font-weight : bold;">'+title+'</a><br />' + 
			description + 
			'</div>' + 
			undoLink + 
			'<div style="clear :both;">&nbsp;</div></div>';
		
		//this.undoList.appendChild(uitem);
		if(this.undoList.firstChild) {
			this.undoList.insertBefore(uitem, this.undoList.firstChild);
		} else {
			this.undoList.appendChild(uitem);
		}
		
		// setup callback
		var cb = callback;
		Event.observe(
			uitem,
			'click',
			callback
			);
		
		this.setHeaderItem(title);
	},
	
	setHeaderItem : function(title) {
		this.headerNode.innerHTML = title;
	}
	
};