/**
 * @author andrew
 * Eye candy and stuff to hide/reveal content - also some ajax stuff
 */

jQuery(document).ready(function() {
	hiddenContent();	//content that starts off hidden
	if (typeof urlHandler == 'function') { urlHandler(); }	//detects ajax urls and loads the required content (the folowing functions will be delayed until after the ajax update)
	if (!ajaxUpdateInProgress) {
		onAjaxDocumentReady();	//functions to execute on DOM ready and after AJAX updates
	 }
});

jQuery(window).load(function () {	//wait till background image has loaded as we need to determine it's height

	if (!ajaxUpdateInProgress) {
		onAjaxWindowLoaded(); //functions to execute on content loaded and after AJAX updates
	}
});


//these initialisation routines run as soon as the DOM is ready. Only use for stuff that needs to run asap as these fns can slow down the loading of other assets 
function onAjaxDocumentReady() {
	cookieCheck();	//display a warning if javascript or cookies disabled 
	busyStart('onAjaxDocumentReady()');	//run the busy animation until the page loads
	if (typeof displayOnload == 'function') { displayOnload(); }	//hide initially and then fade in images as they load 
	if (typeof autoResize == 'function') { autoResize(); }	//resize images based on aspect ratio and other parameters 
	ajaxifyLinks();		//convert marked links to ajax links
	initToggleContent();	//hide content that starts off collapsed 
	phpJsConnector();
	if (typeof upgradeImages == 'function') { upgradeImages(); }	//replace any low quality background images with their high quality counterpart
	jQuery('.uiButton').button();	//style marked links/buttons etc as jQuery UI buttons
	fixContactLinks();	//restore obfuscated email links
}

//these initialisation routines run after all images are loaded. Use for stuff that needs everything to be loaded and stuff that can wait
function onAjaxWindowLoaded() {
	bindFormCookies();
	if (typeof loadQueuedImages == 'function') { loadQueuedImages(); }	//load remaining queued (low priority) images  
//	if (typeof onloadBgImage == 'function') { onloadBgImage(); }	//fade in the bgImage and display the image title
	expandToFit();
	if (typeof delayedFadeIn == 'function') { delayedFadeIn(); }	//finally fade in top layer of content 
	jsHover();	//apply image rollover effects
	linkify();	//apply child's link to a containing element
	initScrollToTop();	//scroll to top of page button
	if (typeof initFileBrowser == 'function') { initFileBrowser(); }	//activates the image clipboard if it's enabled
	if (typeof initPage == 'function') { initPage(); }	//some pages have their own initialisation script
	initSortable();
	initDraggable();
	loadFacebookSDK();
	busyStop();	//stop the busy animation once the page loads
}




//INCOMPLETE function
//absolutely positioned content is not automatically enclosed by it's container. This function detects a resize in the content and adjusts the containing element 
function forceEnclose(containedElement, containerId) {
	jQuery('.forceEnclose').each(function () {
		jQuery(this).live('resize', function() {
//		  alert('content resized');
		});
	});
}


//Browser feature support
//displays the above browser warning notice if javascript or cookies are disabled 
function cookieCheck() {		
    if (!navigator.cookieEnabled) {
       jQuery('#cookieWarning').css('display', 'block');	//display a warning message if cookies are disabled
       return false;
    } else {
		return true;
	}
}
function browserOpacitySupport() {
	return !(jQuery.browser.msie  && jQuery.browser.version < 9);
}
function browserAjaxUpdateSupport() {
	return !(jQuery.browser.msie  && jQuery.browser.version < 8);
}




//make a containing element link to the same place as the child link
function linkify() {
	
	var $container;
	
	jQuery('.linkify').each(function() {
		$container = jQuery(this);
		 
		//setup the container onclick to be a link
		$container.click(
        	function() {
				var url = jQuery(this).find('a:first').attr('href');
            	window.open(url, '_self');
            }
		);
		
		//setup the hover effect
		$container.hover(
        	function() {
            	jQuery(this).css('cursor', 'pointer');
				jQuery(this).css('opacity', 0.7);
            },
			function() {
				jQuery(this).css('cursor', '');
				jQuery(this).css('opacity', '');
			} 
		);
  	});
}


