/*
 * cfl.js: javascript functionality common to all (or at least most or many)
 *		 pages of the curious Food Lover website
 *
 * Parameters google.setOnLoadCallback(callback, bool):
 * bool: true: fire on DOM ready
 *      false: fire on load
 */

/*
 * global variable root: base URL of this site (relative to server root)
 * will be overwritten in the page by the server if it differs  
 */
var root = '/';

/*
 * create kind of a console for IE: a textarea with id="log" on the page or alerts
 */
if (!window.console) {
	var console = (function() {
		function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return(o.join('')); }

		/**
		 * sprintf() for JavaScript v.0.4
		 *
		 * Copyright (c) 2007 Alexandru Marasteanu <http://alexei.417.ro/>
		 * Thanks to David Baird (unit test and patch).
		 *
		 * This program is free software; you can redistribute it and/or modify it under
		 * the terms of the GNU General Public License as published by the Free Software
		 * Foundation; either version 2 of the License, or (at your option) any later
		 * version.
		 *
		 * This program is distributed in the hope that it will be useful, but WITHOUT
		 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
		 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
		 * details.
		 *
		 * You should have received a copy of the GNU General Public License along with
		 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
		 * Place, Suite 330, Boston, MA 02111-1307 USA
		 */
		function sprintf(arguments) {
			var i = 0, a, f = arguments[i++], o = [], m, p, c, x;
			while (f) {
				if (m = /^[^\x25]+/.exec(f)) o.push(m[0]);
				else if (m = /^\x25{2}/.exec(f)) o.push('%');
				else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) {
					if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) throw("Too few arguments.");
					if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
						throw("Expecting number but found " + typeof(a));
						switch (m[7]) {
						case 'b': a = a.toString(2); break;
						case 'c': a = String.fromCharCode(a); break;
						case 'd': a = parseInt(a); break;
						case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
						case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
						case 'o': a = a.toString(8); break;
						case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
						case 'u': a = Math.abs(a); break;
						case 'x': a = a.toString(16); break;
						case 'X': a = a.toString(16).toUpperCase(); break;
					}
					a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
					c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
					x = m[5] - String(a).length;
					p = m[5] ? str_repeat(c, x) : '';
					o.push(m[4] ? a + p : p + a);
				}
				else throw ("Huh ?!");
				f = f.substring(m[0].length);
			}
			return o.join('');
		}

		return {
			log: function(s) {
				var hasLogElt = $("#log").size() > 0;
				s = sprintf(arguments);
				if (hasLogElt) {
					$("#log").append(s + "<br />\n");
				} else {
					//alert(s);			
				}
			}
		};
	}());
}

/*
 * CFL Loader
 *
 * Allows to execute code that depends on other resources to be loaded first
 *
 * Interface:
 * cflLoader.require(resources, waitFor, callback)
 * - resources: array of strings: resources to be loaded first, may be:
 *	- a name as used by the google loader (jquery, maps, jqueryui, ...)
 *	- a name of a js that can be found in the website's js directory
 *	- a URL probably directing to a js script on another domain
 * - waitFor: null or (array of) array containing an object and a method name (string)
 *   if a resource is loaded, the content defined in it, are not immediately available
 *   in all browsers, waitFor defines a method for which to wait for to become available,
 *   before the callback is actually called.
 * - callback: function to be called as soon as all required recources are ready   
 */
