includes/clientside/static/messagebox.js
author Dan Fuhry <dan@enanocms.org>
Wed, 29 Dec 2010 13:25:32 -0500
changeset 1332 12286b3ee214
parent 1227 bdac73ed481e
permissions -rw-r--r--
Added some more hooks to the page editing pipeline. It should now be possible to add controls to the page editor, send the data from them out to the server, and process them on the server side.

// Message box and visual effect system

/**
 * The ultimate message box framework for Javascript
 * Syntax is (almost) identical to the MessageBox command in NSIS
 * @param int type - a bitfield consisting of the MB_* constants
 * @param string title - the blue text at the top of the window
 * @param string text - HTML for the body of the message box
 * Properties:
 *   onclick - an array of functions to be called on button click events
 *             NOTE: key names are to be strings, and they must be the value of the input, CaSe-SeNsItIvE
 *   onbeforeclick - same as onclick but called before the messagebox div is destroyed
 * Methods:
 *   destroy: kills the running message box
 * Example:
 *   var my_message = new MessageBox(MB_OK|MB_ICONSTOP, 'Error logging in', 'The username and/or password is incorrect. Please check the username and retype your password');
 *   my_message.onclick['OK'] = function() {
 *       document.getElementById('password').value = '';
 *     };
 * Deps:
 *   Modern browser that supports DOM
 *   darken() and enlighten() (above)
 *   opacity() - required for darken() and enlighten()
 *   MB_* constants are defined in enano-lib-basic.js
 */

var mb_current_obj;
var mb_previously_had_darkener = false;