//called on document ready to hide .displayOnload images (to be faded in when loaded)
function hiddenContent() {

	//changing the opacity of PNGs in MSIE destroys transparency built into the PNG
	if (browserOpacitySupport()) {	
		//make the bgimage and similiar items transparent initially 
		jQuery('.autoResize, .bgFade').css("opacity", "0.0");
	}
}




//dummy log function if debug.js not included
if (typeof log != 'function') { 
	function log() {
		return;
	} 
}


//attach to a form keypress/onchange handler to have the field submit on ENTER 
function submitForm(element) {
	jQuery(element).keypress(function(e) {
      	if (e.which == 13) {
			e.preventDefault();
//			alert(jQuery(element).closest('form').attr('id'))
       		jQuery(element).closest('form').submit();
		}
     });

} 



//called after page load to resize the content containers so that the content doesn't overflow 
function expandToFit() {
	jQuery('.expandToFit').each(function () {
		$container = jQuery(this);
		
		//determine which dimension may be expanded to fit content 
		expandHorizontally = ($container.css('width') != '0px');
		expandVertically = ($container.css('height') != '0px');
		
		if (!expandHorizontally && !expandVertically) {
			alert("System Error: we can't expand an element whose css width and height are unspecified: "+this.id);			
		}
		
		//if the element's content overflows expand (to measure overflow, overflow must be set to auto)
		containerOverflow = $container.css('overflow');
		containerOverflowX = $container.css('overflow-x');
		containerOverflowY = $container.css('overflow-y');
		$container.css('overflow', 'auto');
		if (($container.height() < this.scrollHeight) || ($container.width() < this.scrollWidth)) {
//			alert($container.height() +':'+ this.scrollHeight +':'+ $container.width() +':'+ this.scrollWidth)
			if (expandHorizontally) {
				$container.animate({ width: (this.scrollWidth)}, 500);
			} 

			if (expandVertically) {
				$container.animate({ height: (this.scrollHeight)}, 500);
			}
		}
		$container.css('overflow-x', containerOverflowX).css('overflow-y', containerOverflowY).css('overflow', containerOverflow);	//restore original overflow settings
	});
}



//restore obfuscated email links (obfuscation is done automatically by a server script to prevent email harvesting)
function fixContactLinks() {
	jQuery('a.contact').each(function () {
		$link = jQuery(this);
		$href = $link.attr('href').replace('contact/', '').replace('/', '@').replace('+', '.');
		if ($link.text() == '[link hidden]') {
			$link.text($href);
		}
		$link.attr('href', 'mailto:'+$href);
	});
}




//dynamically load ckeditor modules on demand to save code bloat - DOESN'T WORK AS EXPECTED  
if (typeof insertEditor != 'function') {
	function insertEditor(id) {
		jQuery.getScript(base_url+'ckeditor/ckeditor.js', function() {

			//use this until ckeditor comes with a working autogrow
			jQuery.getScript(base_url+'js/ckeditor/ckeditor_autogrow.js', function() {

				//this provides a custom image dialog to ckeditor
				jQuery.getScript(base_url+'js/ckeditor/ckeditor_image.js', function() {		
					
					jQuery.getScript(base_url+'js/ckeditor/myCKeditor.js', function() {
					});
				});
			});
		});	
	}
}




//makes items draggable
function initDraggable() {
	jQuery('.draggable').draggable();
}


//enables facebook social plugins to work (eg. Facebook Like & Send button)
function loadFacebookSDK() {
	window.fbAsyncInit = function() {
		FB.init({appId: 'your app id', status: true, cookie: true, xfbml: true});
	};

	jQuery('body').append('<div id="fb-root"></div>');

	jQuery.getScript(document.location.protocol + '//connect.facebook.net/en_US/all.js');
}

	