var cflLoader = (function () {
	var _resources = new Array();
	var _callbacks = new Array();

	function _inResourcesArray(resource) {
		for (var i = 0, length = _resources.length; i < length; i++) {
			if (_resources[i].name == resource) {
				return i;
			}
		}
		return -1;
	}

	function _isArray(object) {
		return typeof (object.constructor) != 'undefined' && object.constructor == Array;
	}
	
	function _addCallbackResources(callback, resources, waitFor) {
		//console.log("_addCallbackResources(%d)", _callbacks.length);
		// add callback to _callbacks housekeeping
		_callbacks.push({callback: callback, required: resources, wait: waitFor, state: "waiting", count: 0});

		// add all not yet added resources to _resources housekeeping
		for (var i = 0, length = resources.length; i < length; i++) {
			//console.log("_addCallbackResources: requiring resource %s", resources[i]);
			if (_inResourcesArray(resources[i]) == -1) {
				//console.log("_addCallbackResources: adding %s", resources[i]);
				_resources.push({name: resources[i], state: "required"});
			}
		}
	}

	function _loadNotLoaded() {
		//console.log("_loadNotLoaded");
		for (var i = 0, length = _resources.length; i < length; i++) {
			if (_resources[i].state == "required") {
				_loadOne(i);
			}
		}
	}

	function _loadOne(i) {
		//console.log("_loadOne(%d): %s", i, _resources[i].name);
		_resources[i].state = "loading";
		switch (_resources[i].name) {
			 case "maps":
				google.load("maps", "3.x", {callback: function() { _ready(i); }, other_params: "sensor=false"});
			 	break;
			 case "jqueryui":
				// load .css and .js
				$("head").append($("<link>").attr({
					type: "text/css",
					rel: "stylesheet",
					href: "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/themes/base/jquery-ui.css"
				})); 
			 	var fullName = "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.2/jquery-ui.min.js";
			 	$.getScript(fullName, function() { _ready(i); });
			 	// line below sometimes returned with something like "not allowed after onload"
				//google.load("jqueryui", "1", {callback: function() { _ready(i); }});
			 	break;
			 default:
			 	var fullName = _resources[i].name;
			 	if (fullName.substr(0, "/".length) == "/") {
			 		// relative to root
			 		fullName = root + fullName.substr(1);
			 	}
			 	else if (fullName.substr(0, "http://".length) != "http://") {
			 		// relative to js directory
			 		fullName = root + "js/" + fullName;
			 	}
			 	// else: remote script (e.g. google analytics)
				$.ajaxSetup({cache: true});
			 	$.getScript(fullName, function() { _ready(i); });
			 	break;
		}
	}

	function _ready(i) {
		//console.log("_ready(" + i + "): " + _resources[i].name);
		_resources[i].state = "ready";
		_callReadyCallbacks();
	}
	
	function _callReadyCallbacks() {
		//console.log("_callReadyCallbacks");
		for (var i = 0, length = _callbacks.length; i < length; i++) {
			_callReadyCallback(i);
		}
	}

	function _callReadyCallback(i) {
		//console.log("_callReadyCallback(%d), state = %s", i, _callbacks[i].state);
		if (_callbacks[i].state == "waiting") {
			if (_allLoaded(i)) {
				//console.log("_callReadyCallback(%d): all resources loaded", i);
				// scripts are loaded, but content possibly not yet parsed: wait for the requested method to become available
				_callbacks[i].state = "waitAvailable";
			}
		}
		while (_callbacks[i].state == "waitAvailable") {
			if (_callbacks[i].wait !== null && _callbacks[i].wait.length > 0) {
				// check if the required function/methods are available
				var object, method;
				// wait is an array containing 2 (non array) elements or an array containing array elements containg 2 non array elements
				if (_isArray(_callbacks[i].wait[0])) {
					object = _callbacks[i].wait[0][0];
					method = _callbacks[i].wait[0][1];
					
				} else {
					object = _callbacks[i].wait[0];
					method = _callbacks[i].wait[1];
				}
				//console.log("_callReadyCallback(%d): check if %s is available", i, method);
				if (object[method]) {
					// object available: remove it from the list of required object/methods
					// remove either the array on index 0, or remove the object and the method name on positions 0 and 1
					//console.log("_callReadyCallback(%d): %s is available", i, method);
					if (!_isArray(_callbacks[i].wait[0])) {
						_callbacks[i].wait.shift();
					}
					_callbacks[i].wait.shift();
				} else {
					// not yet available: wait for it, but not infinetely (maximum of 4 seconds)
					if (_callbacks[i].count++ < 100) {
						//console.log("_callReadyCallback(%d): start waiting for %s to become available, count = %d", i, method, _callbacks[i].count);
						_callbacks[i].state = "waitingAvailable";
						setTimeout(function() {_callbacks[i].state = "waitAvailable";_callReadyCallback(i);}, 40);
					} else {
						//console.log("_callReadyCallback(%d): stop waiting for %s to become available: won't probably happen anymore", i, method);
						_callbacks[i].state = "called";
					}
				}
			} else {
				//console.log("_callReadyCallback(%d): nothing to wait anymore to become available", i);
				_callbacks[i].state = "available";
			}
		}
		if (_callbacks[i].state == "available") {
			//console.log("_callReadyCallback(%d): all resources available and parsed: execute callback", i);
			_callbacks[i].state = "called";
			if (_callbacks[i].callback !== null) {
				setTimeout(_callbacks[i].callback, 0);
			}
		}
	}
	
	function _allLoaded(i) {
		//console.log("_allLoaded: %d", i);
		var index;
		for (var j = 0, length = _callbacks[i].required.length; j < length; j++) {
			index = _inResourcesArray(_callbacks[i].required[j], _resources);
			if (index != -1) {
				if (_resources[index].state != "ready") {
					return false;
				}
			} else {
				//console.log("_allLoaded: error: resource not added to required: %s", _callbacks[i].required[j]);
				return false;
			}
		}
		return true;
	}

	return {
		require: function (resources, waitFor, callback) {
			// add callback together with its required resources to the internal housekeeping
			_addCallbackResources(callback, resources, waitFor);
			// kickoff loading
			_loadNotLoaded();
			// when all dependencies are already loaded (by a previous require), the callback can be fired immediately
			_callReadyCallbacks();
		}
	};
}());