function MessageBox(type, title, message)
{
	if ( !aclDisableTransitionFX )
	{
		load_component('flyin');
	}
	
	var y = getScrollOffset();
	
	// Prevent multiple instances
	if ( document.getElementById('messageBox') )
		return;
	
	if ( document.getElementById('specialLayer_darkener') )
		if ( document.getElementById('specialLayer_darkener').style.display == 'block' )
			mb_previously_had_darkener = true;
	if ( !mb_previously_had_darkener )
		darken(true);
	if ( aclDisableTransitionFX )
	{
		document.getElementById('specialLayer_darkener').style.zIndex = '5';
	}
	var master_div = document.createElement('div');
	master_div.style.zIndex = getHighestZ() + 1;
	var mydiv = document.createElement('div');
	mydiv.style.height = '200px';
	w = getWidth();
	h = getHeight();
	if ( aclDisableTransitionFX )
	{
		master_div.style.left = ((w / 2) - 200)+'px';
		master_div.style.top = ((h / 2) + y - 120)+'px';
		master_div.style.position = 'absolute';
	}
	else
	{
		master_div.style.top = '-10000px';
		master_div.style.position = ( IE ) ? 'absolute' : 'fixed';
	}
	z = ( aclDisableTransitionFX ) ? document.getElementById('specialLayer_darkener').style.zIndex + 1: getHighestZ() + 1;
	mydiv.style.backgroundColor = '#FFFFFF';
	mydiv.style.padding = '10px';
	mydiv.style.marginBottom = '1px';
	mydiv.id = 'messageBox';
	mydiv.style.overflow = 'auto';
	
	var buttondiv = document.createElement('div');
	
	mydiv.style.width = '400px';
	buttondiv.style.width = '400px';
	
	w = getWidth();
	h = getHeight();
	if ( aclDisableTransitionFX )
	{
		//buttondiv.style.left = ((w / 2) - 200)+'px';
		//buttondiv.style.top = ((h / 2) + y + 101)+'px';
	}
	//buttondiv.style.position = ( IE ) ? 'absolute' : 'fixed';
	z = ( aclDisableTransitionFX ) ? document.getElementById('specialLayer_darkener').style.zIndex : getHighestZ();
	buttondiv.style.backgroundColor = '#C0C0C0';
	buttondiv.style.padding = '10px';
	buttondiv.style.textAlign = 'right';
	buttondiv.style.verticalAlign = 'middle';
	buttondiv.id = 'messageBoxButtons';
	
	this.clickHandler = function() { messagebox_click(this, mb_current_obj); };
	
	if( ( type & MB_ICONINFORMATION || type & MB_ICONSTOP || type & MB_ICONQUESTION || type & MB_ICONEXCLAMATION ) && !(type & MB_ICONLOCK) )
	{
		mydiv.style.paddingLeft = '50px';
		mydiv.style.width = '360px';
		mydiv.style.backgroundRepeat = 'no-repeat';
		mydiv.style.backgroundPosition = '8px 8px';
	}
	else if ( type & MB_ICONLOCK )
	{
		mydiv.style.paddingLeft = '50px';
		mydiv.style.width = '360px';
		mydiv.style.backgroundRepeat = 'no-repeat';
	}
	
	if(type & MB_ICONINFORMATION)
	{
		mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/info.png\')';
	}
	
	if(type & MB_ICONQUESTION)
	{
		mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/question.png\')';
	}
	
	if(type & MB_ICONSTOP)
	{
		mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/error.png\')';
	}
	
	if(type & MB_ICONEXCLAMATION)
	{
		mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/warning.png\')';
	}
	
	if(type & MB_ICONLOCK)
	{
		mydiv.style.backgroundImage = 'url(\''+scriptPath+'/images/lock.png\')';
	}
	
	if(type & MB_OK)
	{
		btn = document.createElement('input');
		btn.type = 'button';
		btn.value = $lang.get('etc_ok');
		btn._GenericName = 'OK';
		btn.onclick = this.clickHandler;
		btn.style.margin = '0 3px';
		buttondiv.appendChild(btn);
	}
	
	if(type & MB_OKCANCEL)
	{
		btn = document.createElement('input');
		btn.type = 'button';
		btn.value = $lang.get('etc_ok');
		btn._GenericName = 'OK';
		btn.onclick = this.clickHandler;
		btn.style.margin = '0 3px';
		buttondiv.appendChild(btn);
		
		btn = document.createElement('input');
		btn.type = 'button';
		btn.value = $lang.get('etc_cancel');
		btn._GenericName = 'Cancel';
		btn.onclick = this.clickHandler;
		btn.style.margin = '0 3px';
		buttondiv.appendChild(btn);
	}
	
	if(type & MB_YESNO)
	{
		btn = document.createElement('input');
		btn.type = 'button';
		btn.value = $lang.get('etc_yes');
		btn._GenericName = 'Yes';
		btn.onclick = this.clickHandler;
		btn.style.margin = '0 3px';
		buttondiv.appendChild(btn);
		
		btn = document.createElement('input');
		btn.type = 'button';
		btn.value = $lang.get('etc_no');
		btn._GenericName = 'No';
		btn.onclick = this.clickHandler;
		btn.style.margin = '0 3px';
		buttondiv.appendChild(btn);
	}
	
	if(type & MB_YESNOCANCEL)
	{
		btn = document.createElement('input');
		btn.type = 'button';
		btn.value = $lang.get('etc_yes');
		btn._GenericName = 'Yes';
		btn.onclick = this.clickHandler;
		btn.style.margin = '0 3px';
		buttondiv.appendChild(btn);
		
		btn = document.createElement('input');
		btn.type = 'button';
		btn.value = $lang.get('etc_no');
		btn._GenericName = 'No';
		btn.onclick = this.clickHandler;
		btn.style.margin = '0 3px';
		buttondiv.appendChild(btn);
		
		btn = document.createElement('input');
		btn.type = 'button';
		btn.value = $lang.get('etc_cancel');
		btn._GenericName = 'Cancel';
		btn.onclick = this.clickHandler;
		btn.style.margin = '0 3px';
		buttondiv.appendChild(btn);
	}
	
	heading = document.createElement('h2');
	heading.innerHTML = title;
	heading.style.color = '#50A0D0';
	heading.style.fontFamily = 'trebuchet ms, verdana, arial, helvetica, sans-serif';
	heading.style.fontSize = '12pt';
	heading.style.fontWeight = 'lighter';
	heading.style.textTransform = 'lowercase';
	heading.style.marginTop = '0';
	mydiv.appendChild(heading);
	
	var text = document.createElement('div');
	text.innerHTML = String(message);
	this.text_area = text;
	mydiv.appendChild(text);
	
	this.updateContent = function(text)
		{
			this.text_area.innerHTML = text;
		};
		
	this.destroy = function()
		{
			var mbdiv = document.getElementById('messageBox');
			mbdiv.parentNode.parentNode.removeChild(mbdiv.parentNode);
			if ( !mb_previously_had_darkener )
				enlighten(true);
		};
	
	//domObjChangeOpac(0, mydiv);
	//domObjChangeOpac(0, master_div);
	
	body = document.getElementsByTagName('body');
	body = body[0];
	master_div.appendChild(mydiv);
	master_div.appendChild(buttondiv);
	
	body.appendChild(master_div);
	
	if ( !aclDisableTransitionFX )
		setTimeout('mb_runFlyIn();', 100);
	
	this.onclick = new Array();
	this.onbeforeclick = new Array();
	mb_current_obj = this;
}