//initialize drag and drop sorting of content and images - needs to be run once on page load and after ajax updates 
function initSortable() {
	var $sortable = jQuery('.sortable');
	
	if (!$sortable.length) { return; }	//no sortable elements
	
	
	$sortable.sortable({
		cursor: 'move',
//		axis: 'x',
		items: '.sortableItem',
//		start: function(event, ui) { busyStart('start drag drop sorting'); },
//		stop: function(event, ui) { busyStop(); },
		update: function(event, ui) {
			var sortedItems = jQuery(this).sortable('toArray').toString();
			Post.Send('sortedItems='+sortedItems, base_url+'dataserver/ajaxSort');
		}
	});
	
	$sortable.children('.sortableItem').hover( 
		function () {
			var $sortableItem = jQuery(this);
			var imageTop = $sortableItem.position().top;
//			var imageLeft = $sortableItem.position().left;
//			var imageWidth = $sortableItem.width();
			var imageHeight = $sortableItem.height();
			var $dragDropIcon = $sortableItem.parent().parent().find('.dragDropIcon:first');
			$dragDropIcon.css({'top': imageTop+(imageHeight/2)-($dragDropIcon.height()/2), 'display':''});
		},
		
		function () {
			setTimeout("hideDragDropIcon()", 3000);
		}
		
	);	
	
}

function hideDragDropIcon() {
	jQuery('.dragDropIcon').fadeOut('slow');
}



//any form element that has class="bindCookie" will have a persistent value (using a cookie)
function bindFormCookies() {
	var $formField;
	
	jQuery('.bindCookie').each(function() {
		$formField = jQuery(this);
		
		//retrieve saved form field values from cookie 
		value = jQuery.cookie($formField.attr('id'));
		if (value) {
			$formField.val(value);
		}
		
		//bind form field (on blur event) to save cookie
		$formField.blur(function() {
			jQuery.cookie(jQuery(this).attr('id'), jQuery(this).val(), { path: '/' });
		});
	});	
}


//this function parses and executes javascript vars and code embedded (by PHP) into the HTML content 
function phpJsConnector() {
	var name, value;
	jQuery('.javascriptConnector').each(function() {
		name = jQuery(this).attr('id');
		value = jQuery(this).val();
		if (name == 'js') {
			eval(value.myDecode());
		} else {
			if (isNumeric(value)) {
				jsCommand = name+'='+value+';';
			} else {
				jsCommand = name+'="'+value+'";';
			}
			eval(jsCommand);
			
//			log('assign var', name+'='+value);
		}
	});
}




//converts any links with class="ajaxify" to ajax links (clicking the link will update the existing page with the link page content and the url will be updated)
function ajaxifyLinks() {

	if (!browserAjaxUpdateSupport()) {
		log('reduced functionality', 'ajaxifyLinks() disabled for MSIE', 'orange');
		return;
	}

	jQuery('.ajaxify').each(function() {
		var $link = jQuery(this);
		var newUrl = _getLinkAddress($link);
		if (!newUrl) {
			return;	//ajaxify element specified is not a link
		}
				
		//do not ajaxify external links (eg. blog articles)
		if (newUrl.indexOf('http') == -1 || newUrl.indexOf(base_url) != -1) {
		
			var oldUrl = window.location.href.replace(base_url, '').replace(/^\/|\/$/, '');
			var oldController = oldUrl.replace(document.location.hash, '').split('/')[0];
			var oldAfterController = oldUrl.replace(oldController, '').replace(/^\/|\/$/, '');
			
			newUrl = newUrl.replace(base_url, '').replace(/^\/|\/$/, '');
			var newController = newUrl.split('/')[0].replace('#', '');
			var newAfterController = newUrl.replace(newController, '').replace('#', '').replace(/^\/|\/$/, '');
			
			//determine if we have to do a reload or whether we can simply change the hash location and do an ajax update
			if (oldAfterController.length && oldAfterController.indexOf('#') != 0 || (newController != oldController)) {
				//fix the url with a page reload
				//alert('fix url: '+newController+' '+newAfterController);
				newAfterController = (newAfterController) ? '#' + newAfterController : '';
				_changeLinkAddress($link, base_url + newController + newAfterController)
				
//				$link.attr('href', base_url + newController + newAfterController);
			}
			else {
				//we can do a location hash and an ajax update when the link is clicked
				$link.data('url', _getLinkAddress(jQuery(this)));	//save the original link url as data so that we can destroy any old onclick events  
				$link.attr('onclick', '');	//now remove any old onclick events

				$link.click(function(event) {	//create new onclick function
					var newUrl = _getLinkAddress(jQuery(this)).replace(base_url, '').replace(/^\/|\/$/, '');
					var newController = newUrl.split('/')[0].replace('#', '');
					var newAfterController = newUrl.replace(newController, '').replace('#', '').replace(/^\/|\/$/, '');

				
					
					//initiate a background ajax request
					if (!newAfterController) {
						newAfterController = 'none';
					}
					
					//alert('new hash : '+'#'+newAfterController);
					document.location.hash = newAfterController; //change page by updating the url - only update the anchor (to avoid a page reload)
					checkHash(); //invoke the function that will cause an ajax request to load the associated content
					return false; //kill default link behaviour
				});
			}
		}
  	});
}