/*
 * Load and initialize Google analytics, by using gatag.js, this also tracks external and document links
 */
function initGoogleAnalytics(googleAnalyticsKey)
{
	var googleAnalytics = (function() {
		var _pageTracker;

		function _trackLinks() {
			$("a").each(function () {
				try {
					var domElt = this;
					// href of a link is split into: protocol, host, hostname, port, pathname, search, hash
					if (domElt.protocol == "mailto:") {
						// add all mailto links
						$(domElt).click(_trackMailto);
					} else if (domElt.hostname == location.host)	{
						// add internal links only if they point to a document
						var path = domElt.pathname + domElt.search;
						var isDoc = path.match(/\.(?:doc|docx|eps|jpg|png|svg|xls|ppt|pdf|xlsx|zip|txt|vsd|vxd|js|css|rar|exe|wma|mov|avi|wmv|mp3)($|\&|\?)/);
						if (isDoc) {
							$(domElt).click(_trackDocument);
						}
					}
					else {
						// add all external links
						$(domElt).click(_trackExternal);
					}
				}
				catch(e) {}
			});
		}

		function _trackDocument() {
			var domElt = this;
			var link = (domElt.pathname.charAt(0) == "/") ? domElt.pathname : "/" + domElt.pathname;
			if (domElt.search && domElt.pathname.indexOf(domElt.search) == -1) {
				link += domElt.search;
			}
			if (typeof(_pageTracker) == "object") {
				_pageTracker._trackPageview(link);
			}
		}

		function _trackExternal() {
			var domElt = this;
			var link = (domElt.pathname.charAt(0) == "/") ? domElt.pathname : "/" + domElt.pathname;
			if (domElt.search && domElt.pathname.indexOf(domElt.search) == -1) {
				link += domElt.search;
			}
			link = "/external/" + domElt.hostname + link;
			if (typeof(_pageTracker) == "object") {
				_pageTracker._trackPageview(link);
			}
		}

		function _trackMailto() {
			var domElt = this;
			var link = "/mailto/" + domElt.href.substring(mailto.length);
			if (typeof(_pageTracker) == "object") {
				_pageTracker._trackPageview(link);
			}
		}

		return {
			track: function() {
				_pageTracker = _gat._getTracker(googleAnalyticsKey);
				_pageTracker._initData();
				_pageTracker._trackPageview();
				_trackLinks();
			}
		};
	})();

	cflLoader.require(new Array("http://www.google-analytics.com/ga.js"), new Array(window, "_gat"), googleAnalytics.track);
}

/*
 * translator: functionality for the (Google) translation widget
 * 
 * note: keep translator out of initTranslation because it must also be called after partial updates  
 */
