function toggleSearchField(searchField, value, newValue)
{
	if (searchField.value == value)
	{
		searchField.value = newValue;
	}
}

function toggleBox(heading)
{
	var box = heading.parentNode.getElementsByTagName("div")[0];
	box.style.display = box.style.display == "none" ? "block" : "none";
}

function initCarousel(id, scroll)
{
	$(id).className ="carousel";
	return new UI.Carousel(id, {scrollInc: scroll});
}

function hideSelects()
{
	if (browser == "Internet Explorer")
	{
		var selects = $$('select');
		for ( i = 0; i < selects.length; i++)
		{
			selects[i].style.visibility = "hidden";
		}
	}
}

function showSelects()
{
	if (browser == "Internet Explorer")
	{
		var selects = $$('select');
		for ( i = 0; i < selects.length; i++)
		{
			selects[i].style.visibility = "visible";
		}
	}
}

function startList() 
{
	if (document.all&&document.getElementById)
	{
		navRoot = document.getElementById("nav");
		for (i=0; i<navRoot.childNodes.length; i++)
		{
			node = navRoot.childNodes[i];
			if (node.nodeName=="LI")
			{
				node.onmouseover=function()
				{
					if (this.className != "nodropdown" && this.className != "seperator")
						hideSelects();
					this.className+=" over";
				}
				node.onmouseout=function()
				{
					this.className=this.className.replace(" over", "");
					if (this.className != "nodropdown" && this.className != "seperator")
						showSelects();
				}
			}
		}
		if ($('map') != null)
		{
			node = $('map');
			node.onmouseover=function()
			{
				this.className+=" over";
				hideSelects();
			}
			node.onmouseout=function()
			{
				this.className=this.className.replace(" over", "");
				showSelects();
			}
		}
		if ($('contact') != null)
		{
			node = $('contact');
			node.onmouseover=function()
			{
				this.className+=" over";
				hideSelects();
			}
			node.onmouseout=function()
			{
				this.className=this.className.replace(" over", "");
				showSelects();
			}
		}
		if ($('sites') != null)
		{
			node = $('sites');
			node.onmouseover=function()
			{
				this.className+=" over";
				hideSelects();
			}
			node.onmouseout=function()
			{
				this.className=this.className.replace(" over", "");
				showSelects();
			}
		}
		if ($('share') != null) 
		{
			node = $('share');
			node.onmouseover = function() {
				this.className += " over";
				hideSelects();
			}
			node.onmouseout = function() {
				this.className = this.className.replace(" over", "");
				showSelects();
			}
		}
	}
	
	var boxes = document.getElementsByClassName("box");
	for (i = 0; i < boxes.length; i++)
	{
		boxes[i].getElementsByTagName("div")[0].style.display = "none";
	}
	
	var formElements = $$("input.mandatory, textarea.mandatory, select.mandatory");
	for (i = 0; i < formElements.length; i++)
	{
		formElements[i].observe('blur', checkValue.bindAsEventListener(this, formElements[i]));
	}
}

function showLocationDetail(id, x, y, url) {
	var flashMap = $(id);
	var locationDetail;
	var offsetLeft;
	var offsetTop = y + 5;

	if (id == "flashmap")
	{
		locationDetail = $('maplocationDetail');
		offsetLeft = x;
	}
	else
	{
		locationDetail = $('locationDetail');
		offsetLeft = x - (locationDetail.getWidth()/2);
		offsetTop = y - locationDetail.getHeight() + 5;
	}

	locationDetail.innerHTML = "<div class='location clearfix'><img src='/images/ajax-loader_tcm101-58013.gif' alt='Charge' /></div>";

	new Ajax.Request("/dc_page.jsp?compid=" + url + "&template=tcm:95-58008-32", {
		method: 'get',
		onSuccess: function(transport) {
			locationDetail.innerHTML = transport.responseText;
			if (id == "flashmap")
			{
				locationDetail = $('maplocationDetail');
				offsetLeft = x;
			}
			else
			{
				locationDetail = $('locationDetail');
				offsetLeft = x - (locationDetail.getWidth()/2);
				offsetTop = y - locationDetail.getHeight() + 5;
			}

			locationDetail.clonePosition(flashMap, {setWidth: false, setHeight: false, offsetLeft: offsetLeft, offsetTop: offsetTop});
		}
	});
	
	locationDetail.clonePosition(flashMap, {setWidth: false, setHeight: false, offsetLeft: offsetLeft, offsetTop: offsetTop});
	locationDetail.show();
	locationDetail.onclick = function(){this.hide();};
}

//window.onload=startList;
Event.observe(window, 'load', startList, false);

/********************************************
Solution Finder
*********************************************/
function getNextAnswers(li, selectedAnswer) {
    question = li.down('.question');
    question.onclick = showAnswers.bind(this, question);
    var forQuestion = li.next();
    if (Object.isElement(forQuestion)) {
        var nextQuestion = forQuestion.down('.question');
        if (Object.isElement(nextQuestion)) {
			var otherQuestion =  forQuestion.next();
			while (Object.isElement(otherQuestion))
			{
				Effect.BlindUp(otherQuestion, {duration: 0.5});
				otherQuestion = otherQuestion.next();
			}
            if (!nextQuestion.up('li').visible()) {
                Effect.BlindDown(nextQuestion.up('li'), {duration: 0.5});
            }
            var answerList = forQuestion.down('.answers');
            if (!Object.isElement(answerList)) {
                answerList = new Element('ul', {className: 'answers'});
                forQuestion.appendChild(answerList);
            }
            answerList.update().hide();
            new Ajax.Request("/solution-finder.jsp" + selectedAnswer, {
                method: 'get',
                contentType: 'text/html',
                onSuccess: function(transport)
				{
					answerList.innerHTML += transport.responseText;
					var answer = answerList.down('li');
					while(Object.isElement(answer))
					{
                        var answerLink = answer.down('a');
                        answerLink.onclick = selectAnswer.bind(this, answerLink);
						answer = answer.next();
					}
                    Effect.BlindDown(answerList, {duration: 0.5, queue: 'end'});
                }
            });
        }
        else {
            var btn = forQuestion.down('.submit');
			btn.href = btn.href.substring(0,btn.href.indexOf('?')) + selectedAnswer + "&showofferings=true";
            Effect.BlindDown(btn.up('li'), {duration: 0.5});
        }
    }
	return false;
}

function selectAnswer(answer) {
    var query = $('finderquery');
	
    var li = answer.up('li').up('li');
    var answers = answer.up('.answers');
    var clone = answers.next('.clone');
    if (!Object.isElement(clone))
	{
        var clone = new Element('ul');
        clone.addClassName('clone');
        clone.addClassName('answers');
        var cloneLi = new Element('li');
		clone.appendChild(cloneLi);
		var cloneLink = new Element('a');
        cloneLink.addClassName('answer');
        cloneLink.addClassName('answerSelected');
        cloneLink.onclick = showAnswers.bind(this, li.down('.question'));
		cloneLi.appendChild(cloneLink);
        li.appendChild(clone);
    }
    clone.down('a').update(answer.innerHTML);
    clone.hide();
    var nextQuestion = li.next();
    while (Object.isElement(nextQuestion))
	{
        var nextClone = nextQuestion.down('.clone');
        if (Object.isElement(nextClone))
		{
            Effect.BlindUp(nextClone, {duration: 0.5});
        }
        var nextAnswers = nextQuestion.down('.answers');
        if (Object.isElement(nextAnswers) && nextAnswers.visible())
		{
            Effect.BlindUp(nextAnswers, {duration: 0.5});
        }
        nextQuestion = nextQuestion.next();
    }
	Effect.BlindUp(answers, {duration: 0.5});
	Effect.BlindDown(clone, {duration: 0.5, queue: 'end', afterFinish: getNextAnswers.bind(this, li, answer.href.substr(answer.href.indexOf('?')))});
	return false;
}