//get the link address of an anchor (<a href="http://...") or an element with a javascript link (<span onclick="window.open('http://..', '_self')") or a url stored as jQuery "url" data 
function _getLinkAddress($element) {
	
	if ($element[0].tagName.toLowerCase() == 'a') {
		var url = $element.attr('href');
	} else {
		var url = $element.attr('onclick');
		if (!url || typeof url == 'undefined' || url.toString().indexOf('window.open') == -1) {
			url = $element.data('url');	//check for jQuery "url" data stored on the element
			if (!url) {
//alert('the ajaxify element "'+$element[0].tagName+'" specified is not a link');
				return false;
			}
		}
		
		url = url.toString();	//convert onclick function to string
		url = url.split('window.open');
		url = url[url.length-1].split(',')[0];	//the part after "window.open" and before a ","
		url = url.replace('"', '').replace("'", '').replace(' ', '').replace('(', '').replace(')', '').replace(/^\/|\/$/, '');
	}

	return url;
}


//set the link address of an anchor (<a href="http://...") or an element with a javascript link (<span onclick="window.open('http://..', '_self')")
function _changeLinkAddress($element, url) {
	if ($element[0].tagName.toLowerCase() == 'a') {
		$element.attr('href', url);
	}
	else {
		$element.attr('onclick', "window.open('"+url+"', '_self');");
	}
}





//for long pages, enable the scroll to top button to the page scroll postion  
function initScrollToTop() {
	
	//only implement the scroller button if the page is long
	if (jQuery(document).height() > (jQuery(window).height()*2)) { 

		//show scroll to top button when user scrolls far from top of page
		jQuery(window).scroll(function() {
	//		alert(jQuery(window).height()+' '+ jQuery(window).scrollTop())
	
			if (jQuery(window).scrollTop() > (jQuery(window).height())) { 
	  			if (browserOpacitySupport()) { //changing the opacity of PNGs in MSIE destroys transparency built into the PNG
					jQuery('#scrollTopButton').fadeIn();
				} else {
					jQuery('#scrollTopButton').css('display', '')
				}
			} else {
	  			if (browserOpacitySupport()) { //changing the opacity of PNGs in MSIE destroys transparency built into the PNG
					jQuery('#scrollTopButton').fadeOut();
				} else {
					jQuery('#scrollTopButton').css('display', 'none')
				}
			}
		});
	}
	
	//attach the scroll to top event to the button 
	jQuery('#scrollTopButton').mouseover(function() {
		jQuery('html, body').animate({ scrollTop: jQuery("body").offset().top }, 500);
	});	
}



//collapses content that starts off collapsed (to maximise SEO value of content) and inserts the toggle icon images 
function initToggleContent() {
	jQuery('.collapsedByDefault').slideUp(0).after('<div class="substituteDiv"></div>');		//hide content that is marked for collapse.  The extra div is inserted as a substitute block element 
	jQuery('.toggleIcon').css('cursor', 'pointer');
}