var translator = (function() {
	var _empty = "Translate to...";
	var _currentLang = "en";	// we receive pages in english from the server

	function _initWidget() {
		$.translate(function() {
			// check if translate is available, and initialize
			var branding = $($.translate.getBranding());
			var jqTranslate = $('#translate')
				.empty()								// clear the loading message
				.append(								//insert the dropdown to the page
					$.translate.ui('select', 'option')	//generate dropdown
					.attr('id', 'translate-select')
					.prepend('<option>' + _empty + '</option>')
					.change(function(){					// when selecting another language
						_translatePage($(this).val());
					})
					.val(_empty))
				.append(								//insert Google's logo after the dropdown
					$(document.createElement("label"))
					.attr('for', 'translate-select')
					.attr('class', branding.attr('class'))
					.attr('style', branding.attr('style'))
					.append(branding.html()));

			// translate this page as well if previous page was translated
			dstLang = _getRequestedLang();
			if (dstLang != _currentLang) {
				// select it in the dropdown
				$('.jq-translate-ui').val(dstLang);
				_translatePage(dstLang);
			}
		});
	}
	
	function _getRequestedLang() {
		var lang = $.cookie ? $.cookie('requestedLang') : null;
		return lang ? lang : "en";	// English is the default selection
	}
	
	function _setRequestedLang(lang) {
		if ($.cookie) {
			$.cookie('requestedLang', lang, {path:'/'});
		}
	}

	function _translatePage(dstLang) {
		if (dstLang !== _currentLang) {
			_setRequestedLang(dstLang);
			_translateSelection('#content,#bottom-nav', dstLang);
			_currentLang = dstLang;
		}
	}

	function _partialTranslate(selector) {
		//console.log("_translateSelection(" + selector + "): " + $(selector).size());
		// we receive partial updates in english
		if (_currentLang !== "en") {
			_translateSelection(selector, _currentLang);
		}
		//console.log("_translateSelection(" + selector + "): finished");  
	}
	
	function _translateSelection(selector, dstLang) {
		//console.log("_translateSelection(" + selector + "): " + $(selector).size());
		$(selector).translateTextNodes(
				"en", 
				dstLang,
				{not:"#translate,.nav-info,.results-list strong,.show-detail h1,.contact-characteristics", fromOriginal:true}
		);
		//console.log("_translateSelection(" + selector + "): finished");  
	}

	return {
		createWidget: _initWidget,
		partialTranslate: _partialTranslate 
	};
}());

function initTranslator() {
	cflLoader.require(
		new Array("jquery.cookie.js", "jquery.translate-1.4.7.min.js"),
		new Array(new Array($, "cookie"), new Array($, "translate")),
		translator.createWidget
	);
}

/*
 * setFocus sets the focus to the given field
 */
function setFocus(eltId) {
	var elt = document.getElementById(eltId);
	if (elt) {
		elt.focus();
	}
}

/*
 * initFields lets entry fields default to an explanatory value in gray
 * (removed upon getting focus and not sent with a form post)
 */
function initFields(defaultValues) {
	function setDefaults() {
		for (var i = 0, length = defaultValues.length; i < length; i++) {
			$(defaultValues[i].selector).defaultValue(defaultValues[i].val);
		}
	};
	
	cflLoader.require(new Array("jquery.defaultvalue-1.2.js"), new Array($.fn, "defaultValue"), setDefaults);
}

/*
 * initFancyBox loads and initializes the fancy box image viewer
 */
function initFancyBox()
{
	function createFancyBox() {
		$("a.fancy").fancybox({'zoomSpeedIn': 300, 'zoomSpeedOut': 300, 'zoomOpacity': true, 'hideOnContentClick': false, 'groupOnRel': true, 'loopNavigation': true });
	};

	cflLoader.require(new Array("jquery.fancybox-1.2.1.cfl.js"), new Array($.fn, "fancybox"), createFancyBox);
}

/*
 * initMap initializes Google Map on the show activity detail page
 * 
 * 
 */
/**
 * initializes Google maps. Note that this is a staged initialization:
 * - On clicking "Show on map" for a first time, the Google maps API is loaded (thus real lazy loading)
 * - When the google Maps API is loaded the map is created
 * - When the toggling of the map has finished for the first time, the map is shown,
 *   including a correct center and zoom to show all markers
 * The code handles the case that these last 2 events occur in reversed order
 * - subsequent toggling just toggles the display of the map-panel and the text of the link 
 * 
 * @param {array<object>} points
 * point = 
 * 	{float} lat
 *	{float} lon
 *	{string} label
 * return void
 */