var messagebox = MessageBox;

function mb_runFlyIn()
{
	var mydiv = document.getElementById('messageBox');
	var maindiv = mydiv.parentNode;
	fly_in_top(maindiv, true, false);
}

function messagebox_click(obj, mb)
{
	val = ( typeof ( obj._GenericName ) == 'string' ) ? obj._GenericName : obj.value;
	if(typeof mb.onbeforeclick[val] == 'function')
	{
		var o = mb.onbeforeclick[val];
		var resp = o();
		if ( resp )
			return false;
		o = false;
	}
	
	var mydiv = document.getElementById('messageBox');
	var maindiv = mydiv.parentNode;
	
	if ( aclDisableTransitionFX )
	{
		var mbdiv = document.getElementById('messageBox');
		mbdiv.parentNode.removeChild(mbdiv.nextSibling);
		mbdiv.parentNode.removeChild(mbdiv);
		if ( !mb_previously_had_darkener )
			enlighten(true);
	}
	else
	{
		var to = fly_out_top(maindiv, true, false);
		setTimeout("var mbdiv = document.getElementById('messageBox'); mbdiv.parentNode.parentNode.removeChild(mbdiv.parentNode); if ( !mb_previously_had_darkener ) enlighten(true);", to);
	}
	if(typeof mb.onclick[val] == 'function')
	{
		(mb.onclick[val])();
	}
}

function testMessageBox()
{
	mb = new MessageBox(MB_OKCANCEL|MB_ICONINFORMATION, 'Javascripted dynamic message boxes', 'This is soooooo coool, now if only document.createElement() worked in IE!<br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text<br /><br /><br /><br /><br />this is some more text');
	mb.onclick['OK'] = function()
		{
			alert('You clicked OK!');
		}
	mb.onbeforeclick['Cancel'] = function()
		{
			alert('You clicked Cancel!');
		}
}

/**
 * The miniPrompt function, for creating small prompts and dialogs. The window will be flown in and the window darkened with opac=0.4.
 * @param function Will be passed an HTMLElement that is the body of the prompt window; the function can do with this as it pleases
 */

function miniPrompt(call_on_create)
{
	if ( !aclDisableTransitionFX )
	{
		load_component(['flyin', 'jquery', 'jquery-ui', 'fadefilter']);
	}
	else
	{
		load_component(['fadefilter']);
	}
	var darkener = darken(aclDisableTransitionFX, 40, 'miniprompt_darkener');
	
	var wrapper = document.createElement('div');
	wrapper.className = 'miniprompt';
	var top = document.createElement('div');
	top.className = 'mp-top';
	var body = document.createElement('div');
	body.className = 'mp-body';
	var bottom = document.createElement('div');
	bottom.className = 'mp-bottom';
	if ( typeof(call_on_create) == 'function' )
	{
		call_on_create(body);
	}
	wrapper.appendChild(top);
	wrapper.appendChild(body);
	wrapper.appendChild(bottom);
	var left = ( getWidth() / 2 ) - ( 388 / 2 );
	wrapper.style.left = left + 'px';
	var top = getScrollOffset() - 27;
	wrapper.style.top = top + 'px';
	domObjChangeOpac(0, wrapper);
	var realbody = document.getElementsByTagName('body')[0];
	realbody.appendChild(wrapper);
	
	if ( aclDisableTransitionFX )
	{
		domObjChangeOpac(100, wrapper);
	}
	else
	{
		fly_in_top(wrapper, true, true);
		
		setTimeout(function()
			{
				domObjChangeOpac(100, wrapper);
			}, 40);
	}
	
	// set the darkener's onclick to refocus/shake the miniprompt
	darkener.miniprompt = wrapper;
	darkener.onclick = function()
	{
		if ( !aclDisableTransitionFX )
		{
			$(this.miniprompt).effect("pulsate", { times: 2 }, 200);
		}
	}
	
	return wrapper;
}