//Provides toggle content functionality for a toggle icon.  The content must be in a div that follows the toggle icon.  The toggle icon images for the open/collapsed state must be provided  
function toggleContent(toggleIconSelector, expandImage, collapseImage, groupSelector) {
	var $toggleIconElement = jQuery(toggleIconSelector);
	var $content = $toggleIconElement.next();
	if (!$content.is('div')) { $content = $content.next('div'); }	//the collapsing div may be one more element along 
	
	if ($content.css('display') == 'none') {
		if (typeof groupSelector != 'undefined') {
			toggleAllContent(groupSelector, false, expandImage);
		}
		$content.slideDown(500);
		$toggleIconElement.attr('src', collapseImage);
	} else {
		$content.slideUp(300);
		$toggleIconElement.attr('src', expandImage);
		if (!$content.next().hasClass('substituteDiv')) {
			$content.after('<div class="substituteDiv"></div>');	//the empty div prevents inline elements from shifting when the content is removed 
		}
	}
}


//Provides 'collapse all' or 'expand all' functionality.  This function expands/collapses(default) the content immediately following the specified toggle icon.  The toggle icon image is replaced by the specified image.
function toggleAllContent(toggleIconSelector, open, toggleImage) {
	var $toggleIconElement;
	var $content;
	if (typeof open == 'undefined') {
		open = false;
	}
	
	jQuery(toggleIconSelector).each(function() {
		$toggleIconElement = jQuery(this);
		$content = $toggleIconElement.next();
		if (!$content.is('div')) { $content = $content.next('div'); }	//the collapsing div may be one more element along 
		
		if ($content.css('display') == 'none' && open) {
			$content.slideDown(500);
			$toggleIconElement.attr('src', toggleImage);
		}
		
		if($content.css('display') != 'none' && !open) {
			$content.slideUp(300);
			$toggleIconElement.attr('src', toggleImage);
			if (!$content.next().hasClass('substituteDiv')) {
				$content.after('<div class="substituteDiv"></div>');	//the empty div prevents inline elements from shifting when the content is removed
			}
		}
		
	});
}





function jsHover() {
	
	jQuery('.jsHover').each(function() {
		
		jQuery(this).hover(
        	function() {
            	jQuery(this).css('cursor', 'pointer');
				jQuery(this).css('opacity', 0.7);
            },
			function() {
				jQuery(this).css('cursor', '');
				jQuery(this).css('opacity', '');
			} 
		);
  	});
}



//show busy animation
var whoIsBusy = 'unknown';
function busyStart(message) {
	whoIsBusy = message;
	
	var $busyAnimation = jQuery('#busyAnimation');
	if (!$busyAnimation.length) {
		alert('busy animation not found');
		return;
	}
	$busyAnimation.css('display', 'block');	//start the busy animation 
	jQuery('body').css('cursor', 'progress');	//start the busy cursor indicator 

	window.setTimeout('busyStop(true)', 5000);		//busy timeout (failed load) - stop the busy animation 
}




//stop busy animation
function busyStop(timedOut) {
	var $busyAnimation = jQuery('#busyAnimation');
	if (!$busyAnimation.length) {
		alert('busy animation not found');
		return;
	}
	if (timedOut && $busyAnimation.css('display') != 'none') {
		log('System Error', 'busy timeout: '+whoIsBusy, 'red');
//alert(whoIsBusy);
		whoIsBusy = 'unknown';
	}
	$busyAnimation.css('display', 'none');	//stop the busy animation now that the server has responded
	jQuery('body').css('cursor', 'default');	//stop the busy cursor indicator
}




//returns whether a submit is in progress enabling us to prevent multiple form submits (double clicks)
//usage: in the submit handler do "if (submitting()) return false;"
var submitting = false;
function submitDisabled() {

 	if (submitting)  {
		return submitting;	//a submit is already in progress (wait for completion)
	}
	
	submitting = true;
	window.setTimeout('submitTimeout()', 9000);
	return false;
}
function submitTimeout() {
	submitting = false;
}




//allows the popupDialog function to display the content at the specified url (via ajax call)   
function popupUrl(title, url, modal, data, callback) {
	busyStart('popup '+title);
	if (typeof data == 'undefined') {
		data = false;
	}
	
	if (jQuery('#urlModalDialog').length == 0) {
		jQuery('body').append('<div id="urlModalDialog" title="'+title+'"></div>');	
	}

	//load the specified url content via ajax call into the dialog placeholder
	jQuery('#urlModalDialog').load(url, data, function() {
		popupDialog('#urlModalDialog', '', modal, callback);
		jQuery('#urlModalDialog').dialog( "option", "title", title );	//change the title in the case that another dialog was open
		jQuery('.initialFocus').focus().select();	//select any default form element

		busyStop();
	});
}