function initMap(points, show, hide, showByDefault)
{
	var mapCreated = false;
	var mapZoomIsSet = false;
	//var mapToggled = false; 
	var map = null;
	var bounds = null;
	var infoWindow = null;

	if (show === undefined) {
		show = "Show on map";
	}
	if (hide === undefined) {
		hide = "Hide map";
	}
	if (showByDefault === undefined) {
		showByDefault = false;
	}

	function createMap() {
		//console.log("createMap(): start");  
		var mapElement = document.getElementById("map-panel");
		map = new google.maps.Map(mapElement, {mapTypeId: google.maps.MapTypeId.ROADMAP, zoom: 15, streetViewControl: true});

		//console.log("createMap(): map created");  
		// place markers
		bounds = new google.maps.LatLngBounds();
		var pointInfo, point, marker;
		for (var i = 0; i < points.length; i++) {
			pointInfo = points[i];
			point = new google.maps.LatLng(pointInfo.lat, pointInfo.lon);
			bounds.extend(point);
			marker = new google.maps.Marker({position: point, map: map, title: pointInfo.title});
			if (pointInfo.info !== undefined) {
				marker.infoWindow = new google.maps.InfoWindow({content: pointInfo.info});
				google.maps.event.addListener(marker, 'click', function() {
					this.infoWindow.open(map, this);
				});
			}
		}

		//console.log("createMap(): markers and events created");  
		// define clean up on unload
		$("body").unload(google.maps.Unload);
		
		mapCreated = true;
		// we always call showMap, there seems to be a delay after toggling, before showMap gets called,
		// and it doesn't hurt to call showMap twice
		//if (mapToggled) {
			// map panel finished toggling before the api was loaded and the map created: call showMap once more
			//console.log("createMap(): has to call showMap");  
			showMap();
			//console.log("createMap(): called showMap");  
		//}
		//console.log("createMap(): end");  
	}

	function showMap() {
		//console.log("showMap(): start");  
		//mapToggled = true;
		// can only execute the following if the map has been properly created 
		if (mapCreated) {
			//console.log("showMap(): can show");  
			// center and zoom map around markers
			google.maps.event.trigger(map, 'resize');
			if (!mapZoomIsSet) {
				map.fitBounds(bounds);
				mapZoomIsSet = true;
			}
			if (map.getZoom() > 15) {
				map.setZoom(15);
			}
		}
		//console.log("showMap(): end");  
	}

	function toggleText(elt) {
		if (elt.html().indexOf(show) != -1) {
			text = hide;
		} else {
			text = show;
		}
		elt.html(text);
	}
	
	cflLoader.require(new Array("maps"), null, createMap);
	$("#map-toggle").click(function(event) {
		//console.log("initMap(): click");
		event.preventDefault();
		$("#map-panel").toggle(500, function() {
			showMap();
		});			
		toggleText($('span:last',this));
		//console.log("initMap(): click: end");  
	});
	if (showByDefault) {
		$("#map-toggle").click();
	}
}

/**
 * Adds a show/hide details link on an element that shows/hides the next element
 * 
 * Note that rounded corners (in a browser without native support for rounded corners)
 * may interfere with this function, make sure this function is called before rounded
 * corners are applied
 *  
 * @param selector jquery selector, is passed unchanged to the $() function
 * @param hideByDefault boolean start in hidden (true) or open (false) state, default = true = hidden
 *    this parameter may be replaced by (also) setting a class="hide-by-default" on the element
 *    (i.e the element that gets the hide/show link and normally serves as title/caption)  
 * @param show string, used as anchor text in hidden state, default = show
 * @param hide string, used as anchor text in open state, default = hide
 * @return void
 */
function addShowHide(selector, hideByDefault, show, hide)
{
	if (hideByDefault == undefined) {
		hideByDefault = true;
	}
	if (show == undefined) {
		show = "show";
	}
	if (hide == undefined) {
		hide = "hide";
	}
	// append anchor and click event to that anchor
	matching = $(selector);
	matching
		.append('<span>(<span class="a-sign">»</span><span>' + hide + '</span>)</span>')
		.wrapInner('<a href="" class="show-hide"></a>')
		.children("a.show-hide").click(function(event) {
			event.preventDefault();
			$('span span:last',this).html($(this).html().indexOf(show) != -1 ? hide : show);
			// we use parents (instead of parent) as rounded corners may add a hierarchy in between
			var ancestor = $(this).parents(selector);
			// and formelements and such may add wrappers around our element, so keep on searching
			while (ancestor.next().size() === 0) {
				ancestor = ancestor.parent();
			}
			ancestor.next().toggle(500);
		});
	if (hideByDefault) {
		matching.children("a.show-hide").click();
	} else {
		// hide by default based on class
		matching.filter(".hide-by-default").children("a.show-hide").click();
	}
}

/*
 * initTrailers makes stories hidable/expandable 
 */