function showAnswers(question) {
    var clone = question.next('.clone');
    if (Object.isElement(clone)) {
        Effect.BlindUp(clone, {duration: 0.5, queue: 'front'});
		clone.up().removeChild(clone);
    }
	var otherQuestion =  question.up('li').next();
	while (Object.isElement(otherQuestion))
	{
		Effect.BlindUp(otherQuestion, {duration: 0.5});
		otherQuestion = otherQuestion.next();
	}
    Effect.BlindDown(question.next('.answers'), {duration: 0.5, queue: 'end'});
    question.onclick = hideAnswers.bind(this, question);
	return false;
}

function hideAnswers(question) {
    var clone = question.next('.clone');
    if (Object.isElement(clone)) {
        Effect.BlindDown(clone, {duration: 0.5, queue: 'end'});
    }
    Effect.BlindUp(question.next('.answers'), {duration: 0.5, queue: 'front'});
    question.onclick = showAnswers.bind(this, question);
	return false;
}

function initSolutionFinder() {
    var question = $('where').down('.question');
    question.onclick = showAnswers.bind(this, question);
    Effect.BlindUp(question.next('.answers'), {duration: 0.5, queue: 'front'});
    question = $('who').down('.question');
    question.onclick = showAnswers.bind(this, question);
    question = $('what').down('.question');
    question.onclick = showAnswers.bind(this, question);
    var finder = $('finder')
    finder.select('li .answer').each(function(answer) {
        answer.onclick = selectAnswer.bind(this, answer);
    });
	return false;
}

parseSelector.pseudoClasses = {
  'not': function(nodes, selector) {
    var result = [];
    each: for(var i = 0, node; i < nodes.length; i++) {
      node = nodes[i];
      var ignore = parseSelector(selector, node.parentNode);
      for(var j = 0; j < ignore.length; j++) {
        if(ignore[j] == node) continue each;
      }
      result.push(node);
    }
    return result;
  }
}


/* *******************************************
// LICENSE INFORMATION
// The code, "Detecting Smartphones Using JavaScript" 
// by Anthony Hand, is licensed under a Creative Commons 
// Attribution 3.0 United States License. 
//
// Anthony Hand, ahand@hand-interactive.com
// Web: www.hand-interactive.com
// 
// License info: http://creativecommons.org/licenses/by/3.0/us/
//
// This code is provided ?AS IS? with no expressed or implied warranty.  
// You have the right to use this code or any portion of it 
// so long as you provide credit toward myself as the original author.
//
// *******************************************
*/

//Initialize some initial string variables we'll look for later.
var deviceIphone = "iphone";
var deviceIpod = "ipod";
var devicePlaystation = "playstation";
var deviceWap = "wap";

var deviceWinMob = "windows ce";
var enginePie = "wm5 pie";
var deviceIeMob = "iemobile";

var deviceS60 = "series60";
var deviceSymbian = "symbian";
var deviceS60 = "series60";
var deviceS70 = "series70";
var deviceS80 = "series80";
var deviceS90 = "series90";

var deviceBB = "blackberry";

var deviceAndroid = "android";

var deviceMidp = "midp";
var deviceWml = "wml";
var deviceBrew = "brew";

var devicePalm = "palm";
var engineXiino = "xiino";
var engineBlazer = "blazer"; //Old Palm

var devicePda = "pda";
var deviceNintendoDs = "nitro";

var engineWebKit = "webkit";
var engineNetfront = "netfront";


var manuSonyEricsson = "sonyericsson";
var manuericsson = "ericsson";
var manuSamsung1 = "sec-sgh";

var svcDocomo = "docomo";
var svcKddi = "kddi";
var svcVodafone = "vodafone";

//Due to the flexibility of the S60 OSSO Browser, 
//   you may wish to let new S60 devices get the regular pages instead.
var s60GetsMobile = true;


//Due to the flexibility of the iPhone/iPod Touch Browser, 
//   you may wish to let new S60 devices get the regular pages instead.
var iphoneIpodGetsMobile = true;


//Initialize our user agent string.
var uagent = navigator.userAgent.toLowerCase();


//**************************
// Detects if the current device is an iPhone.
function DetectIphone()
{
   if (uagent.search(deviceIphone) > -1)
   {
      //The iPod touch says it's an iPhone! So let's disambiguate.
      if (uagent.search(deviceIpod) > -1)
         return false;
      else 
         return true;
   }
   else
      return false;
}

//**************************
// Detects if the current device is an iPod Touch.
function DetectIpod()
{
   if (uagent.search(deviceIpod) > -1)
      return true;
   else
      return false;
}

//**************************
// Detects if the current device is an iPhone or iPod Touch.
function DetectIphoneOrIpod()
{
   //We repeat the searches here because some iPods 
   //  may report themselves as an iPhone, which is ok.
   if (uagent.search(deviceIphone) > -1 ||
       uagent.search(deviceIpod) > -1)
       return true;
    else
       return false;
}

//**************************
// Detects if the current device is an Android OS-based device.
function DetectAndroid()
{
   if (uagent.search(deviceAndroid) > -1)
      return true;
   else
      return false;
}


//**************************
// Detects if the current device is an Android OS-based device and
//   the browser is based on WebKit.
function DetectAndroidWebKit()
{
   if (DetectAndroid())
   {
     if (DetectWebkit())
        return true;
     else
        return false;
   }
   else
      return false;
}

//**************************
// Detects if the current browser is based on WebKit.
function DetectWebkit()
{
   if (uagent.search(engineWebKit) > -1)
      return true;
   else
      return false;
}

//**************************
// Detects if the current browser is the Nokia S60 Open Source Browser.
function DetectS60OssBrowser()
{
   if (DetectWebkit())
   {
     if ((uagent.search(deviceS60) > -1 || 
          uagent.search(deviceSymbian) > -1))
        return true;
     else
        return false;
   }
   else
      return false;
}

//**************************
// Detects if the current device is any Symbian OS-based device,
//   including older S60, Series 70, Series 80, Series 90, and UIQ, 
//   or other browsers running on these devices.
function DetectSymbianOS()
{
   if (uagent.search(deviceSymbian) > -1 ||
       uagent.search(deviceS60) > -1 ||
       uagent.search(deviceS70) > -1 ||
       uagent.search(deviceS80) > -1 ||
       uagent.search(deviceS90) > -1)
      return true;
   else
      return false;
}


//**************************
// Detects if the current browser is a BlackBerry of some sort.
function DetectBlackBerry()
{
   if (uagent.search(deviceBB) > -1)
      return true;
   else
      return false;
}

//**************************
// Detects if the current browser is a Windows Mobile device.
function DetectWindowsMobile()
{
   //Most devices use 'Windows CE', but some report 'iemobile' 
   //  and some older ones report as 'PIE' for Pocket IE. 
   if (uagent.search(deviceWinMob) > -1 ||
       uagent.search(deviceIeMob) > -1 ||
       uagent.search(enginePie) > -1)
      return true;
   else
      return false;
}

//**************************
// Detects if the current browser is on a PalmOS device.
function DetectPalmOS()
{
   //Most devices nowadays report as 'Palm', 
   //  but some older ones reported as Blazer or Xiino.
   if (uagent.search(devicePalm) > -1 ||
       uagent.search(engineBlazer) > -1 ||
       uagent.search(engineXiino) > -1)
      return true;
   else
      return false;
}

