/*
	$Id: dialog.class.js,v 1.10 2006/10/21 12:37:03 bob Exp $
	(C) Copyright 2006 Fellownet.
	All rights reserved.

	Usefull urls:
	- http://www.sergiopereira.com/articles/prototype.js.html
	- http://wiki.script.aculo.us/scriptaculous/show/CoreEffects
	- http://blogs.ebusiness-apps.com/jordan/pages/Prototype%20Library%20Info.htm
	- http://www.gotapi.com/index.html
	- http://tagsoup.com/-dev/null-/css/fixed/
*/

var dialog = Class.create();

var __dialogs = new Array();
var __zIndex = 100;
var __dialogIE = false;

dialog.prototype = {
	initialize: function(options_) {
		/**
		 * This is the constructor function for the dialog class. It accepts exactly one parameter which override the
		 * set of default parameters.
		 */
		this.options = {
			width:300,
			name:'Untitled',
			title:false,
			content:'Content',
			buttons:'Close',
			buttons_disabled:'',
			button_default:false,
			button_cancel:false,
			background:'#CACDCE',
			duration:0,
			effect:'blind',
			source:'',
			method:'get',
			parameters:'',
			modal:false,
			standalone:false,
			tag:false,
			callback:function(button_, dialog_) { dialog_.close(); }
		}
		Object.extend(this.options, options_ || {} );

		var self = this;

		// Let's create a new unique id for the current dialog. This is necessary to identify it's various dom objects.
		if (__dialogs.length > 0) {
			// Let's see if there are any dialogs visible at the moment. A modal dialog cannot be set visible if there
			// are other dialogs visible.
			var hasVisibles = __dialogs.any( function(dialog_) {
				return ! dialog_.options['standalone'] && dialog_.visible();
			} );
			if (hasVisibles && this.options['modal']) {
				// This dialog is modal which means it can't be opened unless it's the only one visible at this moment.
				__dialogs.invoke('flash');
				this.show = this.hide = this.enable = this.disable = function() { /* disable these dialog functions */ }
				return;
			} else this.id = __dialogs.last().id + 10;
		} else {
			// This is the first dialog that is to be created. We're going to use it to attach some event handlers and
			// set the starting dialog id. The event handlers are used to capture keypress events that can trigger 
			// default dialog behaviour such as dialog cancellation.
			this.id = 100;
			Event.observe(document, 'keypress', this.onkeypress.bindAsEventListener(this), true);
		}

		this.div_container = document.createElement('div');
		this.div_container.setAttribute('id', 'div_container_' + this.id);
		this.div_container.className = 'dialog';
		this.div_container.style.visibility = 'hidden';

		// On Internet Explorer we need to create an iframe that covers the entire back of the dialog. This prevents
		// windowed controls to appear through the dialog.
		if (__dialogIE) {
			this.iframe = document.createElement('iframe');
			this.iframe.className = 'hidden';
			this.div_container.appendChild(this.iframe);
		}

		// Then we're going to create the inner guts of the dialog. To create the borders and paddings we're going to
		// use several seperate div layers.
		this.div_outer = document.createElement('div');
		this.div_outer.className = 'outer';
		this.div_outer.style.backgroundColor = this.options['background'];
		this.div_container.appendChild(this.div_outer);

		this.div_inner = document.createElement('div');
		this.div_inner.className = 'inner';
		this.div_outer.appendChild(this.div_inner);

		// If there's a title present for the current dialog box we're going to create a DOM element for it below.
		if (this.options['title']) {
			this.div_header = document.createElement('div');
			this.div_header.className = 'header';
			this.div_header.innerHTML = this.options['title'];
			this.div_inner.appendChild(this.div_header);
		}

		// Then we're going to create the bounding box for the actual dialog content.
		this.div_content = document.createElement('div');
		this.div_content.setAttribute('id', 'div_content_' + this.id);
		this.div_content.className = 'content';
		this.div_inner.appendChild(this.div_content);

		// If there are buttons defined for the current dialog we're going to create a buttonbar for it and add the
		// buttons to the buttonbar.
		this.buttons = new Array();
		var disabled = $A(this.options['buttons_disabled'].split(/\s*[,|]\s*/i));
		if (! /^\s*$/i.test(this.options['buttons'])) {
			this.div_buttons = document.createElement('div');
			this.div_buttons.className = 'buttons';

			var buttondefs = this.options['buttons'].split(/\s*[,|]\s*/i);
			buttondefs.each( function(buttondef_) {
				var input_button = document.createElement('button');
				input_button.setAttribute('caption', buttondef_);
				input_button.innerHTML = buttondef_;
				input_button.onclick = function() {
					self.options.callback(buttondef_, self);
				};
				if (self.buttons.length == 0) Element.addClassName(input_button, 'right');

				if (
					self.options['button_default'] &&
					self.options['button_default'] == buttondef_
				) Element.addClassName(input_button, 'default');

				if (disabled.indexOf(buttondef_) > -1) {
					input_button.disabled = true;
					Element.addClassName(input_button, 'disabled');
					input_button.setAttribute('enabled', false);
				} else input_button.setAttribute('enabled', true);

				self.div_buttons.appendChild(input_button);
				self.buttons.push(input_button);
			} );
			this.div_inner.appendChild(this.div_buttons);
		}

		// Finally let's add this new dialog to the end of the list of known dialogs.
		 __dialogs.push(this);
	},

	flash: function() {
		// This function flashes the outermost border for half a second to get the attention of the user.
		new Effect.Shake(this.div_outer);
		this.div_outer.style.border = '1px dotted #FF0000';
		var self = this;
		setTimeout(function() {
			self.div_outer.style.border = '1px solid #6E7172';
		}, 500);
	},

	onkeypress: function(e_) {
		/**
		 * This function gets executed upon pressing a key on the keyboard. We're going to proxy this key
		 * press to the topmost window which should receive the actual event.
		 */
		var top = dialog.onTop();
		if (top) switch(e_.keyCode) {
			case Event.KEY_RETURN:
				if (top.options['button_default']) {
					top.options['callback'](top.options['button_default'], top);
				}
				return false;
			break;
			case Event.KEY_ESC:
				if (top.options['button_cancel']) {
					top.options['callback'](top.options['button_cancel'], top);
				}
				return false;
			break;
			default:
				return true;
			break;
		}
	},

	hide: function(onComplete_) {
		/**
		 * This function hides the current dialog. It does not remove it's elements from the DOM tree, use the
		 * destroy function for that.
		 */
		if (Element.visible('div_container_' + this.id)) {
			this.__toggle(onComplete_);
		}
	},

	show: function(onComplete_) {
		/**
		 * This function results in a visible dialog box. If the content of the dialog box has been laoded before
		 * we can immediately show the dialog box, otherwise lot's of extra work is necessary.
		 */
		if (/^\s*$/i.test(this.div_content.innerHTML)) {
			// There doesn't appear to be any content loaded into the current dialog. Let's load the content and
			// set the boundaries of the dialog before showing it.
			var self = this;
			var setBoundaries = function() {
				// First we're going to set the dimensions of the dialog. These are dependend on the options set
				// for the dialog.
				self.div_container.style.width = self.options['width'] + 'px';
				self.div_outer.style.width = (self.options['width'] - 2) + 'px'; /* 2px for the border of outer */
				self.div_inner.style.width = (self.options['width'] - 22) + 'px'; /* 22px for border and padding */
				self.div_content.style.width = (self.options['width'] - 22) + 'px';
				if (self.div_buttons) self.div_buttons.style.width = (self.options['width'] - 22) + 'px';
				if (self.div_header) self.div_header.style.width = (self.options['width'] - 32) + 'px';

				self.options['height'] = Element.getHeight(self.div_container);

				if (__dialogIE) {
					self.iframe.style.width = (self.options['width']) + 'px';
					self.iframe.style.height = (self.options['height']) + 'px';
					// For Internet Explorer we need to add functions that are triggered opun resizing and scrolling
					// to force centering the dialog box.
					var onScroll = window.onscroll;
					window.onscroll = function() {
						if (self && self.center) self.center();
						if (onScroll) onScroll();
					}
					var onResize = window.onresize;
					window.onresize = function() {
						if (self && self.center) self.center();
						if (onResize) onResize();
					}
					self.center();
				} else {
					self.div_container.style.left = '50%';
					self.div_container.style.top = '48%';
					self.div_container.style.marginLeft = '-' + Math.ceil(self.options['width'] / 2) + 'px';
					self.div_container.style.marginTop = '-' + Math.ceil(self.options['height'] / 2) + 'px';
				}

				// Finally we're going to set the visibilities of the dialog and make it visible by calling the toggle
				// function.
				self.div_container.style.display = 'none';
				self.div_container.style.visibility = 'visible';
				self.__toggle(onComplete_);
			}

			document.getElementsByTagName('body').item(0).appendChild(this.div_container);

			if (! /^\s*$/i.test(this.options['source'])) {
				new Ajax.Updater( {
					success:this.div_content
				}, this.options['source'], {
					method:this.options['method'],
					parameters:this.options['parameters'],
					evalScripts:true,
					onComplete:setBoundaries
				} );
			} else {
				this.div_content.innerHTML = this.options['content'];
				Element.addClassName(this.div_content, 'margin');
				setBoundaries();
			}
		} else {
			// The dialog content has already been laoded so we don't need to create the dialog again. Let's just
			// show the dialog.
			if (! Element.visible('div_container_' + this.id)) {
				this.__toggle(onComplete_);
			}
		}
	},

	close: function(onComplete_) {
		/**
		 * This function can be used to close and destroy a dialog at once.
		 */
		this.disable();
		var self = this;
		this.hide( function() {
			self.destroy();
			if (onComplete_) onComplete_();
		} );
	},

	destroy: function() {
		__dialogs = __dialogs.without(this);
		document.getElementsByTagName('body').item(0).removeChild(this.div_container);
		this.show = this.hide = this.enable = this.disable = this.center = function() { /* disable these dialog functions */ }
	},

	refresh: function(onComplete_) {
		/**
		 * This function reloads the content of the dialog by fetching new content from the server side script.
		 * This is only necessary of the source of the dialog has been set.
		 */
		if (! /^\s*$/i.test(this.options['source'])) {
			new Ajax.Updater( {
				success:this.div_content
			}, this.options['source'] + '?rn=' + Math.random(), {
				method:this.options['method'],
				parameters:this.options['parameters'],
				evalScripts:true,
				onComplete:function() {
					if (onComplete_) onComplete_();
				}
			} );
		}
	},

	disable: function() { this.enable(false); },
	enable: function(enable_) {
		/**
		 * This function iterates through all the buttons on the dialog and disables or enables them all, which makes the
		 * dialog appear to be disabled or enabled at whole.
		 */
		if (enable_ == undefined) enable_ = true;
		var self = this;
		this.buttons.each( function(button_) {
			self.enableButton(button_, enable_, true);
		} );
	},

	disableButton: function(button_) { this.enableButton(button_, false); },
	enableButton: function(button_, enable_, __internal_) {
		/**
		 * This function enables or disables a single button. The button can be provided as a caption or as the botton
		 * object in the dom tree.
		 */
		if (enable_ == undefined) enable_ = true;
		if (__internal_ == undefined) __internal_ = false;
		if (! $(button_)) {
			this.buttons.each( function(dlgbutton_) {
				if (dlgbutton_.getAttribute('caption') == button_) {
					button_ = dlgbutton_;
				}
			} );
		}
		if (__internal_ && enable_) {
			enable_ = button_.getAttribute('enabled');
		}

		Element.addClassName(button_, enable_ ? 'enabled' : 'disabled');
		Element.removeClassName(button_, enable_ ? 'disabled' : 'enabled');
		if (! __internal_) button_.setAttribute('enabled', enable_);
		button_.disabled = (! enable_);
	},

	getName: function() { return this.options['name']; },
	setName: function(name_) { this.options['name'] = name_; },
	getTag: function() { return this.options['tag']; },
	setTag: function(tag_) { this.options['tag'] = tag_; },

	setTitle: function(title_) {
		/**
		 * This function will alter the header of the dialog, but only if it has been set when the dialog was initially
		 * created, otherwise there is no header.
		 */
		if (this.div_header) this.div_header.innerHTML = title_;
	},

	__moveToTop:function() {
		/**
		 * This private function is used to move a dialog to the top of the stack which makes it appear above all
		 * other elements on the page.
		 */
		this.div_container.style.zIndex = __zIndex++;
	},

	__toggle:function(onComplete_) {
		/**
		 * This function shows or hides the actual dialog using either an effect or display it right away if the
		 * duration is set to zero.
		 */
		var self = this;
		var onShow = function() {
			self.buttons.each( function(button_) {
				if (button_.getAttribute('caption') == self.options['button_default']) {
					// There's a default button defined, so let's move the focus to this button upon showing of the
					// dialog.
					try {
						button_.focus();
					} catch(err_) { /* who cares? */ }
				}
			} );
		}
		this.__moveToTop();
		if (parseFloat(this.options['duration']) > 0) {
			Effect.toggle('div_container_' + this.id, this.options['effect'], {
				duration:this.options['duration'],
				afterFinish:function() {
					if (Element.visible('div_container_' + self.id)) onShow();
					if (onComplete_) onComplete_();
				}
			} );
		} else {
			Element.toggle('div_container_' + this.id);
			if (Element.visible('div_container_' + this.id)) onShow();
			if (onComplete_) onComplete_();
		}
	},
	visible: function() {
		try {
			return Element.visible('div_container_' + this.id);
		} catch(err_) {
			return false;
		}
	},

	center: function() {
		/**
		* This function moves the current dialog to the center of the screen. It can be usefull to have the
		* dialog stay in the center when the window or scrollposition changes.
		*/
		var my_width  = 0;
		var my_height = 0;
		if (typeof(window.innerWidth) == 'number') {
			my_width = window.innerWidth;
			my_height = window.innerHeight;
		} else if (document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
			my_width  = document.documentElement.clientWidth;
			my_height = document.documentElement.clientHeight;
		} else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
			my_width  = document.body.clientWidth;
			my_height = document.body.clientHeight;
		}
		var scrollY = 0;
		if (document.documentElement && document.documentElement.scrollTop) {
			scrollY = document.documentElement.scrollTop;
		} else if (document.body && document.body.scrollTop) {
			scrollY = document.body.scrollTop;
		} else if (window.pageYOffset) {
			scrollY = window.pageYOffset;
		} else if (window.scrollY) {
			scrollY = window.scrollY;
		}
		var dimensions = Element.getDimensions(this.div_container);
		var setX = (my_width - dimensions.width) / 2;
		var setY = (my_height - dimensions.height) / 2 + scrollY;
		setX = (setX < 0) ? 0 : setX;
		setY = (setY < 0 ) ? 0 : setY;

		this.div_container.style.left = setX + "px";
		this.div_container.style.top  = setY + "px";
	}

}

dialog.open = function(options_, onComplete_) {
	/**
	 * This 'static' dialog class member can be used to quickly open a dialogbox without having to call the show member
	 * manually.
	 */
	var dlg = new dialog(options_);
	dlg.show(onComplete_);
}

dialog.onTop = function(flash_) {
	/**
	 * This function returns the dialog that is currently on top and visible. It can be used to quickly retrieve
	 * a reference to the topmost dialog.
	 */
	var visibles = __dialogs.findAll( function(el_) {
		return el_.visible() && ! el_.options['standalone'];
	} );
	if (visibles.length > 0) {
		var dialog = visibles.last();
		if (flash_) dialog.flash();
		return dialog;
	} else return false;
}