function initTrailers()
{
	// Add click to all stories that are too long
	$("span.trailer").after(' <a href=""><span class="a-sign">»</span><span>less ...</span></a>');
	$("span.trailer").next('a').click(function(event) {
		event.preventDefault();
		doShow = $(this).html().indexOf("more") != -1;
		$('span:last',this).html(doShow ? 'less ...' : 'more ...');
		// IE has trouble animating (showing only?) the element (hasLayout = false?)
		if ($.browser.msie) {
			if (doShow) {
				$(this).prev("span.trailer").show();
			} else {
				$(this).prev("span.trailer").hide();
			}
		}
		else {
			$(this).prev("span.trailer").toggle(250);
		}
	});
	// hide trailers by default
	$("span.trailer").next('a').click();
}

function initTermsOfUsePopup()
{
	function createDialog() {
		$("#terms-of-use-dialog").dialog({ autoOpen: false, width: 512, height: 400 });
		$('label[for="agree_terms_of_use"] a').click(function(event) {
			event.preventDefault();
			$("#terms-of-use-dialog").dialog("open");
		});
	}

	cflLoader.require(new Array("jqueryui"), new Array($.fn, "dialog"), createDialog);
}

function initPopup(close)
{
	close = typeof(close) != 'undefined' ? close : false;

	if (close === true) {
		cssClass = "popup-form";
		allowEsc = true;
	} else {
		cssClass = "popup-form popup-remain";
		allowEsc = true;
	}
		
	function createDialog() {
		$(".popup").dialog({dialogClass: cssClass, width: 976, closeOnEscape: allowEsc, resizable: true, draggable: true});
	}
	
	cflLoader.require(new Array("jqueryui"), new Array($.fn, "dialog"), createDialog);
}

function initForm(formId, fieldName, value)
{
	elt = $("#" + formId).find("input[name='" + fieldName + "']");
	if (elt.length == 0) {
		// add hidden field to it
		input = $('<input>').attr({
			type: 'hidden',
			name: fieldName,
			value: ''
		});
		$("#" + formId).append(input);
	} else {
		// hide it
		elt.parents('fieldset').addClass('hidden');
	}
	$("#" + formId).find(" input[name='" + fieldName + "']").first().each(function(index) {
		elt = $(this);
		setTimeout(function(){elt.attr('value', value);}, 3000);
	});
}

function showIe6Warning()
{
	function createIe6Warning() {
		$("#ie6-warning").dialog({modal: true, title: "Warning", width: 800, position: [100,10], closeOnEscape: true});
	}

	cflLoader.require(new Array("jqueryui"), new Array($.fn, "dialog"), createIe6Warning);
}

function initRoundedCorners() {
	function setRoundedCorners() {
		if (!$.browser.msie || $.browser.version.substr(0,1) > '6') {
			// results list
			$("li.rating4 > strong, li.rating3 > strong").corners("7px transparent top");
			$("li.rating4 > div, li.rating3 > div").corners("7px transparent bottom");
			$("li.rating2 > strong").corners("7px transparent");
			// detail view
			$(".show-detail h1").corners("7px transparent top");
			$(".show-detail .location-characteristics").corners("7px transparent bottom");
			$(".show-detail .caption").corners("7px transparent");
			// profile view
			$(".show-profile .caption").corners("7px transparent");
			// reviews (detail view and profile view)
			$(".review").corners("7px transparent");
		}
	}

	cflLoader.require(new Array("jquery.corners.min.js"), new Array($.fn, "corners"), setRoundedCorners);
}