//**************************
// Sets whether S60 devices running the 
//   Open Source Browser (based on WebKit)
//   should be detected as 'mobile' or not.
//   Set TRUE to be detected as mobile.
//   Set FALSE and it will not be detected as mobile.
function SetS60GetsMobile(setMobile)
{
   s60GetsMobile = setMobile;
};

//**************************
// Sets whether iPhone/iPod Touch devices running the 
//   Open Source Browser (based on WebKit)
//   should be detected as 'mobile' or not.
//   Set TRUE to be detected as mobile.
//   Set FALSE and it will not be detected as mobile.
function SetS60GetsMobile(setMobile)
{
   iphoneIpodGetsMobile = setMobile;
};


//**************************
// Check to see whether the device is a 'smartphone'.
//   You might wish to send smartphones to a more capable web page
//   than a dumbed down WAP page. 
function DetectSmartphone()
{
   //First, look for iPhone and iPod Touch.
   if (DetectIphoneOrIpod())
      return true;

   //Now, look for S60 Open Source Browser on S60 release 3 devices.
   if (DetectS60OssBrowser())
      return true;

   //Check for other Symbian devices - older S60, UIQ, other.
   if (DetectSymbianOS())
      return true;

   //Check for Windows Mobile devices.
   if (DetectWindowsMobile())
      return true;

   //Next, look for a BlackBerry
   if (DetectBlackBerry())
      return true;

   //PalmOS.
   if (DetectPalmOS())
      return true;

   //Otherwise, return false.
   return false;
};


//**************************
// Detects if the current device is a mobile device.
//  This method catches most of the popular modern devices.
function DetectMobileQuick()
{
   //Attempt to detect most mobile devices, 
   //   especially mass market feature phones.
   // NOTE: Doesn't usually work reliably...
   if (uagent.search(deviceWap) > -1   || 
	uagent.search(deviceMidp) > -1 ||
	uagent.search(deviceWml) > -1  ||
	uagent.search(deviceBrew) > -1  )
   {
      return true;
   }

   //Detect for most smartphones.
   if (DetectSmartphone())
      return true;

   //Check for a NetFront browser
   if (uagent.search(engineNetfront) > -1)
      return true;

   //Check for a Playstation
   if (uagent.search(devicePlaystation) > -1)
      return true;

   //Check for a generic PDA
   if (uagent.search(devicePda) > -1)
      return true;

   return false;
};


//**************************
// Detects in a more comprehensive way if the current device is a mobile device.
function DetectMobileLonger()
{
   //Run the quick check first.
   if (DetectMobileQuick())
      return true;

   //Check for NTT Docomo
   if (uagent.search(svcDocomo) > -1)
      return true;

   //Check for KDDI
   if (uagent.search(svcKddi) > -1)
      return true;

   //Check for Nintendo DS
   if (uagent.search(deviceNintendoDs) > -1)
      return true;

   //Check for Vodafone 3G
   if (uagent.search(svcVodafone) > -1)
      return true;

   //Finally, detect for certain very old devices with stupid useragent strings.
   if (uagent.search(manuSamsung1) > -1 ||
	uagent.search(manuSonyEricsson) > -1 || 
	uagent.search(manuericsson) > -1)
   {
      return true;
   }

   return false;
};

var vista = { src: '/images/vista-slab-ot-medium_tcm101-57745.swf' };

sIFR.useStyleCheck = true;
sIFR.forceTextTransform = true;
sIFR.fitExactly = true;
sIFR.repaintOnResize = true;
sIFR.forceClear = true;

var BrowserDetect = {
	init: function() {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent) ||
		this.searchVersion(navigator.appVersion) ||
		"an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function(data) {
		for (var i = 0; i < data.length; i++) 
		{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) 
			{
				if (dataString.indexOf(data[i].subString) != -1) 
					return data[i].identity;
			}
			else 
				if (dataProp) 
					return data[i].identity;
		}
	},
	searchVersion: function(dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) 
			return;
		return parseFloat(dataString.substring(index + this.versionSearchString.length + 1));
	},
	dataBrowser: [{
		string: navigator.userAgent,
		subString: "Chrome",
		identity: "Chrome"
	}, {
		string: navigator.userAgent,
		subString: "OmniWeb",
		versionSearch: "OmniWeb/",
		identity: "OmniWeb"
	}, {
		string: navigator.vendor,
		subString: "Apple",
		identity: "Safari",
		versionSearch: "Version"
	}, {
		prop: window.opera,
		identity: "Opera"
	}, {
		string: navigator.vendor,
		subString: "iCab",
		identity: "iCab"
	}, {
		string: navigator.vendor,
		subString: "KDE",
		identity: "Konqueror"
	}, {
		string: navigator.userAgent,
		subString: "Firefox",
		identity: "Firefox"
	}, {
		string: navigator.vendor,
		subString: "Camino",
		identity: "Camino"
	}, { // for newer Netscapes (6+)
		string: navigator.userAgent,
		subString: "Netscape",
		identity: "Netscape"
	}, {
		string: navigator.userAgent,
		subString: "MSIE",
		identity: "Explorer",
		versionSearch: "MSIE"
	}, {
		string: navigator.userAgent,
		subString: "Gecko",
		identity: "Mozilla",
		versionSearch: "rv"
	}, { // for older Netscapes (4-)
		string: navigator.userAgent,
		subString: "Mozilla",
		identity: "Netscape",
		versionSearch: "Mozilla"
	}],
	dataOS: [{
		string: navigator.platform,
		subString: "Win",
		identity: "Windows"
	}, {
		string: navigator.platform,
		subString: "Mac",
		identity: "Mac"
	}, {
		string: navigator.userAgent,
		subString: "iPhone",
		identity: "iPhone/iPod"
	}, {
		string: navigator.platform,
		subString: "Linux",
		identity: "Linux"
	}]

};
BrowserDetect.init();

var activatesIFR = true;
//alert("Browser: " + BrowserDetect.browser + " Version: " + BrowserDetect.version);
switch (BrowserDetect.browser)
{
	case "Explorer":
		if (BrowserDetect.version >= 5) 
			activatesIFR = false
		break;
	case "Safari":
		if (BrowserDetect.version >= 3.1) 
			activatesIFR = false
		break;
	case "Opera":
		if (BrowserDetect.version >= 10) 
			activatesIFR = false
		break;
	case "Chrome":
		if (BrowserDetect.version >= 2.0) 
			activatesIFR = false
		break;
	case "Firefox":
		if (BrowserDetect.version >= 3.5) 
			activatesIFR = false
		break;
	default:
		activatesIFR = true;
		break;
}

if (activatesIFR == true && DetectMobileLonger() == false) 
{
	sIFR.activate(vista);
	
	sIFR.replace(vista, {
	  selector: '.header .label',
	  css: '.sIFR-root{font-weight: bold;}.first{font-weight: bold;color: #FA6200;}.second{font-weight: bold;color: #FF9400;}.third{font-weight: bold;color: #FFC100;}.fourth{font-weight: bold;color: #0099CC;}',
	  wmode: 'transparent'
	});

	sIFR.replace(vista, {
	  selector: '#content .map h1',
	  css: '.sIFR-root{font-weight: bold;color: #052F43;} a{color: #0077ad; text-decoration:none} a:hover{color: #FF9400;}',
	  wmode: 'transparent',
	  tuneWidth: -500
	});

	sIFR.replace(vista, {
	  selector: '#content h1, #content h2, #navigation h1, #sidebar h1, .preheader h1',
	  css: '.sIFR-root{font-weight: bold;color: #052F43;} a{font-weight: bold;color: #0077ad; text-decoration:none} a:hover{font-weight: bold;color: #FF9400;}',
	  wmode: 'transparent'
	});
	
}


