/**
 * Lightbox launch controller
 *
 * @usage		LaunchController.init
 * @copyright	Productive Business Systems Ab
 * @uses		jquery.history
 * @uses		nyroModal
 */
var LaunchController = new function() {
	this.settings = {};
	this.currentUri = null;
	this.rootUri = '/';
	this.domains = [];
	var _this = this;
	
	// Launch control initializer
	this.init = function(settings) {
		_this.settings = settings;
		if( typeof settings.rootUri !== 'undefined' ) {
			_this.rootUri = settings.rootUri;
		}
		if( typeof settings.domains !== 'undefined' ) {
			_this.domains = settings.domains;
		}		
		
		// Get the url that the lightbox was launched from
		_this.source = {
			href: location.href,
			title: document.title
		}
		$.history.init( _this.pageLoad );
		
		$('a:not([href^=mailto])').livequery( 'click', _this.click );		
	}
	
	// Click handler
	this.click = function( e ) {
		// Shouldn't be in this class...
		$('#map_menu').fadeOut( 'fast' );		
		
		var target = $(this).attr('target');
		if( target != null && target != '' && target != '_self' ) {
			return true;
		}
		
		// Disable hash linking by using no-forward class in links
		if( $(this).hasClass('no-forward') ) {
			return true;
		}
				
		var href = $(this).attr('href');
		if( typeof href == 'undefined' ) {
			return true;
		}
		
		if( href == '' ) {
			e.preventDefault();
			return false;
		}

		// Do not handle external links
		if( href.indexOf('/') !== 0 ) {
			var domain = href.replace(/[^\:]+:\/\/[^\.]+\.([^/]+).*/, '$1');
			if( domain == href ) {
				$(this).attr('href', LaunchController.rootUri + $(this).attr('href') );
			} else if( $.inArray( domain, _this.domains ) == -1 ) {
				return true;
			} else {
				href = href.replace(/[^\:]+:\/\/[^/]+(\/+)?(.*)/, '$2');
			}
		}

		// Direct access to physical files
		if( ( lastDot = href.lastIndexOf('.') ) > -1 ) {
			var ext = href.substr( lastDot + 1 );
			if( ext.substr( ext.length - 1, 1 ) == '/' ) {
				ext = ext.substr( 0, ext.length - 1 );
			}

			if( ext != parseInt( ext ) && ext.length >= 2 && ext.length <= 4 ) {
				return true;
			}
		}
			
		var parents = $(this).parents();
	
		parents.each(function (index, domEle) {
			if( $(domEle).attr('id') == 'nyroModalFull' )
				return true;
		});
		
		e.preventDefault();
		var hash = _this.getHash( $(this).attr('href') );	
		
		// Livequery plugin doesn't obviously work in IE, so we have to use a workaround
		// [Toni 2010-12-30:] NOTE! .browser property does not detect IE8. 
		// jQuery advices against the use of this property 
		if( $.browser.msie ) {
			hash = hash.replace(/http\:\/\/[^\/]+/gi, '');
		}
		if( hash ) {
			$.history.load( hash );
		}
		
		return false;
	}
	
	// Helper method to extract hash from a URL
	this.getHash = function( url ) {
		return url.replace(/^.*#/, '');
	}
	
	this.normalizeHash = function( hash ) {
		if( !hash ) {
			return null;
		} else {	
			// Illegal hash, return to main page
			if( hash.indexOf( _this.rootUri ) != 0 ) {
				hash = null;
				location.href = _this.rootUri;
			} else {
				// Remove extra slashes
				while( hash.indexOf('//') != -1 ) {
					hash = hash.replace(/\/\//g, '/');
				}	
				
				var hashParts = hash.split('?', 2);
					
				// Add slash at the end if needed
				if( hashParts[0].lastIndexOf('/') != hashParts[0].length - 1 ) {
					hashParts[0] += '/';
				}
				
				// Check if already escaped
				var alreadyEscaped = escape( hashParts[0] ) == encodeURI( hashParts[0] );
				if( alreadyEscaped == false ) {
					hashParts[0] = escape( hashParts[0] )
				}	
				hash = hashParts[0] + ( typeof hashParts[1] != 'undefined' ? '?' + hashParts[1] : '' )				
			}
		}
	
		return hash;
	}
	
	// For some reason jQuery is unable to extract data from head and body tags
	this.getItemData = function( tagName, data ) {
		var tagData = null;
		var tagStart = data.indexOf( '<' + tagName );
		if( tagStart != -1 ) {
			tagEnd = data.indexOf( '</' + tagName + '>', tagStart + tagName.length + 1 ) + tagName.length + 3;
			tagData = data.substring( tagStart, tagEnd );

			tagData = tagData.replace('<' + tagName, '<div');
			tagData = tagData.replace('<\/' + tagName, '</div'); 
			
			return $(tagData);
		}
		
		return null;
	}
	
	// Page loader used by browser history plugin
	this.pageLoad = function( hash ) {
		log('PageLoad: ' + hash);
		log('Current_uri: ' + _this.currentUri);
		var lightbox = false;
		var folder = null;
		
		hash = _this.normalizeHash( hash );
		
		// Get parent folder
		if( hash != null ) {
			var folders = hash.replace(_this.rootUri, '/').split('/');
			
			if( folders.length < 4 ) {
				folder = null					
			} else {
				folder  = folders[1];
			}
		}

		// Handle root index requests
		if( hash == null ) {
			location.href = _this.rootUri + '#' + _this.rootUri;
			return;
		}
		
		// Use non-hash URI for folders defined
		if( folder != null && $.inArray( folder, _this.settings.directFolders ) != -1 ) {
			location.href = hash
		}
			
		if( hash == _this.currentUri || ( _this.currentUri == null && hash == _this.rootUri ) ) {
			return;
		}
		
		_this.currentUri = hash;

		// ...otherwise make an ajax request
		// Use a timestamp to prevent.. guess who... IE!!! caching..
		var ts = new Date().getTime();
		pageLoader(true);
		log('PageLoadAjax: ' + hash + '            ' + location.search.substring(1) + '&dummy=' + ts);
		$.ajax({
 			type: 'GET',
 			url: hash,
 			data: location.search.substring(1) + '&dummy=' + ts,
 			dataType: 'html',
 			
 			error: function(data) {
 				// Display the 404 error page
 				log('PageLoadAjaxError');
 				var body = _this.getItemData( 'body', data.responseText );
 				var title = _this.getItemData( 'title', data.responseText );
				
 				document.title = title.text();
 				$('body').html( body.html() );
 				$('body').attr('class', body.attr('class') );
 				pageLoader(false);
 			},
 			success: function(data) {
 				log('PageLoadAjaxSuccess')
 				pageLoader(false);
 				// Used for password-protected folders
 				matches = data.match(/Location\:\s\<(.+)\>/);
 				if( matches != null && typeof matches[1] !== 'undefined' ) {
 					location.href = matches[1];
 				}
 				
 				_this.currentUri = hash;
 							
 				if( typeof pageTracker !== 'undefined' ) {
 					pageTracker._trackPageview( hash );
 				}
 				
 				// Add additional stylesheets if they don't already exist 
 				var head = _this.getItemData( 'head', data );
 				
 				$('link', head).each(function( a, html ){
 					if( $('head link[@href="' + $(html).attr('href') + '"]').length == 0 ) {
 						$('head').append( html );
 					}
 				});

				var body = _this.getItemData( 'body', data );
				
				if( body.hasClass( 'family_lightbox' ) ) {
				
					// Enable back-button
					if($('body').hasClass('family_system'))
						location.reload(true);
					
					// This is a workaround because jQuery doesn't allow fetching head elements using the normal way
					var title = _this.getItemData( 'title', data );
					document.title = title.text();
					
					var rawDimensions = body.attr('class').replace(/.*type_([0-9]{1,4}x[0-9]{1,4}).*/, '$1');
					
					if( rawDimensions == body.attr('class') ) {
						if(body.attr('class').indexOf('size_big') != -1)
						{
							//[Toni 2011-02-11:] Replaced 620 with 555 to have a consistent height, 
							//Having two different lightbox heights caused all sorts of fuzzy problems
							//in transitions between associations with small vs big lightboxes, see 31059
							var dimensions = { width: 740, height: 620 };
						}
						else
						{
							var dimensions = { width: 580, height: 555 };
						}
					} else {
						var dim = rawDimensions.split('x');
						var dimensions = { width: dim[0], height: dim[1] };
					}
					
					var prevLightboxClass = $('body').attr('prev_lightbox_class');
					$('body').attr('prev_lightbox_class', body.attr('class') );
					
					// If directly accessing a lightbox set background page
					if( location.href == _this.source.href ) 
					{
						// [Toni 2010-11-08:] Rewrote this code block completely to cope with 29021/29666
						// Load the background page in the background		
						global_launchController = _this;
						setTimeout("loadBackdrop('/startsidan');", 1000);
						_this.source.href = '';
					}
					
					$.nyroModalManual({
						type: 'content',
						debug: false,
						content: body.html(),
						modal: true,
						width: dimensions.width,
						height: dimensions.height,
						endShowContent: endShowContent,	
						showContent: showContent,
						showTransition: showTransition,
						hideTransition: hideTransition,
						bodyClasses: { newBody: body.attr('class'), oldBody: prevLightboxClass }
					});
					
					if( !body.hasClass( 'type_riks_blogg' ) ) {
						// Adjust sizes.
						if(body.attr('class').indexOf('size_big') != -1)
						{
							$('.rightcol, .contentarea').css({'width' : '510px'});
							//[Toni 2011-02-11:] Replaced 620 with 555 to have a consistent height, 
							//Having two different lightbox heights caused all sorts of fuzzy problems
							//in transitions between associations with small vs big lightboxes, see 31059							
							$('.wrapper, #nyroModalContent').css({'width' : '740px', 'height' : '620'});
							
							$('.rightcol').css({'height' : 'auto'});//[Toni 2010-09-30:] was 620px, fixed 29506 							
							$('.leftcol').css({'height' : 'auto'}); //[Toni 2010-02-11:] Now performing the same height on big lightboxes as small ones
							//$('.leftcol').css({'height' : '450px'});//[Toni 2010-09-30:] separated .leftcol and .rightcol

							//[Toni 2010-02-11:] Now performing the same height on big lightboxes as small ones
							/*
							$('.contentarea').css({'height' : '427px'});
							$('.menu_box').css({'height' : '460px'}); //[Toni 2010-09-30:] was 520px, fixed 29506
							*/
							
							$('.contentarea').css({'height' : '500px'});
							$('.menu_box').css({'height' : '550px'}); //[Toni 2010-09-30:] was 590px, fixed 29506
						}
						else
						{
							$('.rightcol, .contentarea').css({'width' : '370px'});
							$('.wrapper, #nyroModalContent').css({'width' : '580px', 'height' : '555'});
//							$('.leftcol .rightcol').css({'height' : '450px'}); 

							$('.rightcol').css({'height' : 'auto'}); 
							$('.leftcol').css({'height' : 'auto'});//[Toni 2010-09-30:] separated .leftcol and .rightcol

							$('.contentarea').css({'height' : '427px'});
							$('.menu_box').css({'height' : '460px'}); //[Toni 2010-09-30:] was 520px, fixed 29506
						}
					}
				} else {
					_this.source = {
						href: location.href,
						title: document.title
					}					
					
					if( $('#nyroModalFull').length ) {
						_this.closeLightbox();
					}

					// This is a workaround because jQuery doesn't allow fetching head elements using the normal way
					var title = _this.getItemData( 'title', data );
					document.title = title.text();
					
					// Find old class with family
					var classes = $('body').attr('class').split(' ');
					var oldFamilyClass = false;
	
					for( i in classes ) {
						if( classes[i].indexOf('family_') == 0 ) {
							oldFamilyClass = classes[i];
							break;
						}
					}

					// Find new class with family
					var newFamilyClass = false;
					if( body.attr('class') ){
						var classes = body.attr('class').split(' ');
						
						for( i in classes ) {
							if( classes[i].indexOf('family_') == 0 ) {
								newFamilyClass = classes[i];
								break;
							}
						}
					}
					
					// Apply transitions
					if( typeof _this.settings.transitions[oldFamilyClass] !== 'undefined' && typeof _this.settings.transitions[oldFamilyClass][newFamilyClass] !== 'undefined' ) {
						// Try custom function
						_this.settings.transitions[oldFamilyClass][newFamilyClass]( body );
					} else if( typeof _this.settings.transitions[oldFamilyClass] !== 'undefined' && typeof _this.settings.transitions[oldFamilyClass]['ALL'] !== 'undefined' ) {
						_this.settings.transitions[oldFamilyClass]['ALL']( body );
					} else {
						var head = _this.getItemData( 'head', data );

						$('script[rel="init"]', head).each(function(){
							$.getScript( $(this).attr('src') );
						});

						$('body').attr('class', body.attr('class') );						
					}
				} 
				runTransitionReadyFunctions();
				//Check if current page is in history and add it if not.
				$.prevPagesCheck(location.href);
				$.prevPagesAdd(location.href);
 			} //end of success function
		});
		
		
		
		$('body').removeClass('lbopen');
		
	}
	
	// Lightbox closer method
	this.closeLightbox = function() {
		$.nyroModalRemove();
		this.currentUri = _this.source.href;
		location.href = _this.source.href + (_this.source.href.indexOf('#') == -1 ? '#' : '');
	}
}

var global_launchController;
function loadBackdrop(url)
{
	$.ajax({
			type: 'GET',
			url: url,
			data: null,
			dataType: 'html',
			
			error: function(data) {
				// Display the 404 error page
				var body = global_launchController.getItemData( 'body', data.responseText );
				var title = global_launchController.getItemData( 'title', data.responseText );
			
				document.title = title.text();
				$('body').html( body.html() );
				$('body').attr('class', body.attr('class') );
				pageLoader(false);
			},
			success: function(data) {
				var background = global_launchController.getItemData( 'body', data );
				initStart(background);
				runTransitionReadyFunctions();
			}
	});	
}

function runTransitionReadyFunctions(){
	if($('#transitionReadyFakeButton').length > 0){
		$('#transitionReadyFakeButton').click().unbind('click');
	}
}

function transitionReady(fn){
	if($('#transitionReadyFakeButton').length == 0){
		$('body').append('<button style="display: none;" type="button" disabled="disabled" id="transitionReadyFakeButton" name="transitionReadyFakeButton"></button>');
	}
	$('#transitionReadyFakeButton').click(fn);
}

$(function(){
	runTransitionReadyFunctions();
});

/**
 * Content replacer animation
 *
 * @usage		$(<selector>).replace(<selector>, <OPTIONAL context>);
 * @example		$('#content_container').replace('#content_container', $('body'));
 * @copyright	Carus PBS Ab Ltd.
 */
jQuery.fn.replace = function( selector, context, callback ) {
	 return this.each(function(){
		 
		// Make some adjustments to the content element
		var content = $(this);
		content.css('position', 'relative');
		content.css('overflow', 'hidden');
		
		// Wrap contents into a absolute div transport element and set fixed height for the element.
		// Furthermore, store the original css height definition to restore it after the animation.
		var oldTransporter = $('<div id="old" class="slide" style="position: absolute; left: 0; width: 100%; height: 100%; overflow: hidden;"></div>');
		var newHeight = $(selector, context).css('height');
		content.height( content.height() );
		content.wrapInner( oldTransporter );
		oldTransporter = content.find('.slide');
		
		// Create a transport element for the new contents and place it in the margin
		var newTransporter = $('<div id="new" class="slide" style="position: absolute; left: 0; width: 100%; margin-left: 100%; height: 100%; overflow: hidden;"></div>')
			.html( $( selector, context ).html() )
			.attr('set_height', newHeight)
			.appendTo( content );

		// Animate the appearance of the new contents
		newTransporter.animate({
			marginLeft: 0
		}, 1000, 'linear', function(){ 
			// Reset the store height property
			$(this).parent().css('height', $(this).attr('set_height') );
			// Replace the transporter with its contents
			$(this).children().appendTo( $(this).parent() );
			if( typeof callback != 'undefined' ) {
				callback.apply( $( selector, context ) );
			}

			$(this).remove();
		});
		
		// Animate the disappearance of the old contents.
		oldTransporter.animate({
			marginLeft: -oldTransporter.width()
		}, 1000, 'linear', function(){
			// Finally remove the transporter
			$(this).remove();
		});
	});
}

jQuery.fn.backReplace = function( selector, context, callback ) {
	return this.each(function(){
		// Make some adjustments to the content element
		var content = $(this);
		content.css('position', 'relative');
		content.css('overflow', 'hidden');
		
		// Wrap contents into a absolute div transport element and set fixed height for the element.
		// Furthermore, store the original css height definition to restore it after the animation.
		var oldTransporter = $('<div id="old" class="slide" style="position: absolute; right: 0; width: 100%; height: 100%; overflow: hidden;"></div>');
		var newHeight = $(selector, context).css('height');
		content.height( content.height() );
		content.wrapInner( oldTransporter );
		oldTransporter = content.find('.slide');
		
		// Create a transport element for the new contents and place it in the margin
		var newTransporter = $('<div id="new" class="slide" style="position: absolute; right: 0; width: 100%; margin-right: 100%; height: 100%; overflow: hidden;"></div>')
			.html( $( selector, context ).html() )
			.attr('set_height', newHeight)
			.appendTo( content );

		// Animate the appearance of the new contents
		newTransporter.animate({
			marginRight: 0
		}, 1000, 'linear', function(){ 
			// Reset the store height property
			$(this).parent().css('height', $(this).attr('set_height') );
			// Replace the transporter with its contents
			$(this).children().appendTo( $(this).parent() );
			if( typeof callback != 'undefined' ) {
				callback.apply( $( selector, context ) );
			}

			$(this).remove();
		});
		
		// Animate the disappearance of the old contents.
		oldTransporter.animate({
			marginRight: -oldTransporter.width()
		}, 1000, 'linear', function(){
			// Finally remove the transporter
			$(this).remove();
		});
	});
}

/**
 * Vertical scroller jQuery plugin
 *
 * @usage		$(<selector>).vertScroller();
 * @example		$('.scrollable').vertScroller();
 * @copyright	Carus PBS Ab Ltd.
 */
jQuery.fn.vertScroller = function( settings ) 
{
	settings = settings || {};
	var contentEls = [];
	var scrollerEls = [];
	var clickPixelStep = 200;
	var wheelPixelStep = 30;
	var pixelsPerSecond = 200;
	var i = 0;
	var settings = {
		width: typeof settings.width !== 'undefined' ? settings.width : null,
		height: typeof settings.height !== 'undefined' ? settings.height : null,
		position: typeof settings.position !== 'undefined' ? settings.position : null
	}

	return this.each(function(){
		// Make a scroller div inside the content element
		var contentEl = $(this);
		
		//[Toni 2010-11-10:] Make a distinction betwen lightbox elements and regular page types
		var isLightbox = contentEl.parents('.lightbox_content').length>0;
		var isBlank = contentEl.parents('.type_blank').length>0;
		if(isLightbox)
		{
			
			//[Toni 2011-02-11:] Moved this code into this block. 
			//The lightbox height is needed to calculate the other heights
			
			//Get the gross  height of the lightbox used for content height calculations
			var lightbox_height = parseInt($('#nyroModalWrapper').css('height'));	//FF
			if( isNaN(lightbox_height) )	
			{
				lightbox_height = $('#nyroModalWrapper').clientHeight;	//IE	
			}
					
			//[Toni 2010-10-15:] Added this variable to keep track of lightbox scalings
			var lightbox_scaled = false;
		
			//[Toni 2010-10-12 - 2010-10-13:] Fix differing overhead heights on news pages
			var overhead;
			if(!isBlank)
			{
				var lightbox_borders = 76;
			} else {
				var lightbox_borders = 32;
			}
			var container = contentEl.parents('.lightbox_content')[0];			
			var contactbox_height = 0;
			if($(container).find('.contact_box').length >0)
			{
				contactbox_height = 87;
			} 
			overhead = lightbox_borders + contactbox_height;
		} 		
		
		//alert(overhead);
		// Remove old scroll buttons.
		$('.scroller', this).remove();
		
		if( contentEl.find('.scroller_area').length > 0 ) {
			// Remove scroller
			$('.scroller_area', this).children().appendTo( $('.scroller_area', this).parent() );
			$('.scroller_area', this).remove();
		}
				
		i++;
		
		var scrollerEl = $('<div/>')
			.addClass('scroller_area')
			.css({
				//width: '100%',
				//height: 'auto',
				margin: 0,
				padding: 0
			});
			
		contentEl.wrapInner( scrollerEl )
			.css('overflow', 'hidden');

		// Manually set element position if required
		if( settings.position != null ) {
			contentEl.css( 'position', settings.position );
		} else if( contentEl.css('position') != 'absolute' ) {
			contentEl.css('position', 'relative');
		}
		
		// If no height is set for the content element, we need to do some calculations using parent elements
		// [Toni 2010-09-22:] For some reason, this code was uncommented, but it is necessary when actual
		// display height is smaller than defined height. Removed the uncommentation, fixing case 29349 
		// Tested in FF,IE7 and IE8.
		var testEl = contentEl;
		var cssHeight = parseInt( testEl.get(0).style.height );

		if( isNaN( cssHeight ) || cssHeight == 0 ) {
			var x = 0;
			while( x++ < 100 && testEl.parent().length == 1 && testEl.parent().get(0).tagName != 'HTML' )
			{
				testEl = testEl.parent();
				//[Toni 2010-09-30:] Added test for %, the script used to interpret as 100% as 100px...er... :P
				if(isLightbox)
				{
					if(testEl.get(0).style.height.indexOf('100%') === 0)
					{
						//[Toni 2010-10-01:] Added a height for 100% in order to limit the height of left-column menus
						//[Toni 2010-10-12:] Fix differing overhead heights on news pages
						//some magic numbers...
						//[Toni 2011-02-11:] Use the actual lightbox height instead of magic number
						//cssHeight = 590;
						cssHeight = lightbox_height;
					} else {
						cssHeight = parseInt( testEl.get(0).style.height );						
					}
				}
														
				if( !isNaN( cssHeight ) && cssHeight > 0 ) 
				{
					//[Toni 2011-01-13] 30721. Overhead is deduced for districts and associations only. 
					// Do not use for news lightboxes (type blank)
					if(!isBlank)
					{
						cssHeight -=  overhead;
					} 
					contentEl.css( 'height', cssHeight + 'px' );
					x = 1000;//stop looping
				}
			}

			//[Toni 2011-01-24] 30857
			//Is cssHeight still NaN?
			//Means it is the "På gång" box on the startpage
			if( isNaN( cssHeight )) 
			{
				cssHeight =  260;	//Magic number, from civil.css, line 486
			}
		}

		//[Toni 2010-10-01:] Fix problems with overflow on [lightboxes on] lores screens and small windows
		if(isLightbox)
		{
			//[Toni 2010-12-30:] Rewrote this block completely to make it more logical and to cope with 29715
			
			//The maximum height that can be used for this element within the lightbox
			var height_limit;
						
			//Get the window height 
			var window_height = parseInt(window.innerHeight);	//FF
			if( isNaN(window_height) )	
			{
				window_height = document.documentElement.clientHeight;	//IE	
			}
			
			//Is the window smaller than the lightbox (+ its borders)?
			if( window_height < lightbox_height + lightbox_borders )
			{
				//Yes, use the window limits and set the scaled flag
				height_limit = window_height - overhead; //overhead include borders and contactbox height
				lightbox_scaled = true;
			} else {
				//No, use the lightbox limits
				height_limit = lightbox_height - contactbox_height;				
			}
			
			//Is this element higher than the limit?
			if(cssHeight > height_limit)
			{			
				//Set its height to the limit
				cssHeight = height_limit;
				contentEl.css( 'height', cssHeight + 'px' );
				//[Toni 2010-10-04:] Fixed a bug with the contacts footer not showing in IE
				contentEl.css( 'max-height', cssHeight + 'px' );
			}
		}
		
		// Update the scroller object. For some reason required here.
		var scrollerEl = contentEl.find('.scroller_area');		
		
		// Manually set element height if required
		if( settings.height != null ) {
			contentEl.height( settings.height );
		}
		
		// Manually set element width if required
		if( settings.width != null ) {
			contentEl.width( settings.width );
		}
		
		// Do not apply the scroller if there's no need for one
		// [AdCo] Added check for hidden elements as otherwise scrollerEl.height() is always 0 and scrollers never get added even if needed		
		var contentElDisplay = contentEl.css('display');
		contentEl.show()
		if( contentEl.height() >= scrollerEl.height() ) {
			contentEl.css('display', contentElDisplay);
			return;
		}
		contentEl.css('display', contentElDisplay);
		
		// Make the scroll area a little bit smaller
		scrollerEl.width( scrollerEl.width() - 20 );
		
		// Save elements for use in mouse events
		contentEls[i] = contentEl;
		scrollerEls[i] = scrollerEl;
		
		//[Toni 2010-11-10:] Added this check
		if(cssHeight)
		{
			scrollPos = 'top';
			scrollTopY = cssHeight - 36 + 'px';
			scrollBottomY = cssHeight -20 + 'px';
		} else {
			scrollPos = 'bottom';
			scrollTopY = '36px';
			scrollBottomY = '20px';
			//cssHeight = parseInt(contentEl.css('height'));
		}
		
		
		// Upwards scroll button
		$('<button class="scroll_up scroller"/>')
		.attr( 'scroller_id', i )
		.appendTo( contentEls[i] )
		//[Toni 2010-10-01:] Changed this to cssHeight (was contentEls[i].height(), that returned the wrong value) 
		.css( scrollPos, scrollTopY)
		.mousedown(function(){
			var i = $(this).attr('scroller_id');
			scrollerEls[i].stop();

			//[Toni 2011-02-10:] Added this check to enable scrolling of lightbox calendar menus
			if( isNaN( cssHeight )) 
			{
				cssHeight =  260;	//Magic number, from civil.css, line 486
			}			
			var maxMargin = contentEls[i].height() - scrollerEls[i].height() - 20;
			var progress = parseInt( scrollerEls[i].css('marginTop') ) / maxMargin;

			scrollerEls[i].animate({ 
				marginTop: 0
			}, 1000 / pixelsPerSecond * -maxMargin * progress );
			$(this).attr('start_margin',  parseInt( scrollerEls[i].css('marginTop') ) );
		}).mouseup(function(){
			var i = $(this).attr('scroller_id');
			scrollerEls[i].stop();
		}).click(function(){
			var i = $(this).attr('scroller_id');
			if( $(this).attr('start_margin') - parseInt(scrollerEls[i].css('marginTop')) < -10 )
				return;
						
			scrollerEls[i].stop();
			var maxMargin = contentEls[i].height() - scrollerEls[i].height() - 20;
			var margin = parseInt( scrollerEls[i].css('marginTop') );
			var step = margin < - clickPixelStep ? clickPixelStep : -margin;
			scrollerEls[i].css('marginTop', ( margin + step ) + 'px' );
		});	
		
		// Downwards scroll button
		$('<button class="scroll_down scroller"/>')
		.attr( 'scroller_id', i )
		.appendTo( contentEls[i] )
		.css( scrollPos, scrollBottomY)
		
		.mousedown(function(){
			var i = $(this).attr('scroller_id');
			scrollerEls[i].stop();
			
			//[Toni 2011-02-10:] Added this check to enable scrolling of lightbox calendar menus
			if( isNaN( cssHeight )) 
			{
				cssHeight =  260;	//Magic number, from civil.css, line 486
			}
			var maxMargin = cssHeight - parseInt(scrollerEls[i].height()) - 20;
			
			var progress = parseInt(scrollerEls[i].css('marginTop')) / maxMargin;

			scrollerEls[i].animate({ 
				marginTop: (cssHeight - scrollerEls[i].height() - 20) + 'px'
			}, 1000 / pixelsPerSecond * -maxMargin * (1 - progress) );
			
			$(this).attr('start_margin',  parseInt(scrollerEls[i].css('marginTop')) );
		}).mouseup(function(){
			var i = $(this).attr('scroller_id');		
			scrollerEls[i].stop();
		}).click(function(){
			var i = $(this).attr('scroller_id');			
			if( $(this).attr('start_margin') - parseInt(scrollerEls[i].css('marginTop')) > 10 )
				return;

			scrollerEls[i].stop();
			var maxMargin = cssHeight - parseInt(scrollerEls[i].height()) - 20;
			var margin = parseInt( scrollerEls[i].css('marginTop') );

			var step = margin - clickPixelStep > maxMargin ? clickPixelStep : margin - maxMargin;
			
			scrollerEls[i].css('marginTop', ( margin - step ) + 'px' );
		});			
		
		// Mouse wheel handling
		scrollerEls[i]
		.attr( 'scroller_id', i )
		.mousewheel(function(event, delta){
			var i = $(this).attr('scroller_id');
			scrollerEls[i].stop();
			var maxMargin = cssHeight - scrollerEls[i].height() - 20;
			var margin = parseInt( scrollerEls[i].css('marginTop') );
			var step = wheelPixelStep * delta;
			if( margin + step > 0 )	{
				step = -margin;
			} else if ( margin + step < maxMargin ) {
				step = maxMargin - margin;
			}
		
			scrollerEls[i].css('marginTop', ( margin + step ) + 'px' );		
			event.preventDefault();
		});		

		//[Toni 2010-10-15:] Added some tests to prevent the update box from disappearing under certain conditions
		//[Toni 2010-12-30:] Although inside each-function, this code is run only once per page load.
		if(isLightbox )
		{
			//Things needed to be done *after* the lightbox contents have been properly scaled and equipped with scrollers
			
			//Find out height of the right col used as position for "page updated" box
			var cHeight = parseInt($('.lightbox_content .rightcol .scrollable').css('height'));
			var updatePos = cHeight + 100;
			var maxHeight = parseInt($('.lightbox_content .leftcol .menu_box').css('height'))-12;
			if(updatePos > maxHeight)
			{
				updatePos = maxHeight;
			} 
			
			//[Toni 2010-10-04:] Make sure the "page updated" box is visible at all times
			$('.leftcol').css('position','relative');
			$('.page_updated').css('position','absolute');			
			$('.page_updated').css('top',parseInt(updatePos)+'px');	
			$('.page_updated').css('left','0px');	
			
			//[Toni 2010-12-30:] 29715, limit height of side menus, not to intersect with the "page updated" box
			updatePos -= (20+5)*3; //cut away menu headings (20 pixels high, 5 pixels margin)
			updatePos -= 8; //cut away a few pixels margin between menu and "updated box";
			
			$('.side_menu').find('.scrollable').each(function () {
				//get its present height
				cssHeight = parseInt($(this).css('height'));

				//is it higher than allowed?
				if(cssHeight > updatePos)
				{
					//Limit the element height
					$(this).css( 'height', updatePos + 'px' );
					$(this).css( 'max-height', updatePos + 'px' );					
				}
				
			});
		}	
	});

}

// Handle clicks outside the dropdowns in order to close them...
function handleBodyElementClicks() {
	if( $('.dropdown .content:visible').length > 0 && $('body').attr('hit') == 0 )
	{
		if( $(this).hasClass('dropdown') || $(this).parents('.dropdown').length > 0 ) {
			$('body').attr('hit', 1);
		}
	}		
}

// Body hits are handle last so here we close all dropdowns that should be closed
function handleBodyClicks() {
	if( $(this).attr('hit') == 0 ) {
		$('.dropdown .content').dropdownClose();			
	}
	$(this).attr('hit', 0);		
}

/**
 * Dropdown jQuery plugin
 *
 * @usage		$(<selector>).dropdown( <item hit callback>, <direction> );
 * @example		$('.dropdown').dropdown( showItem, 'down' );
 * @copyright	Carus PBS Ab Ltd.
 */
jQuery.fn.dropdown = function(callback, direction, settings) {
	$('body *').unbind('click', handleBodyElementClicks).bind('click', handleBodyElementClicks);
	$('body').unbind('click', handleBodyClicks).bind('click', handleBodyClicks);
	
	return this.each(function(){
		// Stop all animations
		$('.content', this).stop();
		$('.content', this).find('.top_row').remove();
		$('.content', this).find('.bottom_row').remove();
		
		if( direction == 'up' ) {
			$('.content', this).css('height', 0);
			$('.content', this).css('top', 0);
			$('.content', this).attr('direction', 'up');
			$('h4', this).removeClass('down').addClass('up');
			$('.content', this).removeClass('down').addClass('up');
			
			// Top corners
			$('<div class="top_row"><div class="corner_container"><div class="corner corner_top_left"></div><div class="corner corner_top_right"></div><div style="clear: both; font-size: 0; height: 0;"></div></div></div>')
				.prependTo( $('.content', this) );			
		} else {
			$('.content', this).css('height', 'auto');
			$('.content', this).css('top', '100%');
			$('.content', this).attr('direction', 'down');
			$('h4', this).removeClass('up').addClass('down');
			$('.content', this).removeClass('up').addClass('down');
			
			// Bottom corners
			$('<div class="bottom_row"><div class="corner_container"><div class="corner corner_bottom_left"></div><div class="corner corner_bottom_right"></div><div style="clear: both; font-size: 0; height: 0;"></div></div></div>')
				.appendTo( $('.content', this) );
		}
		
		if( typeof settings != 'undefined' && typeof settings.height != 'undefined' ) {
			$('ul', this).attr('max_height', settings.height);
		}
		
		if( $(this).hasClass('dropdown')  ) {
			return;
		}
		$(this).addClass('dropdown');
		
		var el = $(this);
		
		// Hit on items
		$('a', this).click(function(){
			$(this).parent().parent().parent().dropdownClose();
		});
		$('a', this).click( callback );
		
		
		// Hit on a dropdown
		$('h4', this).click(function(e){
			// Close all other dropdowns
			$('.dropdown[@id!="' + $(this).parent().attr('id') + '"] .content').dropdownClose();
			$(this).parent().find('.content').dropdownToggle();
		});		
	});
}
jQuery.fn.dropdownOpen = function( callback ) {
	return this.each(function(){
		if( $(this).attr('direction') == 'up') {
			$(this).height(0);
			$(this).show();
			var height = 12 + parseInt($('*:first', this).next().height());
			$(this).attr('sliding', 1);
			$(this).animate({
				'marginTop': '-' + height + 'px',
				'height': height + 'px'
			}, function(){
				$(this).vertScroller();
				if( callback != null ) {
					callback();
				}
			});	
		} else {
			$(this).slideDown( null, callback );
		}
	});
}

jQuery.fn.dropdownClose = function( callback ) {
	return this.each(function(){
		if( $(this).attr('direction') == 'up') {
			$(this).removeAttr('sliding');
			$(this).animate({
				'marginTop': 0,
				'height': 0
			}, function() {
				$(this).hide();
				if( callback != null ) {
					callback();
				}
			});
		} else {
				$(this).animate({
					'top': '100%'
				});
				
			$(this).slideUp( null, callback );				
		}
	});
}

jQuery.fn.dropdownToggle = function( callback ) {
	return this.each(function(){
		// Upwards "throwup" 
		if( $(this).attr('direction') == 'up' ) {
			if( $(this).attr('sliding') != 1 ) {
				$(this).dropdownOpen();
			} else {
				$(this).dropdownClose();		
			}
		// Classic dropdown
		} else {
			// Toggle the scrollbar and add a scroller
			$(this).slideToggle('fast', function(){
	
				// IE 7 fix
				var marginLeft = $('.corner_bottom_left', this).css('marginLeft');
				$('.corner_bottom_left', this).css('marginLeft', '0');
				$('.corner_bottom_left', this).css('marginLeft', marginLeft);
				
				// If max height is exceeded, extend upwards
				if( $(this).css('display') != 'none' ) {
					if( $(this).height() > $('ul', this).attr('max_height') ) {
						
						if( $('.top_row', this).length == 0 ) {
							$('<div class="top_row"><div class="corner_container"><div class="corner corner_top_left"></div><div class="corner corner_top_right"></div><div style="clear: both; font-size: 0; height: 0;"></div></div></div>').prependTo( $(this) );
						}							
						$(this).animate({
							'top': - ( $(this).height() - $('ul', this).attr('max_height') )
						});
					}	
				}
				$(this).vertScroller();
			});				
		}			
	});
}

/**
 * Vertical scroller jQuery plugin
 *
 * @usage		$(<selector>).slideMenu(<click selector>);
 * @example		$('.side_menu').slideMenu('h4 a');
 * @copyright	Carus PBS Ab Ltd.
 */
jQuery.fn.slideMenu = function(tag, menuClass) {
	if( tag == null ) {
		tag = 'h3';
	}
	if( menuClass == null) {
		menuClass = 'slide_menu';
	}
	
	//[Toni 2011-01-20:] Fixed 31059, open menu by default if lightbox
	var isLightbox = ( $('body').find('.lightbox_content').length > 0 );
		
	this.each(function(){
		//[Toni 2010-12-28:] Added some comments for clarity
		
		
		//Add the menu class to the menu item
		$(this).addClass(menuClass);
		
		//[Toni 2010-12-28:] Rewrote this block according to 29972:2
		//Open any active menu on the Tips&Råd page
		

		//[Toni 2011-02-10:] Additional fix to 31059, limit default lightbox menu to the first item
		if(isLightbox)	//is the menu item the first menu in a lightbox?...
		{		
			//mock variable for debug		
			var str = String(location);
			var isCalendar = str.indexOf('handelser')>0;
	
			//Don't do this if the calendar is supposed to be open (31622)
			if(!isCalendar)
			{
				//Note: At deployment, remember to update the corresponding page templates of districts and associations!
				$(tag, this).eq(0).addClass('default');
				$(tag, this).eq(0).parents('li').eq(0).find('.content').slideDown();
			}			
		} else {
			$(tag, this).each(function(){
				if(	$(this).parents('li').hasClass('open') ||	$(this).parents('li').hasClass('first_open') )	//...or is it classified as open?
				{
					$(this).addClass('default');
					$(this).parents('li').eq(0).find('.content').slideDown();
				}
			});
		}
		
		//Specify what happens when the user clicks the item
		$(tag, this).click(function() {
			//[Toni 2011-03-10] Rewrote this function once again (31582)
			var toggler = $(this).hasClass('toggled_open');		//Is the menu currently open (clicked by user)
			var _default = $(this).hasClass('default');			//Is the menu open by default (user entered a subpage directly by its url)
			
			//close all menus
			$(this).parents('.' + menuClass).find(tag).removeClass('default');					//Default menu only matters when entering the page the first time
			$(this).parents('.' + menuClass).find(tag).removeClass('toggled_open');				//Remove toggler class
			$(this).parents('.' + menuClass).eq(0).find('.content:visible').slideUp();			//Animate the menu until fully closed
			$(this).parents('.' + menuClass).find(tag).parents('li').removeClass('open');		//Remove class for open menu
			$(this).parents('.' + menuClass).find(tag).parents('li').removeClass('first_open');	//Remove class for open menu (first option in its parent menu)
			
						
			//Open the default menu or a clicked menu that is not open at the present
			if( _default || !toggler)
			{
				//open this menu 
				$(this).parent().addClass('open');								//Add class for open menu
				$(this).addClass('toggled_open');								//Add class to keep track of the toggler state
				$(this).parents('li').eq(0).find('.content').slideDown();		//Animate the menu until fully open
			} 				
				
		});
	});
	
	// For some weird reason this timeout is needed. Probably to assure the animation has completed.
	// [Toni 2010-12-28]: I can't see why this is needed. Seems to work alright without it in both FF and IE8.  
	// If the timeout is there, any open menu in Tips & Råd [29972:2] is closed. 
	// Hence I've added a bypass for Tips & Råd
	// [Toni 2011-01-20]: And now, this bypass shall apply to all side menus (30666).
	/*if(menuClass != 'tips--rad_cats')
	{			
		var __this = this;
		setTimeout( function() { 
			$('.content:visible', __this).hide();
			$('.default', __this).next().show();
		},1000);
	}
	*/
	return this;
}

/**
 * Vertical mouseover slider
 *
 * @usage		new vertMouseoverSlider( <menu object selector>, [ launcher selectors ] );
 * @example		new vertMouseoverSlider( '#access_menu', [ '#access', '#access_menu' ] );
 * @copyright	Carus PBS Ab Ltd.
 */
vertMouseoverSlider = function( menuEl, els ) {
	this.menuEl = menuEl;
	this.els = els;
		
	var _this = this;
	$(document).ready(function(){
		_this.init();
	});
}

vertMouseoverSlider.prototype = {
	
	// Slider constuctor
	init: function() {
		if( typeof this.els == 'object' ) {
			for( i in this.els ) {
				$(this.els[i]).bind( 'mouseover', {'_this':this}, this.slideDown );
				$(this.els[i]).bind( 'mouseout', {'_this':this}, this.slideUp );
			}
		} else {
			$(this.els).bind( 'mouseover', {'_this':this}, this.slideDown );
			$(this.els).bind( 'mouseout', {'_this':this}, this.slideUp );
		}
	},
	
	// Slide open method
	slideDown: function( e ) {
		var _this = e.data['_this'];
		if( !$(_this.menuEl).attr('max_height') ) {
			$(_this.menuEl).attr('max_height', $(_this.menuEl).height())
			$(_this.menuEl).height( 0 )
		}

		var progress = $(_this.menuEl).height() / $(_this.menuEl).attr('max_height') * 100;
		
		$(_this.menuEl).show(); // Webkit fix
		$(_this.menuEl).stop().animate({ 
	     		height: $(_this.menuEl).attr('max_height')
	      	}, 
	      	2 * (100 - progress)
		);			
	},
	
	// Slide close method
	slideUp: function( e ) {
		var _this = e.data['_this'];
		var progress = $(_this.menuEl).height() / $(_this.menuEl).attr('max_height') * 100;
		$(_this.menuEl).stop().animate({ 
				height: '0px'
			}, 
			2 * progress, 
			function(){ $(_this.menuEl).hide(); } // Webkit fix
		);			
	}
}

var pageLoaded = false;
var pageLoaderID  = false;
var pageLoaderRunning = false;
function pageLoader(startLoader){
	if(startLoader){
		if(pageLoaderID === false && pageLoaderRunning == false){
			pageLoaderID = setTimeout('runPageLoader();', 1000);
		}
	}else{
		//Stop loader
		if(pageLoaderID){
			clearTimeout(pageLoaderID);
			pageLoaderID = false;			
		}
		if(pageLoaderRunning){
			pageLoaderRunning = false;
			$('#page_loader').hide();
		}
	}
}
function runPageLoader(){
	pageLoaderID = false;
	if(pageLoaded == false){
		pageLoaderRunning = true;
		var loader = $('#page_loader');
		if( loader.length == 0 ) { 
			loader = $('<div id="page_loader" style="display: none;"/>')
			.css({
				position: 'absolute',
				left: '50%',
				top: '50%',
				padding: '10px',				
				zIndex: '100',
				border: 'solid 4px #000',
				background: '#333333 url(' + LaunchController.rootUri + '.composer/images/loader.gif) no-repeat center',
				color: '#fff',
				lineHeight: '50px',
				height: '100px',
				width: '100px',
				marginLeft: '-50px',
				textAlign: 'left'
			})
			.text( 'Laddar...' )
			.appendTo( $('body') );
		}
		loader.fadeIn();	
	}
}