/**
 * For a given element, loops through the element and all of its ancestors looking for a miniPrompt div, and returns it. Returns false on failure.
 * @param object:HTMLElement Child node to scan
 * @return object
 */

function miniPromptGetParent(obj)
{
	while ( true )
	{
		// prevent infinite loops
		if ( !obj || obj.tagName == 'BODY' )
			return false;
		
		if ( $dynano(obj).hasClass('miniprompt') )
		{
			return obj;
		}
		obj = obj.parentNode;
	}
	return false;
}

/**
 * Destroys the first miniPrompt div encountered by recursively checking all parent nodes.
 * Usage: <a href="javascript:miniPromptDestroy(this);">click</a>
 * @param object:HTMLElement a child of the div.miniprompt
 * @param bool (DEPRECATED) If true, does not call enlighten().
 */

function miniPromptDestroy(obj, nofade)
{
	obj = miniPromptGetParent(obj);
	if ( !obj )
		return false;
	
	// found it
	var parent = obj.parentNode;
	// if ( !nofade )
	//   enlighten(aclDisableTransitionFX);
	enlighten((aclDisableTransitionFX || nofade), 'miniprompt_darkener');
	if ( aclDisableTransitionFX || nofade )
	{
		parent.removeChild(obj);
	}
	else
	{
		var timeout = fly_out_top(obj, true, true);
		setTimeout(function()
			{
				parent.removeChild(obj);
			}, timeout);
	}
}

/**
 * Simple test case
 */

function miniPromptTest()
{
	miniPrompt(function(div) { div.innerHTML = 'hello world! <a href="#" onclick="miniPromptDestroy(this); return false;">destroy me</a>'; });
}

/**
 * Message box system for miniPrompts. Less customization but easier to scale than the regular messageBox framework.
 * @example
 <code>
 miniPromptMessage({
 	title: 'Delete page',
 	message: 'Do you really want to delete this page? This is reversible unless you clear the page logs.',
 	buttons: [
 		{
 			text: 'Delete',
 			color: 'red',
 			style: {
 				fontWeight: 'bold'
 			},
 			onclick: function() {
 				ajaxDeletePage();
 				miniPromptDestroy(this);
 			}
 		},
 		{
 			text: 'cancel',
 			onclick: function() {
 				miniPromptDestroy(this);
 			}
 		}
 	]
 });
 </code>
 */

function miniPromptMessage(parms)
{
	if ( ( !parms.title && !parms.message ) || !parms.buttons )
		return false;
	
	return miniPrompt(function(parent)
		{
			try
			{
				if ( parms.title )
				{
					var h3 = document.createElement('h3');
					h3.appendChild(document.createTextNode(parms.title));
				}
				if ( parms.message )
				{
					var body = document.createElement('p');
					var message = parms.message.split(unescape('%0A'));
					for ( var i = 0; i < message.length; i++ )
					{
						body.appendChild(document.createTextNode(message[i]));
						if ( i + 1 < message.length )
							body.appendChild(document.createElement('br'));
					}
				}
				
				parent.style.textAlign = 'center';
				
				if ( parms.title )
					parent.appendChild(h3);
				if ( parms.message )
					parent.appendChild(body);
				parent.appendChild(document.createElement('br'));
				
				// construct buttons
				for ( var i = 0; i < parms.buttons.length; i++ )
				{
					var button = parms.buttons[i];
					button.input = document.createElement('a');
					button.input.href = '#';
					button.input.clickAction = button.onclick;
					button.input.className = 'abutton';
					if ( button.color )
					{
						button.input.className += ' abutton_' + button.color;
					}
					button.input.appendChild(document.createTextNode(button.text));
					if ( button.style )
					{
						for ( var j in button.style )
						{
							button.input.style[j] = button.style[j];
						}
					}
					if ( button.sprite )
					{
						var sprite = gen_sprite(button.sprite[0], button.sprite[1], button.sprite[2], button.sprite[3], button.sprite[4]);
						sprite.style.position = 'relative';
						sprite.style.top = '3px';
						button.input.insertBefore(sprite, button.input.firstChild);
						insertAfter(button.input, document.createTextNode(' '), sprite);
					}
					else if ( button.image )
					{
						button.input.className += ' icon';
						button.input.style.backgroundImage = 'url(' + button.image + ')';
					}
					button.input.onclick = function(e)
					{
						try
						{
							this.clickAction(e);
						}
						catch(e)
						{
							console.error(e);
						}
						return false;
					}
					parent.appendChild(button.input);
				}
				// don't focus this in opera - it looks kinda ugly
				if ( parms.buttons[0] && !window.opera )
				{
					var timeout = ( aclDisableTransitionFX ) ? 10 : 1000;
					setTimeout(function()
						{
							parms.buttons[0].input.focus();
						}, timeout);
				}
			}
			catch ( e )
			{
				console.error(e);
			}
		});
}