function CreateBookmarkLink(url, title)
{
	if (window.sidebar)
	{ // Mozilla Firefox Bookmark
		window.sidebar.addPanel(title, url,"");
	}
	else if( window.external )
	{ // IE Favorite
		window.external.AddFavorite( url, title);
	}
	else if(window.opera && window.print)
	{ // Opera Hotlist
		return true;
	}
}

function checkFormField(field)
{
	new Form.Element.DelayedObserver(field, 0.5, function(){
		var label = field.up('p').previous('label');
		if ($(field).getValue() == "")
			label.addClassName("mandatory");
		else
			label.removeClassName("mandatory");
	});
	checkValue(field);
}

function checkValue(e)
{
	var field = Event.element(e);
	var label = $(field).up('p').previous('label');
	if ($(field).getValue() == "")
		label.addClassName("mandatory");
	else
		label.removeClassName("mandatory");
}

/*  Prototype-UI, version trunk
 *
 *  Prototype-UI is freely distributable under the terms of an MIT-style license.
 *  For details, see the PrototypeUI web site: http://www.prototype-ui.com/
 *
 *--------------------------------------------------------------------------*/

if(typeof Prototype == 'undefined' || !Prototype.Version.match("1.6"))
  throw("Prototype-UI library require Prototype library >= 1.6.0");

if (Prototype.Browser.WebKit) {
  Prototype.Browser.WebKitVersion = parseFloat(navigator.userAgent.match(/AppleWebKit\/([\d\.\+]*)/)[1]);
  Prototype.Browser.Safari2 = (Prototype.Browser.WebKitVersion < 420);
}

if (Prototype.Browser.IE) {
  Prototype.Browser.IEVersion = parseFloat(navigator.appVersion.split(';')[1].strip().split(' ')[1]);
  Prototype.Browser.IE6 =  Prototype.Browser.IEVersion == 6;
  Prototype.Browser.IE7 =  Prototype.Browser.IEVersion == 7;
}

Prototype.falseFunction = function() { return false };
Prototype.trueFunction  = function() { return true  };

/*
Namespace: UI

  Introduction:
    Prototype-UI is a library of user interface components based on the Prototype framework.
    Its aim is to easilly improve user experience in web applications.

    It also provides utilities to help developers.

  Guideline:
    - Prototype conventions are followed
    - Everything should be unobstrusive
    - All components are themable with CSS stylesheets, various themes are provided

  Warning:
    Prototype-UI is still under deep development, this release is targeted to developers only.
    All interfaces are subjects to changes, suggestions are welcome.

    DO NOT use it in production for now.

  Authors:
    - Sébastien Gruhier, <http://www.xilinus.com>
    - Samuel Lebeau, <http://gotfresh.info>
*/

var UI = {
  Abstract: { },
  Ajax: { }
};
Object.extend(Class.Methods, {
  extend: Object.extend.methodize(),

  addMethods: Class.Methods.addMethods.wrap(function(proceed, source) {
    // ensure we are not trying to add null or undefined
    if (!source) return this;

    // no callback, vanilla way
    if (!source.hasOwnProperty('methodsAdded'))
      return proceed(source);

    var callback = source.methodsAdded;
    delete source.methodsAdded;
    proceed(source);
    callback.call(source, this);
    source.methodsAdded = callback;

    return this;
  }),

  addMethod: function(name, lambda) {
    var methods = {};
    methods[name] = lambda;
    return this.addMethods(methods);
  },

  method: function(name) {
    return this.prototype[name].valueOf();
  },

  classMethod: function() {
    $A(arguments).flatten().each(function(method) {
      this[method] = (function() {
        return this[method].apply(this, arguments);
      }).bind(this.prototype);
    }, this);
    return this;
  },

  // prevent any call to this method
  undefMethod: function(name) {
    this.prototype[name] = undefined;
    return this;
  },

  // remove the class' own implementation of this method
  removeMethod: function(name) {
    delete this.prototype[name];
    return this;
  },

  aliasMethod: function(newName, name) {
    this.prototype[newName] = this.prototype[name];
    return this;
  },

  aliasMethodChain: function(target, feature) {
    feature = feature.camelcase();

    this.aliasMethod(target+"Without"+feature, target);
    this.aliasMethod(target, target+"With"+feature);

    return this;
  }
});
Object.extend(Number.prototype, {
  // Snap a number to a grid
  snap: function(round) {
    return parseInt(round == 1 ? this : (this / round).floor() * round);
  }
});
/*
Interface: String

*/

Object.extend(String.prototype, {
  camelcase: function() {
    var string = this.dasherize().camelize();
    return string.charAt(0).toUpperCase() + string.slice(1);
  },

  /*
    Method: makeElement
      toElement is unfortunately already taken :/

      Transforms html string into an extended element or null (when failed)

      > '<li><a href="#">some text</a></li>'.makeElement(); // => LI href#
      > '<img src="foo" id="bar" /><img src="bar" id="bar" />'.makeElement(); // => IMG#foo (first one)

    Returns:
      Extended element

  */
  makeElement: function() {
    var wrapper = new Element('div'); wrapper.innerHTML = this;
    return wrapper.down();
  }
});
Object.extend(Array.prototype, {
  empty: function() {
    return !this.length;
  },

  extractOptions: function() {
    return this.last().constructor === Object ? this.pop() : { };
  },

  removeAt: function(index) {
    var object = this[index];
    this.splice(index, 1);
    return object;
  },

  remove: function(object) {
    var index;
    while ((index = this.indexOf(object)) != -1)
      this.removeAt(index);
    return object;
  },

  insert: function(index) {
    var args = $A(arguments);
    args.shift();
    this.splice.apply(this, [ index, 0 ].concat(args));
    return this;
  }
});
Element.addMethods({
  getScrollDimensions: function(element) {
    return {
      width:  element.scrollWidth,
      height: element.scrollHeight
    }
  },

  getScrollOffset: function(element) {
    return Element._returnOffset(element.scrollLeft, element.scrollTop);
  },

  setScrollOffset: function(element, offset) {
    element = $(element);
    if (arguments.length == 3)
      offset = { left: offset, top: arguments[2] };
    element.scrollLeft = offset.left;
    element.scrollTop  = offset.top;
    return element;
  },

  // returns "clean" numerical style (without "px") or null if style can not be resolved
  // or is not numeric
  getNumStyle: function(element, style) {
    var value = parseFloat($(element).getStyle(style));
    return isNaN(value) ? null : value;
  },

  // by Tobie Langel (http://tobielangel.com/2007/5/22/prototype-quick-tip)
  appendText: function(element, text) {
    element = $(element);
    text = String.interpret(text);
    element.appendChild(document.createTextNode(text));
    return element;
  }
});

document.whenReady = function(callback) {
  if (document.loaded)
    callback.call(document);
  else
    document.observe('dom:loaded', callback);
};