//popup message window
var openDialogs = new Object();

function popupDialog(title, body, modal, callback) {
	modal = (typeof modal == 'undefined') ? false : modal;
	
	if (typeof body != 'undefined' && body.length > 0) {
		jQuery('#modalDialog').dialog('close').remove();	//remove any previous message
		jQuery('body').append('<div id="modalDialog" title="'+title+'"><p style="text-align:left;">'+body+'</p></div>');
		placeholder = jQuery('#modalDialog');
		openDialogs['#modalDialog'] = placeholder;	
	} else {
		if (typeof openDialogs[title] != 'undefined') {
			openDialogs[title].dialog('open');	//re-open existing dialog
			return;
		}
		placeholder = jQuery(title);
		openDialogs[title] = placeholder;
	}

	if (placeholder.length == 0) {
		alert("couldn't locate the dialog placeholder " + title);
		return;
	}	

	//initialize the dialog
	placeholder.dialog({
		modal: modal,
		resizable: true,
		width: 'auto',
		hide: 'slide',
		autoOpen: false
	});

	//execute any callback function on dialog open. Caution: if the callback script is loaded dynamically by popupUrl() then it may not execute properly in Firefox 
	if (typeof callback == 'function') {
		placeholder.bind("dialogopen", function(event, ui){
			callback();
		});
	}
	
	placeholder.dialog('open');	//open the dialog


	//maxHeight setting only applies to resizing - hence the need to manually limit the popup height to fit the viewport 
	if (placeholder.height() > (0.9*jQuery(window).height())) {
log('resize dialog height to', 0.8*jQuery(window).height());
		placeholder.dialog( "option", "height", 0.8*jQuery(window).height());
	}


/*	//maxWidth setting only applies to resizing - hence the need to manually limit the popup width to fit the viewport 
	if (placeholder.width() > (0.9*jQuery(window).width())) {
log('resize dialog width to', 0.8*jQuery(window).width());
		placeholder.dialog( "option", "width", (0.8*jQuery(window).width()));
	}
*/
}



//close any open popup dialogs
function closePopupDialogs() {
	var size = 0;
	for (key in openDialogs) {
        if (openDialogs.hasOwnProperty(key)) {
			if (openDialogs[key].dialog('isOpen')) {
log('closing dialog', key);
				openDialogs[key].dialog('close');
			}
		}
    }
}



function displayResult(selector) {
	var $element = jQuery(selector);
	if (!$element.length) {
		return;
	}
	
	$element.css('display', 'inline')
		.css('opacity', 0.3)
		.css('fontSize', '0.3em')
		.animate({ fontSize: "1.5em", opacity: 1.0 }, 2000 ).animate({ opacity: 0.0 }, 8000 ).animate({ fontSize: "0em" }, 1000 );	//the fontsize reduction is because IE8 ignores the opacity fade
}






