/**
 * Popup class for jQuery
 * 
 * @author 		khaehlen <khaehlen@zigwebsoftware.nl>
 * @version 	0.2
 * @copyright	Copyright (c) 2011, Zig Websoftware
 * 
 * 
 * TODO: Documentation - of course ;)
 */


/** TYPICAL USAGE
 
   // EXAMPLE 1: Attaching a Popup to a DOM element
 
   var oLettertypePopup = new Popup({
			sContent: 		'<img src="fileadmin/templates/skins/elanwonen/images/lettergrootte.png" />',
			sHideSelector:	'',		// empty string will make the popup close when clicked
		}, '#fontSizeButton');
	
	// EXAMPLE 2: Using Popup.open (directly opens the popup). It takes a Popup as parameter
	
	var oPopup = Popup.open(new Popup({sContentId: 'c53', sStyle: Popup.style.SIMPLE }));
	
	// EXAMPLE 3: Using Popup.open again. You can also give a config array as parameter
	
	var oPopup = Popup.open({sContent:'Hello world'});
	
	// EXAMPLE 4: Calling the run() function on the popup. Note that all DOM nodes with class .inside-a-popup will become popups (see grab())
	
	var aPopups = Popup.grab('.inside-a-popup');
	aPopups[1].run();
 */

function Popup(oConfig, sTriggeringElSelector)
{
	// constructor
	
	this.oConfig 				= {
									sContainerId:			'popup',
									sContent:				'Inhoud van de popup',
									sTitle:					null,
									sContentId:				null,		// this will OVERRIDE sContent. If set, the element with this id will
																		// be inserted into the popup. You can also give a node.
									fnShow:					null,
									fnHide:					null,
									sHideSelector:			null,		// an empty string will result in the popup closing when clicked
																		// Note this is an SELECTOR, you should enter something like
																		// "#hide-button", OR "#hide-button1,#hide-button2" OR 
																		// "#button-bar .hide-button". Do NOT forget the # before an id.
									sStyle:					null,
									sSkinId:				null,		// custom skin for the popup,
									bLazyContentLoading: 	false,		// if true, the given content will be placed inside the popup when
																		// the run() method is called (that is, when the popup is opened).
																		// If false it will be placed there immediately
									sButtonsHtml:			null,		// HTML that will be inserted at the bottom of the popup. You can
																		// use specific classes to trigger specific functionality:
																		// 		.close-button: Closes the popup
									bBlocking:				false,		// if true, the popup cannot be closed by clicking the overlay or
																		// pressing the ESC key
									bRemoveCloseButton:		false		// remove the close button in the header buttons bar
								};
	
	this.sTriggeringElSelector 	= (typeof sTriggeringElSelector != 'undefined') ? sTriggeringElSelector : null; // see above about SELECTORS
	this.sContainerId			= '';		// will be set later
	this.oContainer				= null;		// the jQuery container element with the above id
	this.sBaseClass				= 'Popup';	// do not change!

	this.initialize(oConfig);
	
	return true;
}

// STATIC PROPERTIES
Popup.oOverlay 					= null;
Popup.iActivePopups 			= 0;
Popup.iPopupIndex 				= 0;
Popup.aStack					= [];
Popup.style 					= new Object();		// see constants

// CONSTANTS

// our skin
Popup.sDefaultSkin 				= '																\
									<div id="popup-skin" class="popup" style="display: none">	\
										<div class="popup-header">								\
											<div class="popup-title"></div>						\
											<div class="popup-header-buttons">					\
												<div class="close-button"><span>X</span></div>	\
											</div>												\
										</div>													\
										<div class="popup-content"></div>						\
										<div class="popup-buttons"></div>						\
									</div>';
Popup.oDefaultSkinNode			= null;

// prefab stylings
Popup.style.SIMPLE				= 'background-color: white; width: 300px; min-height: 40px; border: 1px solid black;';


// METHODS