Object.extend(document.viewport, {
  // Alias this method for consistency
  getScrollOffset: document.viewport.getScrollOffsets,

  setScrollOffset: function(offset) {
    Element.setScrollOffset(Prototype.Browser.WebKit ? document.body : document.documentElement, offset);
  },

  getScrollDimensions: function() {
    return Element.getScrollDimensions(Prototype.Browser.WebKit ? document.body : document.documentElement);
  }
});
/*
Interface: UI.Options
  Mixin to handle *options* argument in initializer pattern.

  TODO: find a better example than Circle that use an imaginary Point function,
        this example should be used in tests too.

  It assumes class defines a property called *options*, containing
  default options values.

  Instances hold their own *options* property after a first call to <setOptions>.

  Example:
    > var Circle = Class.create(UI.Options, {
    >
    >   // default options
    >   options: {
    >     radius: 1,
    >     origin: Point(0, 0)
    >   },
    >
    >   // common usage is to call setOptions in initializer
    >   initialize: function(options) {
    >     this.setOptions(options);
    >   }
    > });
    >
    > var circle = new Circle({ origin: Point(1, 4) });
    >
    > circle.options
    > // => { radius: 1, origin: Point(1,4) }

  Accessors:
    There are builtin methods to automatically write options accessors. All those
    methods can take either an array of option names nor option names as arguments.
    Notice that those methods won't override an accessor method if already present.

     * <optionsGetter> creates getters
     * <optionsSetter> creates setters
     * <optionsAccessor> creates both getters and setters

    Common usage is to invoke them on a class to create accessors for all instances
    of this class.
    Invoking those methods on a class has the same effect as invoking them on the class prototype.
    See <classMethod> for more details.

    Example:
    > // Creates getter and setter for the "radius" options of circles
    > Circle.optionsAccessor('radius');
    >
    > circle.setRadius(4);
    > // 4
    >
    > circle.getRadius();
    > // => 4 (circle.options.radius)

  Inheritance support:
    Subclasses can refine default *options* values, after a first instance call on setOptions,
    *options* attribute will hold all default options values coming from the inheritance hierarchy.
*/