function animateResult(selector, className, message, cantWait) {
	var $target = jQuery(selector);
	
	//if the result target doesn't exist then send the message to the general server-result box
	if (!$target.length) {
		$target = selector.replace('-result', '');
		$target = jQuery($target);
		if (!$target.length) {
			$target = jQuery('#server-result');
		} else {
			$target.after(' <span id="'+selector.replace(/^[#.]/g, '')+'"></span>');	//insert a result container
			$target = jQuery(selector);
		}
	}

	//if there's already an animation running wait till it finishes 
	if ($target.queue("fx").length > 3 && typeof cantWait == 'undefined') {		//for some reason the last 3 animations don't get cleared 
log('queue size', $target.queue("fx").length);
	window.setTimeout('animateResult("'+selector+'", "'+className+'", "'+message+'", true)',3000);	//there's another message being displayed - wait 3 seconds and then overwrite it with this message
log('delay message', 'animateResult("'+selector+'", "'+className+'", "'+message+'")');
//alert('delay queue '+$target.queue("fx").length);
		return;
	} else {
		$target.stop(true, true).clearQueue(); 	//kill any queue and display this right away
	}
	
log('displaying', message);
	
	$target.css('display', '');
	if ((typeof message != 'undefined' && message.length > 0 && message != 'undefined')) {
		message = message.replace(/\+/g, ' ');	//fixes an incompatibility between php url encode and javascript decode
		message = decodeURIComponent(message);
		$target.html(message);
	}	
	$target.addClass(className);
	//jquery dialog boxes css may overide our stylesheet so ensure errors are red etc

	$target.css('opacity', 0.3);
	$target.css('fontSize', '0.3em');

	switch (className) {
		case 'success':
			//$target.css('color', '#00FF00');
			$target.addClass('success');
			$target.animate({ fontSize: "1.5em", opacity: 1.0 }, 1000 ).delay(1500).fadeOut();			
		   	break;
		default:
			//$target.css('color', '#FF0000');		//assume it's an error
			$target.addClass('error');
			$target.animate({ fontSize: "2em", opacity: 1.0 }, 1000 ).delay(4000).fadeOut(2000);	//the fontsize reduction is because IE8 ignores the opacity fade			
			break;
	}
}





function removeElements(jQuerySelector) {	
	jQuery(jQuerySelector).fadeOut('slow').slideUp('slow', function () {
      this.remove();
    });
}




function shake(selector) {
 	jQuery(selector).fadeIn(100).animate({left:"-=20px"},100).animate({left:"+=20px"},100).animate({left:"-=20px"},100).animate({left:"+=20px"},100).animate({left:"-=20px"},100).animate({left:"+=20px"},100);
}




function reloadPage(url) {
	//if url is specified and is different 
	if (url && (url != window.location.href)) {
		
		//if only the hash part of the url is different   
		if (url.split('#')[0] == window.location.href.split('#')[0]) {
			var newHash = url.split('#')[1];
			newHash = (newHash != undefined) ? newHash : 'none';
			alert(newHash);
			document.location.hash = newHash;		//set new # part of url
			if (typeof window.checkHash == 'function') {
				checkHash(); //this causes any # anchors in the url to get handled by ajax
			}
		} else {
			window.open(randomiseUrl(url), '_self');	//the random element in the url prevents Internet Explorer from caching pages
		}

	} else {
		//no url provided or url has not changed - just reload page
		window.location.reload(true); //force a complete reload (not using the browser cache)
	}	
}








//Internet Explorer insists on caching pages and so we need a random element in the url to prevent caching
function randomiseUrl(url) {
	var randomQueryString = '?nocache='+Math.round(Math.random() * 10000);
	
	if (typeof url == 'undefined' || url == '_self') {
		var hash = (document.location.hash) ? '#'+document.location.hash : '';
		var currentUrl = window.location.href.split('?nocache=')[0];	//remove any existing random element from url
		currentUrl = window.location.href.split('#')[0];	//remove any portion from hash from url
log ('randomising url', currentUrl+randomQueryString+hash);
		return currentUrl+randomQueryString+hash;
	} else {
		if (typeof url != 'string') {
			alert('the randomise() function requires the url to be a string');
		}
		var hash = (url.split('#').length > 1) ? '#'+url.split('#')[1] : '';
log ('randomising url', url+randomQueryString+hash);
		return url.split('#')[0]+randomQueryString+hash;
	}
}









var ajaxUpdateInProgress = false;

//compares the current webpage with a refreshed version of itself (or an entirely different url) and updates the current page content  
function updateWebpage(sourceUrl, container, data, updateSelector) {
//alert('updateWebpage ('+sourceUrl+', '+container+', '+data+', '+updateSelector);
	if (ajaxUpdateInProgress) { return; } //one update at a time 
	ajaxUpdateInProgress = true;

//jsDebug = true;
	if (typeof data == 'undefined') {
		data = false;
	}

	if (typeof container == 'undefined' || container.indexOf('#') == -1) {
		alert('System error: missing or badly formed arg "container" in fn updateWebpage(url, container, data)');
	}

	//determine the url of the updated page content (if the url contains a hash then convert it to a non-ajax url)
	if (typeof sourceUrl == 'undefined' || !sourceUrl || sourceUrl == '_self') {
		sourceUrl = window.location.href;
		sourceUrl = sourceUrl.replace('#', '/');	//convert an ajax hash url to a proper segment based url. eg. wiki#page/554 --> wiki/page/554
		sourceUrl = randomiseUrl(sourceUrl);	//for Internet Explorer we need a random element in the url to prevent caching
	}
	

	//create a jQuery function so that we can update the DOM in reverse (child nodes first) 
	jQuery.fn.reverse = function() {
	    return this.pushStack(this.get().reverse(), arguments);
	};


	//if an update selector is specified then only update the specified selector content else update all elements with an id without class="noUpdate"
	if (typeof updateSelector == 'undefined') {
		//get an array of all elements with an id (in reverse order) - we do this before inserting the updated content div
		var oldContentIds = jQuery(container + ' *[id]').reverse();
	} else {
		var oldContentIds = jQuery(updateSelector).reverse();
	}


	//create the temporary placeholder for the updated page
	if (!jQuery('#updatedPage').length) {
		jQuery('body').append('<div id="updatedPage" style="display:none"></div>');	
	}

	busyStart('updateWebpage('+sourceUrl+')');	//start the busy cursor indicator because we're going to initiate an ajax load 

	jQuery(container + ' .hideOnAjaxUpdate').animate({opacity: "0.0"});	//hide content that's about to be replaced by new ajax content 

	//load the specified (else current) page via ajax call into the placeholder and update any changed ids
	jQuery('#updatedPage').load(sourceUrl+' '+container, data, function() {

		oldContentIds.each(function() {
			_updateId('#'+this.id, '#updatedPage #'+this.id);
		});
		
		//now update the whole container if it's still different
		if (typeof updateSelector == 'undefined') {
			_updateId(container, '#updatedPage ' + container);
		}
		
		jQuery(container + ' .hideOnAjaxUpdate').animate({opacity: "1.0"});	//reveal new ajax content

		jQuery('#updatedPage').remove();	//this page fragment causes duplicate ids which can interfere with other DOM scripts

		//create a bookmark link for ajax urls
		jQuery('head').append('<link rel="bookmark" href="'+sourceUrl+'" />');
		
		//run the page initialisation javascript (this would happen for a non-ajax page load)
		ajaxUpdateInProgress = false;	//this allows the following functions to execute
		onAjaxDocumentReady();
		setTimeout('onAjaxWindowLoaded()', 500);	//the delay seems to be necessary in some cases

		busyStop();	//stop the busy animation now that the server has responded
//jsDebug = false;
	});
}



function _updateId(oldSelector, newSelector) {
//jsDebug=true;
//log('comparing ids', oldSelector+' == '+newSelector)

	var $old = jQuery(oldSelector);
	
	if (!$old.hasClass('noUpdate') && (oldSelector.indexOf('cke') == -1) && (oldSelector != '')) {
		//does element exist in updated content
		var $new = jQuery(newSelector);
		if ($new.length) {
			var newContent = $new.html();
			
			//update changed content
			if ($old.html() != newContent) {
				$old.html(newContent);		//update changed element content
log('updated content for ', oldSelector+' <-- '+newSelector);
//alert('updated content for '+oldSelector+' <-- '+newSelector)
			} else {
//log('unchanged - skipped update for id', oldSelector)
			}
			
			//update element attributes
			applyAttributes($old, getAttributes($new, ['class', 'src', 'title'])); 	//update changed attributes
/*	
			if ($old.attr('class') != $new.attr('class')) {
				$old.attr('class', $new.attr('class'));		//update changed content
log('updated class for id', oldSelector);
			}
*/					
			//update changed display & visibility styles
			applyStyles($old, getStyles($new, ['display', 'visibility'])); 	//update changed styles
			
		} else {
			log('hide id', oldSelector);
			$old.fadeOut();
		}
	} else {
log('noUpdate - skipped update for id', oldSelector);
	}
}



function isNumeric(input)
{
   return (input - 0) == input && input.length > 0;
}