Popup.prototype = {
		
		initialize: function(oConfig)
		{
	
			// override default settings
			if (typeof oConfig == 'object')
			{
				for (var i in oConfig)
				{
					this.oConfig[i] = oConfig[i];
				}
			}
			
			// initialize observer
			if (this.sTriggeringElSelector)
			{
				var oThis = this;
				jQuery(this.sTriggeringElSelector).bind('click', function(oEvent){
					oEvent.preventDefault();
					oThis.run();
				});
			}
			
			var oSkinNode = this.loadSkin();
			
			var sContainerId = this.oConfig.sContainerId + parseInt(++Popup.iPopupIndex);
			
			var oPopupNode = oSkinNode.cloneNode(true);
			jQuery(oPopupNode).attr('id', sContainerId);
			
			// additional styling
			if (this.oConfig.sStyle)
			{
				// add the additional styling. And ALWAYS ADD display:none!!
				jQuery(oPopupNode).attr('style', this.oConfig.sStyle+';display:none;');
			}
			
			
			jQuery('body').append(oPopupNode);
			
			this.sContainerId = '#' + sContainerId;
			
			this.oContainer = jQuery(this.sContainerId);
			
			// install the content (if not done already)
			// if this.oConfig.sContentId is given the DOM node with the given id will be placed in the popup,
			// else the popup will make use of this.oConfig.sContent
			
			if (!this.oConfig.bLazyContentLoading)
			{
				this.installContent();
			}
			
			if (this.oConfig.sTitle)
			{
				this.setTitle(this.oConfig.sTitle);
			}
			
			if (this.oConfig.sButtonsHtml)
			{
				this.setButtons(this.oConfig.sButtonsHtml);
			}
			
			if (this.oConfig.bRemoveCloseButton)
			{
				this.oContainer.find('.popup-header-buttons .close-button').remove();
			}
			
			// make the necessary arrangements for the overlay
			Popup.initOverlay();
			
		},
		
		/**
		 * Our entry point
		 */
		run: function()
		{
			var oThis = this;
			
			// install the content (if not done already)
			// if this.oConfig.sContentId is given the DOM node with the given id will be placed in the popup,
			// else the popup will make use of this.oConfig.sContent
		
			if (this.oConfig.bLazyContentLoading)
			{
				this.installContent();
			}
			
			// wait for the content to load (images etc.), then do a callback to the given function
			this.onContentLoaded(function()
			{
				// set the listeners
				oThis.initObservers();
				
				// and open the popup
				oThis.openPopup();
				
			}, this);
			
		},
		
		installContent: function()
		{
			var mContent = null;
			
			if (this.oConfig.sContentId)
			{
				if (typeof this.oConfig.sContentId == 'object')
				{
					// sContentId is actually a DOM node
					mContent = jQuery(this.oConfig.sContentId);
				}
				else
				{
					// sContentId is a string designating a DOM node-id
					mContent = jQuery('#' + this.oConfig.sContentId);
				}
				
				// we do not use the original, but a cloned version
				mContent = jQuery(mContent.get(0).cloneNode(true));
				
				mContent.css({'display': 'block', 'position': 'static'});
			}
			else
			{
				mContent = this.oConfig.sContent;
			}
			
			this.setContent(mContent);
		},
		
		show: function(oThis)
		{
			// Hi extenders! Make sure you do a callback to oThis.afterShow(oThis) when you're done animating
			oThis.oContainer.css('z-index','20000');
			oThis.center();
			oThis.oContainer.fadeIn(400, function(){ oThis.afterShow(oThis) });
		},
		
		hide: function(oThis)
		{
			// Hi extenders! Make sure you do a callback to oThis.afterHide(oThis) when you're done animating
			oThis.oContainer.hide(400, function(){ oThis.afterHide(oThis) });
		},
		
		center: function()
		{	
			var oContainer = this.oContainer;
			
			oContainer.css("position","absolute");
			oContainer.css("top","-10000px");
			oContainer.css("left","-10000px");
			
			oContainer.css("top", ( jQuery(window).height() - oContainer.height() ) / 2+jQuery(window).scrollTop() + "px");
			oContainer.css("left", ( jQuery(window).width() - oContainer.width() ) / 2+jQuery(window).scrollLeft() + "px");
		},
		
		initHidingObserver: function()
		{
			var oThis = this;
			
			// initialize observer
			if (this.oConfig.sHideSelector !== null)
			{
				
				// an empty string results in: The popup will close itself if it is clicked
				var sHideString = this.oConfig.sHideSelector == '' ? this.sContainerId : this.oConfig.sHideSelector;
				
				jQuery(sHideString).bind('click', function(oEvent){
					oEvent.preventDefault(); 
					oThis.closePopup();
				});
			}
			
			this.oContainer.find('.close-button').bind('click', function(oEvent){
				oEvent.preventDefault(); 
				oThis.closePopup();
			});
			
		},
		
		initObservers: function()
		{
			this.initHidingObserver();
			this.initViewportObservers();
		},
		
		removeObservers: function()
		{
			this.removeHidingObserver();
			this.removeViewportObservers();
		},
		
		removeHidingObserver: function()
		{
			if (this.oConfig.sHideSelector !== null)
			{
				
				// an empty string results in: The popup will close itself if it is clicked
				var sHideString = this.oConfig.sHideSelector == '' ? this.sContainerId : this.oConfig.sHideSelector;
				
				jQuery(sHideString).unbind('click');
			}
			
			this.oContainer.find('.close-button').unbind('click');
			
		},
		
		removeViewportObservers: function()
		{
			// TODO
		},
		
		initViewportObservers: function()
		{
			var oThis = this;
			
			jQuery(window).scroll(function() {
				// allow for a hook (oConfig.fnOnMove)
				var fnMove = typeof oThis.oConfig.fnOnMove == 'function' ? oThis.oConfig.fnOnMove : oThis.onMove;
				fnMove(oThis);
			}); 
			
			jQuery(window).resize(function() {
				// allow for a hook (oConfig.fnOnMove)
				var fnMove = typeof oThis.oConfig.fnOnMove == 'function' ? oThis.oConfig.fnOnMove : oThis.onMove;
				fnMove(oThis);
			});
		},
		
		openPopup: function()
		{
			// tell the popup manager we are going to open a popup. It will display the overlay if necessary
			Popup.notifyOpen(this);
			
			// allow for a hook (oConfig.fnShow)
			var fnShowFunction = typeof this.oConfig.fnShow == 'function' ? this.oConfig.fnShow : this.show;
			fnShowFunction(this);
		},
		
		closePopup: function()
		{
			// tell the popup manager we are going to close a popup. It will hide the overlay if necessary
			Popup.notifyClose(this);
			
			// allow for a hook (oConfig.fnHide)
			var fnHideFunction = typeof this.oConfig.fnHide == 'function' ? this.oConfig.fnHide : this.hide;
			fnHideFunction(this);
			
		},
		
		onContentLoaded: function(fnCallback, oThis, iTimesToCall)
		{
			if (typeof iTimesToCall == 'undefined') iTimesToCall = 10;
			
			if ((oThis.oContainer.find('.popup-content').height() != 0) || iTimesToCall <= 0)
			{
				fnCallback();
			}
			else
			{
				setTimeout(function() { oThis.onContentLoaded(fnCallback, oThis, iTimesToCall-1); }, 10);
			}
		},
		
		setContent: function(mContent)
		{
			var oContentContainer = this.oContainer.find('.popup-content');
			
			// install the content
			oContentContainer.empty();
			oContentContainer.append(mContent);
			
		},
		
		loadSkin: function()
		{
			if (this.oConfig.sSkinId)
			{
				// custom skin
				return document.getElementById(this.oConfig.sSkinId);
			}
			else
			{
				// default skin
				if (!Popup.oDefaultSkinNode)
				{
					Popup.oDefaultSkinNode = jQuery(Popup.sDefaultSkin).appendTo('body').get(0);
					jQuery(Popup.oDefaultSkinNode).detach();
				}
				
				return Popup.oDefaultSkinNode;
			}
			
			
		},
		
		forceArray: function(mStringOrArray)
		{
			if (typeof(mStringOrArray) == 'object')
			{
				return mStringOrArray;
			}
			else
			{
				return [mStringOrArray];
			}
		},
		
		setTitle: function(sTitle)
		{
			this.oContainer.find('.popup-title').html(sTitle);
		},
		
		setButtons: function(sButtonsHtml)
		{
			this.oContainer.find('.popup-buttons').html(sButtonsHtml);
		},
		
		afterShow: function(oThis)
		{
			// this function is called after the popup is shown
		},
		
		afterHide: function(oThis)
		{
			// this function is called after the popup is hidden
			oThis.removeObservers();
			
			// if the popup is not dependent of an triggering element, remove the DOM node
			if (oThis.sTriggeringElSelector == null)
			{
				oThis.oContainer.remove();
			}
		},
		
		onMove: function(oThis)
		{
			oThis.center();
		}
};