(function() {
  UI.Options = {
    methodsAdded: function(klass) {
      klass.classMethod($w(' setOptions allOptions optionsGetter optionsSetter optionsAccessor '));
    },

    // Group: Methods

    /*
      Method: setOptions
        Extends object's *options* property with the given object
    */
    setOptions: function(options) {
      if (!this.hasOwnProperty('options'))
        this.options = this.allOptions();

      this.options = Object.extend(this.options, options || {});
    },

    /*
      Method: allOptions
        Computes the complete default options hash made by reverse extending all superclasses
        default options.

        > Widget.prototype.allOptions();
    */
    allOptions: function() {
      var superclass = this.constructor.superclass, ancestor = superclass && superclass.prototype;
      return (ancestor && ancestor.allOptions) ?
          Object.extend(ancestor.allOptions(), this.options) :
          Object.clone(this.options);
    },

    /*
      Method: optionsGetter
        Creates default getters for option names given as arguments.
        With no argument, creates getters for all option names.
    */
    optionsGetter: function() {
      addOptionsAccessors(this, arguments, false);
    },

    /*
      Method: optionsSetter
        Creates default setters for option names given as arguments.
        With no argument, creates setters for all option names.
    */
    optionsSetter: function() {
      addOptionsAccessors(this, arguments, true);
    },

    /*
      Method: optionsAccessor
        Creates default getters/setters for option names given as arguments.
        With no argument, creates accessors for all option names.
    */
    optionsAccessor: function() {
      this.optionsGetter.apply(this, arguments);
      this.optionsSetter.apply(this, arguments);
    }
  };

  // Internal
  function addOptionsAccessors(receiver, names, areSetters) {
    names = $A(names).flatten();

    if (names.empty())
      names = Object.keys(receiver.allOptions());

    names.each(function(name) {
      var accessorName = (areSetters ? 'set' : 'get') + name.camelcase();

      receiver[accessorName] = receiver[accessorName] || (areSetters ?
        // Setter
        function(value) { return this.options[name] = value } :
        // Getter
        function()      { return this.options[name]         });
    });
  }
})();
/*
  Class: UI.Carousel

  Main class to handle a carousel of elements in a page. A carousel :
    * could be vertical or horizontal
    * works with liquid layout
    * is designed by CSS

  Assumptions:
    * Elements should be from the same size

  Example:
    > ...
    > <div id="horizontal_carousel">
    >   <div class="previous_button"></div>
    >   <div class="container">
    >     <ul>
    >       <li> What ever you like</li>
    >     </ul>
    >   </div>
    >   <div class="next_button"></div>
    > </div>
    > <script>
    > new UI.Carousel("horizontal_carousel");
    > </script>
    > ...
*/
UI.Carousel = Class.create(UI.Options, {
  // Group: Options
  options: {
	// Property: direction
	//   Can be horizontal or vertical, horizontal by default
    direction               : "horizontal",

    // Property: previousButton
    //   Selector of previous button inside carousel element, ".previous_button" by default,
    //   set it to false to ignore previous button
    previousButton          : ".previous_button",

    // Property: nextButton
    //   Selector of next button inside carousel element, ".next_button" by default,
    //   set it to false to ignore next button
    nextButton              : ".next_button",

    // Property: container
    //   Selector of carousel container inside carousel element, ".container" by default,
    container               : ".container",

    // Property: scrollInc
    //   Define the maximum number of elements that gonna scroll each time, auto by default
    scrollInc               : "auto",

    // Property: disabledButtonSuffix
    //   Define the suffix classanme used when a button get disabled, to '_disabled' by default
    //   Previous button classname will be previous_button_disabled
    disabledButtonSuffix : '_disabled',

    // Property: overButtonSuffix
    //   Define the suffix classanme used when a button has a rollover status, '_over' by default
    //   Previous button classname will be previous_button_over
    overButtonSuffix : '_over'
  },

  /*
    Group: Attributes

      Property: element
        DOM element containing the carousel

      Property: id
        DOM id of the carousel's element

      Property: container
        DOM element containing the carousel's elements

      Property: elements
        Array containing the carousel's elements as DOM elements

      Property: previousButton
        DOM id of the previous button

      Property: nextButton
        DOM id of the next button

      Property: posAttribute
        Define if the positions are from left or top

      Property: dimAttribute
        Define if the dimensions are horizontal or vertical

      Property: elementSize
        Size of each element, it's an integer

      Property: nbVisible
        Number of visible elements, it's a float

      Property: animating
        Define whether the carousel is in animation or not
  */

  /*
    Group: Events
      List of events fired by a carousel

      Notice: Carousel custom events are automatically namespaced in "carousel:" (see Prototype custom events).

      Examples:
        This example will observe all carousels
        > document.observe('carousel:scroll:ended', function(event) {
        >   alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled");
        > });

        This example will observe only this carousel
        > new UI.Carousel('horizontal_carousel').observe('scroll:ended', function(event) {
        >   alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled");
        > });

      Property: previousButton:enabled
        Fired when the previous button has just been enabled

      Property: previousButton:disabled
        Fired when the previous button has just been disabled

      Property: nextButton:enabled
        Fired when the next button has just been enabled

      Property: nextButton:disabled
        Fired when the next button has just been disabled

      Property: scroll:started
        Fired when a scroll has just started

      Property: scroll:ended
        Fired when a scroll has been done,
        memo.shift = number of elements scrolled, it's a float

      Property: sizeUpdated
        Fired when the carousel size has just been updated.
        Tips: memo.carousel.currentSize() = the new carousel size
  */

  // Group: Constructor

  /*
    Method: initialize
      Constructor function, should not be called directly

    Parameters:
      element - DOM element
      options - (Hash) list of optional parameters

    Returns:
      this
  */
  initialize: function(element, options) {
    this.setOptions(options);
    this.element = $(element);
    this.id = this.element.id;
    this.container   = this.element.down(this.options.container).down('ul');
    this.elements    = this.container.childElements();
    this.previousButton = this.options.previousButton == false ? null : this.element.down(this.options.container).down(this.options.previousButton);
    this.nextButton = this.options.nextButton == false ? null : this.element.down(this.options.container).down(this.options.nextButton);

    var containerElm   = this.element.down(this.options.container);
	this.element.observe("mouseover", function() {this.element.addClassName('carousel_hover')}.bind(this));
	this.element.observe("mouseout",  function() {this.element.removeClassName('carousel_hover')}.bind(this));
	
	
    this.posAttribute = (this.options.direction == "horizontal" ? "left" : "top");
    this.dimAttribute = (this.options.direction == "horizontal" ? "width" : "height");

    this.elementSize = this.computeElementSize();
    this.nbVisible = this.currentSize() / this.elementSize;

    var scrollInc = this.options.scrollInc;
    if (scrollInc == "auto")
      scrollInc = Math.floor(this.nbVisible);
    [ this.previousButton, this.nextButton ].each(function(button) {
      if (!button) return;
      var className = (button == this.nextButton ? "next_button" : "previous_button") + this.options.overButtonSuffix;
      button.clickHandler = this.scroll.bind(this, (button == this.nextButton ? -1 : 1) * scrollInc * this.elementSize);
      button.observe("click", button.clickHandler)
            .observe("mouseover", function() {button.addClassName(className)}.bind(this))
            .observe("mouseout",  function() {button.removeClassName(className)}.bind(this));
    }, this);
    this.updateButtons();
  },

  // Group: Destructor

  /*
    Method: destroy
      Cleans up DOM and memory
  */
  destroy: function($super) {
    [ this.previousButton, this.nextButton ].each(function(button) {
      if (!button) return;
        button.stopObserving("click", button.clickHandler);
    }, this);
	  this.element.remove();
	  this.fire('destroyed');
  },

  // Group: Event handling

  /*
    Method: fire
      Fires a carousel custom event automatically namespaced in "carousel:" (see Prototype custom events).
      The memo object contains a "carousel" property referring to the carousel.

    Example:
      > document.observe('carousel:scroll:ended', function(event) {
      >   alert("Carousel with id " + event.memo.carousel.id + " has just been scrolled");
      > });

    Parameters:
      eventName - an event name
      memo      - a memo object

    Returns:
      fired event
  */
  fire: function(eventName, memo) {
    memo = memo || { };
    memo.carousel = this;
    return this.element.fire('carousel:' + eventName, memo);
  },

  /*
    Method: observe
      Observe a carousel event with a handler function automatically bound to the carousel

    Parameters:
      eventName - an event name
      handler   - a handler function

    Returns:
      this
  */
  observe: function(eventName, handler) {
    this.element.observe('carousel:' + eventName, handler.bind(this));
    return this;
  },

  /*
    Method: stopObserving
      Unregisters a carousel event, it must take the same parameters as this.observe (see Prototype stopObserving).

    Parameters:
      eventName - an event name
      handler   - a handler function

    Returns:
      this
  */
  stopObserving: function(eventName, handler) {
	  this.element.stopObserving('carousel:' + eventName, handler);
	  return this;
  },

  // Group: Actions

  /*
    Method: checkScroll
      Check scroll position to avoid unused space at right or bottom

    Parameters:
      position       - position to check
      updatePosition - should the container position be updated ? true/false

    Returns:
      position
  */
  checkScroll: function(position, updatePosition) {
    if (position > 0)
      position = 0;
    else {
      var limit = this.elements.last().positionedOffset()[this.posAttribute] + this.elementSize;
      var carouselSize = this.currentSize();

      if (position + limit < carouselSize)
        position += carouselSize - (position + limit);
      position = Math.min(position, 0);
    }
    if (updatePosition)
      this.container.style[this.posAttribute] = position + "px";

    return position;
  },

  /*
    Method: scroll
      Scrolls carousel from maximum deltaPixel

    Parameters:
      deltaPixel - a float

    Returns:
      this
  */
  scroll: function(deltaPixel) {
    if (this.animating)
      return this;

    // Compute new position
    var position =  this.currentPosition() + deltaPixel;

    // Check bounds
    position = this.checkScroll(position, false);

    // Compute shift to apply
    deltaPixel = position - this.currentPosition();
    if (deltaPixel != 0) {
      this.animating = true;
      this.fire("scroll:started");

      var that = this;
      // Move effects
      this.container.morph("opacity:0.5", {duration: 0.2, afterFinish: function() {
        that.container.morph(that.posAttribute + ": " + position + "px", {
          duration: 0.4,
          delay: 0.2,
          afterFinish: function() {
            that.container.morph("opacity:1", {
              duration: 0.2,
              afterFinish: function() {
                that.animating = false;
                that.updateButtons()
                  .fire("scroll:ended", { shift: deltaPixel / that.currentSize() });
              }
            });
          }
        });
      }});
    }
    return this;
  },

  /*
    Method: scrollTo
      Scrolls carousel, so that element with specified index is the left-most.
      This method is convenient when using carousel in a tabbed navigation.
      Clicking on first tab should scroll first container into view, clicking on a fifth - fifth one, etc.
      Indexing starts with 0.

    Parameters:
      Index of an element which will be a left-most visible in the carousel

    Returns:
      this
  */
  scrollTo: function(index) {
    if (this.animating || index < 0 || index > this.elements.length || index == this.currentIndex() || isNaN(parseInt(index)))
      return this;
    return this.scroll((this.currentIndex() - index) * this.elementSize);
  },

  /*
    Method: updateButtons
      Update buttons status to enabled or disabled
      Them status is defined by classNames and fired as carousel's custom events

    Returns:
      this
  */
  updateButtons: function() {
	  this.updatePreviousButton();
    this.updateNextButton();
    return this;
  },

  updatePreviousButton: function() {
    var position = this.currentPosition();
    var previousClassName = "previous_button" + this.options.disabledButtonSuffix;
    var scrollInc = this.options.scrollInc;
    if (scrollInc == "auto")
		scrollInc = Math.floor(this.nbVisible);

    if (this.previousButton.hasClassName(previousClassName) && position != 0) {
      this.previousButton.removeClassName(previousClassName);
      this.fire('previousButton:enabled');
    }
    if (!this.previousButton.hasClassName(previousClassName) && position == 0)
	{
		/*this.previousButton.addClassName(previousClassName);
		this.fire('previousButton:disabled');*/
		this.previousButton.clickHandler = this.scrollTo.bind(this, this.elements.length - 1);
		this.previousButton.observe("click", this.previousButton.clickHandler);
    }
	else
	{
		this.previousButton.stopObserving("click", this.previousButton.clickHandler);
		this.previousButton.clickHandler = this.scroll.bind(this, 1 * scrollInc * this.elementSize);
		this.previousButton.observe("click", this.previousButton.clickHandler);
	}
  },

  updateNextButton: function() {
    var lastPosition = this.currentLastPosition();
    var size = this.currentSize();
    var nextClassName = "next_button" + this.options.disabledButtonSuffix;
    var scrollInc = this.options.scrollInc;
    if (scrollInc == "auto")
		scrollInc = Math.floor(this.nbVisible);

    if (this.nextButton.hasClassName(nextClassName) && lastPosition != size) {
      this.nextButton.removeClassName(nextClassName);
      this.fire('nextButton:enabled');
    }
    if (!this.nextButton.hasClassName(nextClassName) && lastPosition == size)
	{
		/*this.nextButton.addClassName(nextClassName);
		this.fire('nextButton:disabled');*/
		this.nextButton.clickHandler = this.scrollTo.bind(this, 0);
		this.nextButton.observe("click", this.nextButton.clickHandler);
    }
  	else
	{
		this.nextButton.stopObserving("click", this.nextButton.clickHandler);
		this.nextButton.clickHandler = this.scroll.bind(this, -1 * scrollInc * this.elementSize);
		this.nextButton.observe("click", this.nextButton.clickHandler);
	}
},

  // Group: Size and Position

  /*
    Method: computeElementSize
      Return elements size in pixel, height or width depends on carousel orientation.

    Returns:
      an integer value
  */
  computeElementSize: function() {
    return this.elements.first().getDimensions()[this.dimAttribute];
  },

  /*
    Method: currentIndex
      Returns current visible index of a carousel.
      For example, a horizontal carousel with image #3 on left will return 3 and with half of image #3 will return 3.5
      Don't forget that the first image have an index 0

    Returns:
      a float value
  */
  currentIndex: function() {
    return - this.currentPosition() / this.elementSize;
  },

  /*
    Method: currentLastPosition
      Returns the current position from the end of the last element. This value is in pixel.

    Returns:
      an integer value, if no images a present it will return 0
  */
  currentLastPosition: function() {
    if (this.container.childElements().empty())
      return 0;
    return this.currentPosition() +
           this.elements.last().positionedOffset()[this.posAttribute] +
           this.elementSize;
  },

  /*
    Method: currentPosition
      Returns the current position in pixel.
      Tips: To get the position in elements use currentIndex()

    Returns:
      an integer value
  */
  currentPosition: function() {
    return this.container.getNumStyle(this.posAttribute);
  },

  /*
    Method: currentSize
      Returns the current size of the carousel in pixel

    Returns:
      Carousel's size in pixel
  */
  currentSize: function() {
    return this.container.parentNode.getDimensions()[this.dimAttribute];
  },

  /*
    Method: updateSize
      Should be called if carousel size has been changed (usually called with a liquid layout)

    Returns:
      this
  */
  updateSize: function() {
    this.nbVisible = this.currentSize() / this.elementSize;
    var scrollInc = this.options.scrollInc;
    if (scrollInc == "auto")
      scrollInc = Math.floor(this.nbVisible);

    [ this.previousButton, this.nextButton ].each(function(button) {
      if (!button) return;
      button.stopObserving("click", button.clickHandler);
      button.clickHandler = this.scroll.bind(this, (button == this.nextButton ? -1 : 1) * scrollInc * this.elementSize);
      button.observe("click", button.clickHandler);
    }, this);

    this.checkScroll(this.currentPosition(), true);
    this.updateButtons().fire('sizeUpdated');
    return this;
  }
});
/*
  Class: UI.Ajax.Carousel

  Gives the AJAX power to carousels. An AJAX carousel :
    * Use AJAX to add new elements on the fly

  Example:
    > new UI.Ajax.Carousel("horizontal_carousel",
    >   {url: "get-more-elements", elementSize: 250});
*/
UI.Ajax.Carousel = Class.create(UI.Carousel, {
  // Group: Options
  //
  //   Notice:
  //     It also include of all carousel's options
  options: {
	// Property: elementSize
	//   Required, it define the size of all elements
    elementSize : -1,

	// Property: url
	//   Required, it define the URL used by AJAX carousel to request new elements details
    url         : null
  },

  /*
    Group: Attributes

      Notice:
        It also include of all carousel's attributes

      Property: elementSize
        Size of each elements, it's an integer

      Property: endIndex
        Index of the last loaded element

      Property: hasMore
        Flag to define if there's still more elements to load

      Property: requestRunning
        Define whether a request is processing or not

      Property: updateHandler
        Callback to update carousel, usually used after request success

      Property: url
        URL used to request additional elements
  */

  /*
    Group: Events
      List of events fired by an AJAX carousel, it also include of all carousel's custom events

      Property: request:started
        Fired when the request has just started

      Property: request:ended
        Fired when the request has succeed
  */

  // Group: Constructor

  /*
    Method: initialize
      Constructor function, should not be called directly

    Parameters:
      element - DOM element
      options - (Hash) list of optional parameters

    Returns:
      this
  */
  initialize: function($super, element, options) {
    if (!options.url)
      throw("url option is required for UI.Ajax.Carousel");
    if (!options.elementSize)
      throw("elementSize option is required for UI.Ajax.Carousel");

    $super(element, options);

    this.endIndex = 0;
    this.hasMore  = true;

    // Cache handlers
    this.updateHandler = this.update.bind(this);
    this.updateAndScrollHandler = function(nbElements, transport, json) {
	    this.update(transport, json);
	    this.scroll(nbElements);
	  }.bind(this);

    // Run first ajax request to fill the carousel
    this.runRequest.bind(this).defer({parameters: {from: 0, to: Math.ceil(this.nbVisible) - 1}, onSuccess: this.updateHandler});
  },

  // Group: Actions

  /*
    Method: runRequest
      Request the new elements details

    Parameters:
      options - (Hash) list of optional parameters

    Returns:
      this
  */
  runRequest: function(options) {
    this.requestRunning = true;
    new Ajax.Request(this.options.url, Object.extend({method: "GET"}, options));
    this.fire("request:started");
    return this;
  },

  /*
    Method: scroll
      Scrolls carousel from maximum deltaPixel

    Parameters:
      deltaPixel - a float

    Returns:
      this
  */
  scroll: function($super, deltaPixel) {
    if (this.animating || this.requestRunning)
      return this;

    var nbElements = (-deltaPixel) / this.elementSize;
    // Check if there is not enough
    if (this.hasMore && nbElements > 0 && this.currentIndex() + this.nbVisible + nbElements - 1 > this.endIndex) {
      var from = this.endIndex + 1;
      var to   = Math.ceil(from + this.nbVisible - 1);
      this.runRequest({parameters: {from: from, to: to}, onSuccess: this.updateAndScrollHandler.curry(deltaPixel).bind(this)});
      return this;
    }
    else
      $super(deltaPixel);
  },

  /*
    Method: update
      Update the carousel

    Parameters:
      transport - XMLHttpRequest object
      json      - JSON object

    Returns:
      this
  */
  update: function(transport, json) {
    this.requestRunning = false;
    this.fire("request:ended");
    if (!json)
      json = transport.responseJSON;
    this.hasMore = json.more;

    this.endIndex = Math.max(this.endIndex, json.to);
    this.elements = this.container.insert({bottom: json.html}).childElements();
    return this.updateButtons();
  },

  // Group: Size and Position

  /*
    Method: computeElementSize
      Return elements size in pixel

    Returns:
      an integer value
  */
  computeElementSize: function() {
    return this.options.elementSize;
  },

  /*
    Method: updateSize
      Should be called if carousel size has been changed (usually called with a liquid layout)

    Returns:
      this
  */
  updateSize: function($super) {
    var nbVisible = this.nbVisible;
    $super();
    // If we have enough space for at least a new element
    if (Math.floor(this.nbVisible) - Math.floor(nbVisible) >= 1 && this.hasMore) {
      if (this.currentIndex() + Math.floor(this.nbVisible) >= this.endIndex) {
        var nbNew = Math.floor(this.currentIndex() + Math.floor(this.nbVisible) - this.endIndex);
        this.runRequest({parameters: {from: this.endIndex + 1, to: this.endIndex + nbNew}, onSuccess: this.updateHandler});
      }
    }
    return this;
  },

  updateNextButton: function($super) {
    var lastPosition = this.currentLastPosition();
    var size = this.currentSize();
    var nextClassName = "next_button" + this.options.disabledButtonSuffix;

    if (this.nextButton.hasClassName(nextClassName) && lastPosition != size) {
      this.nextButton.removeClassName(nextClassName);
      this.fire('nextButton:enabled');
    }
    if (!this.nextButton.hasClassName(nextClassName) && lastPosition == size && !this.hasMore) {
	    this.nextButton.addClassName(nextClassName);
      this.fire('nextButton:disabled');
    }
  }
});