//TOOD: put in an IE specific file
var ieSelect = (function() {
	var _smallSelects;
	var _smallSelectsSettings;

	function _addIfTooSmall(index, domElt) {
		var elt = $(domElt);
		//console.log("_addIfTooSmall(" + index + "," + domElt.id + ")");
		orgCssWidth = elt.css("width");
		orgPixelWidth = elt.width();
		elt.css("width", "auto");
		newPixelWidth = elt.width();
		elt.css("width", orgCssWidth);
		if (orgPixelWidth < newPixelWidth) {
			_smallSelects.push(domElt);
			_smallSelectsSettings[domElt.id] = {offset: elt.offset(), width: elt.width(), height: elt.height(), orgCssWidth: orgCssWidth, state: "closed"};
		}
	}

	function _findSmallSelects() {
		//console.log("_findSmallSelects()");
		_smallSelects = new Array();	// (re)initialise here because of ajax calls that may update complete panels
		_smallSelectsSettings = new Object;
		$('select:enabled').each(_addIfTooSmall);
	}

/*
	// @deprecated
	function inSelect(domElt, eventObject) {
		bodyElt = $("body");
		console.log("body scroll = (" + bodyElt.scrollLeft() + "," + body.scrollTop() + ")");  
		obj = _smallSelectsSettings[domElt.id];
		elt = $(domElt);
		offset = elt.offset();
		eltX = offset.left;
		eltY = offset.top;
		console.log("offsetX,Y = (" + eltX + "," + eltY  + ")");
		width = elt.width();
		height = elt.height();
		mouseX = eventObject.clientX;
		mouseY = eventObject.clientY;
		result = eltX <= mouseX && mouseX <= eltX + width
		      && eltY <= mouseY && mouseY <= eltY + height;
		console.log("inSelect(name: " + domElt.id + ", x: " + eltX + " to " + (eltX + width) + ", y: " + eltY + " to " + (eltY + height) + ", mouse: " + eventObject.clientX + "," + eventObject.clientY + ") returns " + (result?"true":"false"));
		return result; 
	}
*/

	function _setAutoWidth(domElt) {
		//console.log("setAutoWidth(" + domElt.id + ") state = opening");
		_smallSelectsSettings[domElt.id].state = "opening";
		$(domElt).css("width", "auto");
	};

	function _setOrgWidth(domElt) {
		//console.log("setOrgWidth(" + domElt.id + ")");
		if (_smallSelectsSettings[domElt.id]) {
			//console.log("setOrgWidth(" + domElt.id + ") state = closed");
			_smallSelectsSettings[domElt.id].state = "closed";
			$(domElt).css("width", _smallSelectsSettings[domElt.id].orgCssWidth);
		}
	};

	function _handleMousedown(eventObject) {
		//console.log("handleMousedown(" + this.id + ") while state = " + _smallSelectsSettings[this.id].state);  		
		if (_smallSelectsSettings[this.id].state == "closed") {
			// mousedown while closed: this will open the list, so set it to auto width
			_setAutoWidth(this);
		} //else {
			// mousedown while list is open: if in select itself, this will immediately close the list
			// if in the list, the list will only be closed on mouseup
			//if (inSelect(this, eventObject)) {
			//	closeElt = this
			//	setTimeout(function(){setOrgWidth(closeElt)}, 0); 
			//}	 
		//}
	}
	
	function _handleMouseup(eventObject) {
		// mouseup: if just opened (on the mousedown): change state to opened
		// if not yet closed: close it
		//console.log("handleMouseup(" + this.id + ")");
		if (_smallSelectsSettings[this.name].state == "opening") {
			//console.log("handleMouseup(" + this.id + "): state == opening => state = opened");
			_smallSelectsSettings[this.id].state = "opened";
		} else if (_smallSelectsSettings[this.id].state == "opened") {
			//console.log("handleMouseup(" + this.id + "): state == opened => closing");
			closeElt = this; 
			setTimeout(function(){_setOrgWidth(closeElt);}, 0);
		}
		else {
			//console.log("handleMouseup(" + this.id + "): state = " + _smallSelectsSettings[this.id].state);
		}
	}
	
	function _handleKeypress(eventObject) {
		// keypress: escape while opened: close it
		//console.log("handleKeypress(" + this.id + "," + eventObject.keyCode + ")");
		keyCode = eventObject.keyCode; 
		if (keyCode == 27 && _smallSelectsSettings[this.id].state == "opened") {
				closeElt = this; 
				_setOrgWidth(closeElt);
		}
	}
	
	return {
		fixWidth: function() {
			if($.browser.msie) {
				_findSmallSelects();
				$(_smallSelects).wrap("<div class='wrapper'></div>").
					mousedown(_handleMousedown).
					mouseup(_handleMouseup).
					keypress(_handleKeypress).
					change(function() { _setOrgWidth(this); }).
					blur(function() { _setOrgWidth(this); });
					// TODO: normal behaviour:
					// - mousedown in open mode and in select itself (not in list below): close
					// - mousedown in open mode but outside select itself (thus in list below): nothing
					// - mouseup in open mode but outside select itself (thus in list below): close
					// note:
					// - the mouseup event comes earlier than change and covers the case where the already selected item is selected
					// - this holds only if the mousedown and mouseup where not part of a scroll event
			}
		}
	};
}());