// static functions, these are the 'manager'-type functions

Popup.initOverlay = function()
{	
	if (Popup.oOverlay == null)
	{
		var sOverlayId = 'popupOverlay';
		
		if(document.getElementById(sOverlayId))
		{
        	Popup.oOverlay = document.getElementById(sOverlayId);
		}
		else                           
		{    
			Popup.oOverlay = document.createElement('div');
			document.body.appendChild(Popup.oOverlay);
		}
		
		Popup.oOverlay.style.background = '#000';
		Popup.oOverlay.style.display = 'none';
		Popup.oOverlay.style.position = 'absolute';
		Popup.oOverlay.style.zIndex = "900";
		
		
		if (jQuery.browser.msie)
		{
			Popup.oOverlay.style.filter = 'alpha(opacity=60)';
		}
		else
		{
			Popup.oOverlay.style.opacity = 0.6;		
		}
	}
	
	
};

Popup.positionOverlay = function()
{
	// if active
	if (Popup.isActive())
	{
		Popup.oOverlay.style.top = jQuery(window).scrollTop() + "px";
		Popup.oOverlay.style.left = jQuery(window).scrollLeft() + "px";
		
		Popup.oOverlay.style.width = jQuery(window).width()+'px';
		Popup.oOverlay.style.height = jQuery(window).height()+'px';
	}
};

Popup.showOverlay = function()
{
	Popup.positionOverlay();
	jQuery(Popup.oOverlay).css({display: 'block'});
};