function getParameter( name )
{
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( window.location.href );
  if( results == null )
    return "";
  else
    return results[1];
}

var OfferingContacts = {
	activeLinkId : "",
	init: function ()
	{
		if($$('#content .countrycontacts').length > 0)
		{
			$$('#content .countrycontacts').each(function(elem)
			{
				var contact = $(elem);
				var link = contact.down('div.countryname');
				var div = contact.down('div.dynamic');
				Event.observe(link, 'click', OfferingContacts.toggle.bindAsEventListener(this), false);
				if(contact.id != getParameter("country").toLowerCase())
				{
					Effect.BlindUp(div, {duration: 0.3});
					link.addClassName('reduced');
				}
				else
				{
					this.activeLinkId = contact.id;
				}
			});
		}
	},
	toggle: function(event)
	{
		var link = Event.element(event);
		var contact = link.up('div');
		var div = contact.down('div.dynamic');
		if(this.activeLinkId != contact.id && $(this.activeLinkId) != undefined)
		{
			var activeLink = $(this.activeLinkId)
			Effect.BlindUp(activeLink.down('div.dynamic'));
			activeLink.down('div.countryname').addClassName('reduced');
		}
		if(link.hasClassName('reduced'))
		{
			link.removeClassName('reduced');
			Effect.BlindDown(div);
			this.activeLinkId = contact.id;
		}
		else
		{
			Effect.BlindUp(div);
			link.addClassName('reduced');
			this.activeLinkId = "";
		}
	}
}
Event.observe(window, 'load', OfferingContacts.init.bindAsEventListener(this), false);