/**
 * Identical to whiteOutElement(), but safe to call on miniPrompt divs.
 */

function whiteOutMiniPrompt(el)
{
	el = miniPromptGetParent(el);
	var width = 320;
	var height = $dynano(el).Height() - 58;
	var topoffset = 27;
	
	var container = document.createElement('div');
	container.style.padding = topoffset + 'px 0 0 0';

	var top = getScrollOffset();
	var left = getWidth() / 2 - width / 2;
	
	// using fixed here allows modal windows to be blacked out
	container.style.position = 'absolute';
	container.style.top = ( top - topoffset ) + 'px';
	container.style.left = left + 'px';
	container.style.zIndex = 1000;
	
	var blackout = document.createElement('div');
	blackout.style.backgroundColor = '#ffffff';
	blackout.style.width = width + 'px';
	blackout.style.height = height + 'px';
	domObjChangeOpac(60, blackout);
	var background = ( $dynano(el).Height() < 48 ) ? 'url(' + scriptPath + '/images/loading.gif)' : 'url(' + scriptPath + '/includes/clientside/tinymce/themes/advanced/skins/default/img/progress.gif)';
	blackout.style.backgroundImage = background;
	blackout.style.backgroundPosition = 'center center';
	blackout.style.backgroundRepeat = 'no-repeat';
	blackout.isMiniPrompt = true;
	blackout.miniPromptObj = el;
	
	container.appendChild(blackout);
	var body = document.getElementsByTagName('body')[0];
	body.appendChild(container);
	
	return blackout;
}

function whiteOutDestroyOnMiniPrompt(whitey)
{
	var body = document.getElementsByTagName('body')[0];
	var parent = whitey.miniPromptObj;
	fly_out_top([parent, whitey.parentNode], true, true);
	setTimeout(function()
		{
			body.removeChild(parent);
			body.removeChild(whitey.parentNode);
		}, 1000 * FI_MULTIPLIER);
	enlighten(true, 'miniprompt_darkener');
}

function testMPMessageBox()
{
	miniPromptMessage({
		title: 'The Game of LIFE question #73',
		message: 'You just got your girlfriend pregnant. Please select an option:',
		buttons: [
			{
				text: 'Abort',
				color: 'red',
				style: {
					fontWeight: 'bold'
				},
				sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 0 ],
				onclick: function() {
					var w = whiteOutMiniPrompt(this);
					var me = this;
					setTimeout(function()
						{
							whiteOutReportSuccess(w, true);
							void(me);
							setTimeout(function()
								{
									miniPromptDestroy(me);
								}, 1250);
						}, 500);
					return false;
				}
			},
			{
				text: 'Retry',
				color: 'blue',
				sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 16 ],
				onclick: function() {
					var w = whiteOutMiniPrompt(this);
					setTimeout(function()
						{
							whiteOutReportSuccess(w);
						}, 1500);
					return false;
				}
			},
			{
				text: 'Ignore',
				color: 'green',
				sprite: [ cdnPath + '/images/icons/abortretryignore-sprite.png', 16, 16, 0, 32 ],
				onclick: function() {
					miniPromptDestroy(this);
				}
			}
		]
	});
}