Popup.hideOverlay = function()
{
	jQuery(Popup.oOverlay).css({display: 'none'});
};

Popup.notifyOpen = function(oPopup)
{
	Popup.aStack.push(oPopup);
	
	if (Popup.iActivePopups++ == 0)
	{
		Popup.addObservers();
		Popup.showOverlay();
	}
};

Popup.notifyClose = function(oPopup)
{
	if (iIndex = Popup.aStack.indexOf(oPopup))
	{
		Popup.aStack.splice(iIndex, 1);
	}
	
	
	if (--Popup.iActivePopups <= 0)
	{
		Popup.iActivePopups = 0;
		
		this.hideOverlay();
		this.removeObservers();
	}
	
};

Popup.forceClose = function()
{
	if (Popup.aStack.length > 0)
	{
		var oPopup = Popup.aStack[Popup.aStack.length - 1];
		
		if (!oPopup.oConfig.bBlocking)
		{
			oPopup.closePopup();
		}
	}
};

Popup.addObservers = function()
{
	jQuery(window).bind('scroll', Popup.moveHandler);
	jQuery(window).bind('resize', Popup.moveHandler);
	jQuery(Popup.oOverlay).bind('click', Popup.forceClose);
	jQuery(document).bind('keyup',function(e)
	{
		if (e.keyCode == 27)
		{
			Popup.forceClose();
		}
	});
};

Popup.removeObservers = function()
{
	jQuery(window).unbind('scroll', Popup.moveHandler);
	jQuery(window).unbind('resize', Popup.moveHandler);
	jQuery(Popup.oOverlay).unbind('click');
	jQuery(document).unbind('keyup');
};

Popup.moveHandler = function()
{
	Popup.positionOverlay();
};

Popup.isActive = function()
{
	return (Popup.iActivePopups > 0);
};

/**
 * oPopupData can be either one of these:
 * 1. config. This config will be used to create the Popup. e.g. Popup.open( { sContent: 'test' } );
 * 2. new Popup(). An instance of a popup. e.g. Popup.open( new Popup({ sContent: 'test' }) );
 * 
 * The second method has the advantage that one can instantiate subclasses of the Popup class, and still
 * open that popup with one command (Popup.open())
 */

Popup.open = function(oPopupData, bPreventClickEvent)
{
	if (typeof oPopupData != 'undefined')
	{
		if (typeof oPopupData.sBaseClass == 'string')
		{
			if (oPopupData.sBaseClass == 'Popup')
			{
				// our parameter is actually a popup!
				// run the thing...
				
				oPopupData.run();
				return (typeof bPreventClickEvent == 'boolean' && bPreventClickEvent == true) ? false : oPopupData;
			}
		}
	}
	
	var oPopup = new Popup(oPopupData);
	oPopup.run();
	return (typeof bPreventClickEvent == 'boolean' && bPreventClickEvent == true) ? false : oPopup;
	
};

Popup.grab = function(sPopupContentSelector, oPopupConfig)
{
	if (typeof oPopupConfig == 'undefined')
	{
		oPopupConfig = {};
	}
	
	var aPopups = [];
	
	jQuery(sPopupContentSelector).each(function(iCounter, oPopupContent)
	{
		var sIndex = oPopupContent.id ? oPopupContent.id : iCounter;
		oPopupConfig.sContentId = oPopupContent;
		aPopups[sIndex] = new Popup(oPopupConfig);
	});
	
	return aPopups;

};


/* ------------- EXTRA LIBRARIES ------------- */

/**
 * A function used to extend one class with another
 * 
 * @param {Object} subClass
 * 		The inheriting class, or subclass
 * @param {Object} baseClass
 * 		The class from which to inherit
 */
ZigClass = new Object();

ZigClass.extend = function(subClass, baseClass) {
   function inheritance() {}
   inheritance.prototype = baseClass.prototype;

   subClass.prototype = new inheritance();
   subClass.prototype.constructor = subClass;
   subClass.baseConstructor = baseClass;
   subClass.superClass = baseClass.prototype;
}

/**
 * IE does not always support indexOf
 */

if(!Array.indexOf){
    Array.prototype.indexOf = function(obj){
        for(var i=0; i<this.length; i++){
            if(this[i]==obj){
                return i;
            }
        }
        return -1;
    }
}

/* --------------- EXTENDING CLASSES OF Popup (we need the libraries above to be loaded!) --------------- */


function AlertPopup(oConfig, sTriggeringElSelector)
{
	AlertPopup.baseConstructor.call(this, oConfig, sTriggeringElSelector);

	// extra config
	this.oConfig.bBlocking = true;
	this.oContainer.find('.popup-header-buttons .close-button').css('display','none');
}

// We extend from Popup
ZigClass.extend(AlertPopup, Popup);