var geocoder;
var icon;
var reasons = [];

function initGoogleMap(map)
{
	geocoder = new GClientGeocoder();
	var baseIcon = new GIcon();
	baseIcon.iconSize = new GSize(10, 15);
	baseIcon.iconAnchor = new GPoint(5, 15);
	baseIcon.infoWindowAnchor = new GPoint(0, 1);
	icon = new GIcon(baseIcon, "/images/pointer_small_tcm101-67295.png", null, "");
	
	map.enableScrollWheelZoom();

    SelectMapType.prototype = new GControl();
	SelectMapType.prototype.container = document.createElement("div");
    SelectMapType.prototype.initialize = function(map){
        map.getContainer().appendChild(this.container);
        return this.container;
    }
	SelectMapType.prototype.addButton = function(map, name, mapType){
        var button = document.createElement("span");
        button = this.container.appendChild(button);
		$(button).className = "gmap-button";
        button.appendChild(document.createTextNode(name));
        GEvent.addDomListener(button, "click", function(){
            map.setMapType(mapType);
        });
		
	}
    // By default, the control will appear in the top left corner of the
    // map with 7 pixels of padding.
    SelectMapType.prototype.getDefaultPosition = function(){
        return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(10, 10));
    }
	
	// ====== Array for decoding the failure codes ======
	reasons[G_GEO_SUCCESS] = "Success";
	reasons[G_GEO_MISSING_ADDRESS] = "Missing Address: The address was either missing or had no value.";
	reasons[G_GEO_UNKNOWN_ADDRESS] = "Unknown Address:  No corresponding geographic location could be found for the specified address.";
	reasons[G_GEO_UNAVAILABLE_ADDRESS] = "Unavailable Address:  The geocode for the given address cannot be returned due to legal or contractual reasons.";
	reasons[G_GEO_BAD_KEY] = "Bad Key: The API key is either invalid or does not match the domain for which it was given";
	reasons[G_GEO_TOO_MANY_QUERIES] = "Too Many Queries: The daily geocoding quota for this site has been exceeded.";
	reasons[G_GEO_SERVER_ERROR] = "Server error: The geocoding request could not be successfully processed.";
}

function addMarkerAddress(map, address, compId)
{
	geocoder.getLocations(
		address,
		function(result)
		{
			if (result.Status.code != G_GEO_SUCCESS)
			{
				$(map.getContainer()).up('div').insert("<p>" + reasons[result.Status.code] + ": " + address + "</p>");
			}
			else
			{
				var point = result.Placemark[0].Point.coordinates;
				addMarkerLatLng(map, new GLatLng(point[1], point[0]), compId);
			}
		});
}

function addMarkerLatLng(map, coords, compId)
{
	var marker = new GMarker(coords, { icon: icon});
	map.addOverlay(marker);
	GEvent.addListener(marker, "click", function() {
		var x =  marker.getIcon().iconAnchor.x - marker.getIcon().infoWindowAnchor.x;
		var y = marker.getIcon().iconAnchor.y - marker.getIcon().infoWindowAnchor.y;
		var p = new GPoint(x, y);
		var ppx = map.fromLatLngToDivPixel(marker.getPoint());
		var dragObject = map.getPane(G_MAP_MAP_PANE).parentNode;

		x = ppx.x - p.x + parseInt(dragObject.style.left);
		y = ppx.y - p.y + parseInt(dragObject.style.top);
		showLocationDetail(map.getContainer(), x, y, compId);
		GEvent.addListener(map, "dragstart", function() {
			$('locationDetail').hide();
		} );
		GEvent.addListener(map, "moveend", function() {
			$('locationDetail').hide();
		} );
	});
	if (getParameter("showinfo") == compId)
	{
		GEvent.trigger(marker, "click");
	}
}

function addOverlay(map, shape, color){
    var latlngs = makeLatLngsFromArray(shape.points);
    var poly = new GPolygon(latlngs, color, 3, 0.8, color, 0.4);
    map.addOverlay(poly);
}

function makeLatLngsFromArray(a)
{
	var res=[];
	for(var i=0;i<a.length;i++)
	{
		res[i]=new GLatLng(a[i][0],a[i][1])
	};
	return res;
}

function SelectMapType(){
}

var VideoGallery = {
	init: function(gallID) {
		var content = $(gallID);
		var sidebar = $(gallID + "sb");
		$(sidebar).select('div').each(function(el) {
			$(el).select('img', 'h2').each(function(img) {
				var link = img.up('a');
				if (link == undefined) 
					link = img.down('a');
				link.observe("click", function(e) {
					var newVidSB = Event.element(e).up('div.video');
					var newVidCont = $(newVidSB.id + 'long');
					var aktVidCont = $$('#content .aktvid')[0];
					var aktVidSB = $(aktVidCont.id.replace("long", ""));
					if (!newVidSB.hasClassName('inactivevid')) 
					{
						Effect.Fade(aktVidCont, {
							duration: 0.5,
							queue: {
								position: 'end',
								scope: 'content'
							}
						});
						Effect.Appear(newVidCont, {
							duration: 0.5,
							queue: {
								position: 'end',
								scope: 'content'
							}
						});
						Effect.BlindUp(newVidSB, {
							duration: 0.5,
							queue: {
								position: 'end',
								scope: 'sidebar'
							}
						});
						Effect.BlindDown(aktVidSB, {
							duration: 0.5,
							queue: {
								position: 'end',
								scope: 'sidebar'
							}
						});
						aktVidCont.removeClassName("aktvid")
						aktVidSB.removeClassName("inactivevid");
						newVidSB.addClassName("inactivevid");
						newVidCont.addClassName("aktvid");
					}
					return false;
				}, false);
				link.onclick = function() {
					return false;
				};
			});
		});
	}
}

Event.observe(window, "load", function() {
	//Init video-gallery
	$$('#content div.gallery').each(function(gal) {
		VideoGallery.init(gal.id);
		$(gal).select('.video').each(function(el) {
			if (!$(el).hasClassName('aktvid'))
				Effect.Fade( el, { duration: 0});
		});
	});
});