function eventLogger() {
	function _logEvents() {
		$("select").
			mousedown(function(eventObject) { console.log(this.name + ": mousedown (" + eventObject.clientX + "," + eventObject.clientY + ")"); }). 
			mouseup(function() { console.log(this.name + ": mouseup"); }). 
			mouseover(function() { console.log(this.name + ": mouseover "); }).
			mouseout(function() { console.log(this.name + ": mouseout"); }). 
			mouseenter(function() { console.log(this.name + ": mouseenter"); }). 
			mouseleave(function() { console.log(this.name + ": mouseleave"); }). 
//			mousemove(function() { console.log(this.name + ": mousemove"); }). 
			click(function() { console.log(this.name + ": click"); }). 
			dblclick(function() { console.log(this.name + ": dblclick"); }). 
			change(function() { console.log(this.name + ": change"); }). 
			focus(function() { console.log(this.name + ": focus"); }). 
			blur(function() { console.log(this.name + ": blur"); });
	};

	elementSelection._logEvents();
};

//Mail to link bescherming
//door mail to links niet hard in onze html op te nemen, bereiken we 2 dingen:
//- spambots worden misleid
//- er komt geeen melding dat de pagina beveiligde en onbeveiligde items bevat.
function zetMeel(eltId) {
	var elt = document.getElementById(eltId);
	if (elt) {
		// normally mell address will be put in innerHtml and href will equal "#"
		// but if innerHtml should not become the plain meel address, put the meel
		// address directly in href, but in this case prefixed by meelto: as
		// otherwise the href property wil be expanded as if being a relative URL
		var meelTo = "mai";
		meelTo += "lto";
		meelTo += ":";
		var versleuteldMeelAdres;
		var changeInnerHtml;
		if (elt.href.length > 13 && elt.href.substr(0,7) == meelTo) {
			versleuteldMeelAdres = elt.href.substr(7);
			changeInnerHtml = false;
		}
		else {
			versleuteldMeelAdres = elt.innerHTML;
			changeInnerHtml = true;
		}
		// only decrypt if it not already contains a meel address (in combination
		// with a jQuery popup, this code will be exucted twice)
		if (versleuteldMeelAdres.indexOf("@") == -1) {
			var meelAdres = "";
			for(var i = 0; i < versleuteldMeelAdres.length; i++)
			{
				meelAdres = String.fromCharCode(versleuteldMeelAdres.charCodeAt(i)) + meelAdres;
			}
			meelAdres = meelAdres.replace(/\?/, "@");
			var href = meelTo;
			href += meelAdres;
			elt.href = href;
			if (changeInnerHtml) {
				elt.innerHTML = meelAdres;
			}
		}
	}
}

//
// (Hierarchical) Dropdown Ajax functionality
//
//TODO: bind events using jquery, and do not render onchange attributes
//TODO: put in 1 init function like the rest?
//TODO: get options on init (faster main page load)
function partialUpdate(url, sourceId, valuesSelector, targetId)
{
	var showErrors = true;
	var params = $(valuesSelector).serialize();
	params += "&source=" + sourceId + "&targetId=" + targetId;
	$.ajax({
		type: "POST",
		url: root + url,
		data: params,
		error: function(xhr, errorText, e) {
			var message = '';
			if (e) {
				message = "An exception occurred. Error name: " + e.name + ". Error message: " + e.message;
			} else {
				if (errorText) {
					message = "A '" + errorText + "' error occurred";
				} else {
					message = "An error occurred";
				}
				if (xhr.statusText) {
					message += ": " + xhr.statusText;
				}
				if (xhr.responseText) {
					message += ". " + xhr.responseText;
				}
			}
			if (showErrors) {
				showErrors = false;
				alert(message);
			}
		},
		success: function(data) {
			$("#" + targetId).html(data);
			translator.partialTranslate('#' + targetId);
			ieSelect.fixWidth();
		}
	});	
}

function updateSearchQuick(sourceId)
{
	partialUpdate("search/search-quick", sourceId, "#search-form", "search-quick");
}

function updateSearchPanel(sourceId)
{
	partialUpdate("search/search-panel", sourceId, "#search-form", "search-panel");
}

function partialUpdateActivityType(sourceId)
{
	partialUpdate("activity/partial-update", sourceId, "#fieldset-activity_types :input", "fieldset-activity_types");
}

function partialUpdateSpecialty(sourceId)
{
	partialUpdate("activity/partial-update", sourceId, "#fieldset-specialties :input", "fieldset-specialties");
}

function partialUpdateLocation(sourceId)
{
	partialUpdate("activity/partial-update", sourceId, "#fieldset-location :input", "fieldset-location");
}

