var form_html = [];
var settings_array = [];
var pikavalinnat_array = [];

var USER_URL = '';
var TRANSLATOR_HOST = '';

function addEngine(name,ext,cat)
{
	if ((typeof window.sidebar == "object") && (typeof window.sidebar.addSearchEngine == "function"))
	{
		window.sidebar.addSearchEngine("http://static2.sanakirja.org/sanakirja/" + name + ".src", "http://static2.sanakirja.org/sanakirja_img/fx_" + name + "." + ext, name, cat);
	}
	else
	{
		alert(ui_phrases['FIREFOX_REQUIRED']);
	}
}

function switch_languages()
{
	var form = document.getElementById('search');
	tmp = form['l'].value;
	form['l'].value = document.forms['search']['l2'].value;
	form['l2'].value = tmp;
	
	form['q'].focus();
	form['q'].select();
		
	return false;
}

if (top.location != location)
{
    top.location.href = document.location.href;
}

function init_pikavalinnat()
{
	var pikavalinnat = document.getElementsByName('pikavalinta');
	
	var l, l2;
	
	pikavalinnat_array = new Array();
	
	for (var i = 0; i < pikavalinnat.length; i++)
	{
		l = pikavalinnat[i].href.replace(/.*l=([0-9]+).*/, '$1');
		l2 = pikavalinnat[i].href.replace(/.*l2=([0-9]+).*/, '$1');
		
		pikavalinnat_array[i] = new Array(l, l2);
		pikavalinnat_cookie.setSubValue(i + 's', l + ',' + l2);
		
		pikavalinnat[i].onclick = function()
		{
			var form = document.getElementById('search');
			form['l'].value = this.href.replace(/.*l=([0-9]+).*/, '$1');
			form['l2'].value = this.href.replace(/.*l2=([0-9]+).*/, '$1');
			
			form['q'].focus();
			form['q'].select();
			return false;
		}
	}
	
	function show_pikavalinnat_link()
	{
		$('#pikavalinnat_link').slideDown();
	}
	
	if (pikavalinnat.length == 0)
	{
		$('#pikavalinnat_td').append('<ul><li id="pikavalinnat_link"><a href="">' + ui_phrases['MODIFY_SHORTCUTS'] + '</a></li></ul>');
		$('#pikavalinnat_link').show();
	}
	else
	{
		$('#pikavalinnat_td ul').append('<li id="pikavalinnat_link"><a href="">' + ui_phrases['MODIFY_SHORTCUTS'] + '</a></li>');
	}
	
	function hide_pikavalinnat_link()
	{
		$('#pikavalinnat_link').slideUp();
	}
	
	var config =
	{    
		sensitivity: 3, // number = sensitivity threshold (must be 1 or higher)    
		interval: 500, // number = milliseconds for onMouseOver polling interval    
		over: show_pikavalinnat_link, // function = onMouseOver callback (REQUIRED)    
		timeout: 400, // number = milliseconds delay before onMouseOut    
		out: hide_pikavalinnat_link // function = onMouseOut callback (REQUIRED)    
	};
	
	$("#pikavalinnat_td").hoverIntent(config);
	
	var pikavalinnat_link = document.getElementById('pikavalinnat_link');
	
	if (pikavalinnat_link)
	{
		pikavalinnat_link.href = '#';
		pikavalinnat_link.onclick = function()
		{
			document.getElementById('pikavalinnat').style.display = 'block';
			hide_flash();
			
			return false;
		}
	}
	
	pikavalinnat_div = document.getElementById('pikavalinnat');
	
	if (!pikavalinnat_div)
	{
		return;
	}
	
	var html = '';
	var poista_pikavalinta_select = '';
	
	html += '<input type="button" value="' + ui_phrases['CLOSE'] + '" style="float: right;" onclick="sulje_pikavalinnat();" />';
	html += '<h1>' + ui_phrases['MODIFY_SHORTCUTS'] + '</h1><br />';
	html += '<table style="margin:auto;"><tr><th>' + ui_phrases['SOURCE_LANGUAGE'] + ':</th><th>' + ui_phrases['TARGET_LANGUAGE'] + ':</th></tr><td>';

	languages_select = '<select size="10" name="pikavalinnat_l">';
	var language_names = $('#search select[name="l"] option[value!="-1"]');
	
	for (var i = 1; i < language_names.length; i++)
	{
		languages_select += '<option value="' + language_names[i].value + '">' + language_names[i].text + '</option>';
	}
	
	languages_select += '</select>';
	
	html += languages_select + '</td><td>' + languages_select;
	
	html += '</td></tr></table>';
	
	html += '<div style="text-align: center;"><input type="button" value="' + ui_phrases['ADD'] + '" onclick="lisaa_pikavalinta();" /></div>'
	
	html += '<p />' + ui_phrases['REMOVE_SHORTCUT'] + ': ';
	
	if (pikavalinnat_array.length == 0)
	{
		poista_pikavalinta_select = '<select id="poista_pikavalinta"><option value="-1">(' + ui_phrases['EMPTY'] + ')</option></select>';
	}
	else
	{
		poista_pikavalinta_select = '<select id="poista_pikavalinta">';
		
		for (var i = 0; i < pikavalinnat_array.length; i++)
		{
			poista_pikavalinta_select += '<option value="' + isocodes[pikavalinnat_array[i][0]] + '-' + isocodes[pikavalinnat_array[i][1]] + '">' + isocodes[pikavalinnat_array[i][0]] + '-' + isocodes[pikavalinnat_array[i][1]] + '</option>';
		}
		
		poista_pikavalinta_select += '</select>';
	}
	
	html += poista_pikavalinta_select + ' <input type="button" value="' + ui_phrases['REMOVE'] + '" onclick="poista_pikavalinta();" />';
	
	html += '<p /><input type="button" value="' + ui_phrases['RESTORE_DEFAULTS'] + '" onclick="palauta_oletuspikavalinnat();" />';
	
	pikavalinnat_div.innerHTML = html;
}

function hide_flash()
{
	$('object').filter(function() {return $(this).parents('div.dialog').length == 0;}).css('visibility', 'hidden');
	$('embed').filter(function() {return $(this).parents('div.dialog').length == 0;}).css('visibility', 'hidden');
}

function show_flash()
{
	$('object').css('visibility', 'visible');
	$('embed').css('visibility', 'visible');
}

function sulje_pikavalinnat()
{
	document.getElementById('pikavalinnat').style.display = 'none';
	show_flash();
}

function palauta_oletuspikavalinnat()
{
	if (!confirm(ui_phrases['CONFIRM_RESTORE_DEFAULTS']))
	{
		return;
	}
	
	var html = generoi_linkit(default_shortcuts);
	
	for (var i = 0; i < isocodes.length; i++)
	{
		pikavalinnat_cookie.setSubValue(i + 's', null);
	}
	
	for (var i = 0; i < default_shortcuts.length; i++)
	{
		pikavalinnat_cookie.setSubValue(i + 's', default_shortcuts[i][0] + ',' + default_shortcuts[i][1]);
	}
	
	document.getElementById('pikavalinnat_td').innerHTML = html;
	
	init_pikavalinnat();
}

function lisaa_pikavalinta()
{
	var pikavalinnat_td = document.getElementById('pikavalinnat_td');
	
	var html = '';
	var exists = false;
	
	l = document.getElementsByName('pikavalinnat_l')[0].value;
	l2 = document.getElementsByName('pikavalinnat_l')[1].value;
	
	if (l == '' || l2 == '')
	{
		return false;
	}
	
	pikavalinnat_td.innerHTML = '';
	
	for (var i = 0; i < pikavalinnat_array.length; i++)
	{
		if (pikavalinnat_array[i][0] == l && pikavalinnat_array[i][1] == l2)
		{
			exists = true;
			break;
		}
	}
	
	if (!exists)
	{
		pikavalinnat_array[pikavalinnat_array.length] = new Array(l, l2);
	}
	
	html = generoi_linkit(pikavalinnat_array);
	
	pikavalinnat_td.innerHTML = html;
	
	init_pikavalinnat();
	_gaq.push(['_trackEvent', 'sanakirja', 'lisaa_pikavalinta']);
}

function generoi_linkit(pikavalinnat)
{
	var html = '';
	
	for (var i = 0; i < pikavalinnat.length; i++)
	{
		html += '<a href="/index.php?l=' + pikavalinnat[i][0] + '&l2=' + pikavalinnat[i][1] + '" name="pikavalinta">' + isocodes[pikavalinnat[i][0]] + '-' + isocodes[pikavalinnat[i][1]] + '</a> ';
	}
	
	return trim(html);
}

function trim(str)
{
	return str.replace(/^\s*|\s*$/g, "");
}

function poista_pikavalinta()
{
	if (document.getElementById('poista_pikavalinta').value == -1)
	{
		return;
	}
	
	for (var i = 0; i < isocodes.length; i++)
	{
		pikavalinnat_cookie.setSubValue(i + 's', null);
	}
	
	var l = get_language_by_isocode(document.getElementById('poista_pikavalinta').value.substring(0,2));
	var l2 = get_language_by_isocode(document.getElementById('poista_pikavalinta').value.substring(3,5));
	
	var pikavalinnat_td = document.getElementById('pikavalinnat_td');
	var html = '';
	
	for (var i = 0; i < pikavalinnat_array.length; i++)
	{
		if (pikavalinnat_array[i][0] == l && pikavalinnat_array[i][1] == l2)
		{
			pikavalinnat_array.splice(i, 1); // unset pikavalinnat_array[i]
			pikavalinnat_cookie.setSubValue(i + 's', null);
			break;
		}
	}
	
	html = generoi_linkit(pikavalinnat_array);
	
	pikavalinnat_td.innerHTML = html;
	
	init_pikavalinnat();
}

function get_language_by_isocode(isocode)
{
	for (var i = 0; i < isocodes.length; i++)
	{
		if (isocodes[i] == isocode)
		{
			return i;
		}
	}
	
	return -1;
}

function track_funnel(word_text, language, target_word)
{
	var img = new Image();
	img.src = 'http://www.sanakirja.org/funnel.php?w=' + word_text + '&l=' + language + "&wid=" + target_word;
	_gaq.push(['_trackEvent', 'sanakirja', 'funnel', word_text, target_word]);
}

function init_suggestions()
{
	var suggestions = $('#suggestions a');
	
	if (suggestions && (!document.referrer || document.referrer.indexOf('www.google.') == -1))
	{
		suggestions.click(function()
		{
			var word_id_regexp = /search\.php\?id=([0-9]+)/;
			var word_text_regexp = /q=([^&]+)/;
			var lang_regexp = /&l=([0-9]+)/;
			
			var word_id = this.href.match(word_id_regexp);
			var word_text = location.href.match(word_text_regexp);
			
			if (!word_text)
			{
				word_text = document.forms[0]['q'].value;
			}
			else
			{
				word_text = word_text[1];
			}
			
			var source_language = location.href.match(lang_regexp);
			
			if (!source_language)
			{
				source_language = document.getElementById('current_language').href.match(lang_regexp);
			}
			
			track_funnel(word_text, source_language[1], word_id[1]);
			setTimeout('location.href="' + this.href + '"', 150);
			
			return false;
		});
	}
}

function init_token_refresh()
{
	if (SK && SK['form_token_str'] != '')
	{
		setInterval(load_form_tokens, 1000 * 1600);
	}
}

function load_form_tokens(additional_callback)
{
	var callback = function(data)
	{
		refresh_form_tokens(data.form_token_str, data.form_token_time);
		
		if (typeof additional_callback == 'function')
		{
			additional_callback();
		}
	};
	
	$.post(USER_URL, 'refresh_token=', callback, 'jsonp');
}

function refresh_form_tokens(form_token_str, form_token_time)
{
	$('a').each(function()
	{
		if (this.href && this.href.indexOf('tkn=') != -1)
		{
			this.href = this.href.replace('tkn=' + SK['form_token_str'], 'tkn=' + form_token_str);
			this.href = this.href.replace('tkn_t=' + SK['form_token_time'], 'tkn_t=' + form_token_time);
		}
	});
	
	$('input[name="tkn"]').each(function()
	{
		this.value = form_token_str;
	});
	
	$('input[name="tkn_t"]').each(function()
	{
		this.value = form_token_time;
	});
	
	SK['form_token_str'] = form_token_str;
	SK['form_token_time'] = form_token_time;
}

function global_onload()
{
	if (SK['translator_host'])
	{
		TRANSLATOR_HOST = SK['translator_host'];
	}
	else
	{
		TRANSLATOR_HOST = 'http://translator.sanakirja.org:8080/';
	}
	
	$('#swap_languages img').click(function() { switch_languages(); });
	
	init_pikavalinnat();
	init_error_report();
	init_login();
	init_suggestions();
	init_user();
	
	$('#show_sources').click(function()
	{
		$.getScript('http://static2.sanakirja.org/sanakirja/error_report.js?' + Math.random(), show_sources);
		return false;
	});
	
	if (location.href.indexOf('#') == -1 && $('#search input[name="q"]').val() == SK['main_word_text'])
	{
		focus($('#search input[name="q"]')[0]);
	}
	
	$('#cookies_disclaimer').click(show_cookies_disclaimer);
	
	$('select.category_source_language').change(function()
	{
		var href = location.href;
		
		if (href.indexOf('&l=') == -1)
		{
			href += '&l=' + $(this).val();
		}
		else
		{
			href = href.replace(/&l=[0-9]+/, '&l=' + $(this).val());
		}
		
		if (href.indexOf('&l2=' + $(this).val()))
		{
			href = href.replace('&l2=' + $(this).val(), '');
		}
		
		href = href.replace(/&start=[0-9]+/, '');
		
		location.href = href;
	});
	
	$('a.audio').click(function()
	{
		return play_audio(this);
	});
	
	init_tooltips();
	
	if (document.getElementById('xa'))
	{
		var xa = new Image();
		xa.src = 'http://' + document.domain + '/search.php?xa=' + document.getElementById('xa').innerHTML.split(',')[0] + '&c=' + document.getElementById('xa').innerHTML.split(',')[1];
	}
	
	if (typeof customers_init == 'function')
	{
		customers_init();
	}
}

function init_tooltips()
{
	if ($('table#translations').length)
	{
		$('div.definitions h3').each(function()
		{
			$(this).append(' <img src="http://static2.sanakirja.org/sanakirja_img/information.png" class="info" alt="" />');
			attach_tooltip($(this).find('img'), ui_phrases['EXAMPLES_INFO']);
		});
	}
	
	$('abbr').each(function()
	{
		var title = $(this).attr('title');
		attach_tooltip($(this), title);
		$(this).attr('title', '');
	});
	
	$('a.pronunciation_alphabet').each(function()
	{
		$(this).click(function()
		{
			show_pronunciation_guide($(this));
			return false;
		});
	});
}

function show_pronunciation_guide(link)
{
	var alphabet = link.text();
	var language = link.attr('rel');
	var text = link.attr('href').substring(1);
	
	function callback()
	{
		var rows = $('#pronunciation_guide tr:has(td)');
		
		$('#pronunciation_guide tr:has(td:has(big))').sortElements(function(a, b)
		{
			return $(a).find('big').text().length < $(b).find('big').text().length ? 1 : -1;
		});
		
		var symbols = rows.find('td big').textNodes([',', '']);
		rows.css('display', 'none');
		var found_symbols = [];
		
		symbols.each(function()
		{
			var symbol = this.textContent.replace(',', '').trim();
			
			if (text.indexOf(symbol) != -1)
			{
				for (var i = 0; i < found_symbols.length; i++)
				{
					if (found_symbols[i].length > symbol.length && found_symbols[i].indexOf(symbol) != -1)
					{
						return;
					}
				}
				
				$(this).parents('tr').css('display', 'table-row').attr('symbol', symbol).addClass('visible');
				found_symbols.push(symbol);
			}
		});
		
		$('#pronunciation_guide tr.visible').sortElements(function(a, b)
		{
			return text.indexOf($(a).attr('symbol')) > text.indexOf($(b).attr('symbol')) ? 1 : -1;
		});
	}
	
	var title = 'Ääntämisohje - ' + text;
	
	var audio = link.parent().parent().parent().find('a.audio, object');
	if (audio.length)
	{
		audio = audio.first().clone(true);
		audio.css('margin-left', '10px');
		
		var flashVars = audio.find('param[name="flashVars"]');
		if (flashVars.length)
		{
			flashVars.attr('value', flashVars.attr('value').replace('autoplay=true', 'autoplay=false'));
		}
		
		title = $('<span/>').text(title).append(audio);
	}
	
	var dialog = create_dialog(title, 'pronunciation_guide');
	show_template_dialog(dialog, 'pronunciation_guide_' + alphabet + "_" + language, callback);
}

function show_sources()
{
	var main_word = get_main_word();
	var words = get_words(document);
	var texts = [];
	for (var i = 0; i < words.length; i++)
	{
		texts.push(words[i].text);
	}
	$.post('report_error.php?get_wikipages', {words: texts.join('|||')}, function(wikipages) { show_sources_callback(main_word, words, wikipages) }, 'json');
}

function show_sources_callback(main_word, words, wikipages)
{
	var content = $('<ul style="max-height: 600px; overflow-y: scroll;" />');
	var wikis = ['en', 'fi', 'fr', 'sv'];
	var have_main_word = false;
	
	function li(word, wiki)
	{
		var li = $('<li style="margin-bottom: 10px;" />');
		var a = $('<a />');
		
		a.attr('href', 'http://' + wiki + '.wiktionary.org/wiki/' + encodeURIComponent(word.text));
		a.text('http://' + wiki + '.wiktionary.org/wiki/' + word.text);
		li.append(a);
		return li;
	}
	
	for (var i = 0; i < words.length; i++)
	{
		for (var j = 0; j < wikis.length; j++)
		{
			wikipage = wikipages[words[i].text];
			
			if (wikipage && wikipage.indexOf(wikis[j]) != -1)
			{
				content.append(li(words[i], wikis[j]));
			}
		}
	}
	
	var dialog = create_dialog(ui_phrases['SOURCES']);
	show_dialog(dialog, content, function() { hide_dialog(dialog); return false; });
}

function play_audio(anchor)
{
	var url = anchor.href;
	$(anchor).replaceWith($('<object style="margin-right: 5px; margin-bottom: -3px;" type="application/x-shockwave-flash" data="http://static2.sanakirja.org/sanakirja/player.swf" height="17" width="17"><param name="movie" value="http://static2.sanakirja.org/sanakirja/player.swf"><param name="flashVars" value="song_url=' + url + '&autoplay=true"><param name="wmode" value="transparent">Ääninäyte</object>'));
	_gaq.push(['_trackEvent', 'sanakirja', 'audio', anchor.getAttribute('rel')]);
	
	return false;
}

function init_error_report()
{
	var report_error_link = document.getElementById('report_error');
	
	if (report_error_link)
	{
		report_error_link.onclick = function()
		{
			show_report_error();
			return false;
		}
	}
	
	if (location.href.indexOf('#showreports') != -1)
	{
		show_report_error();
	}
}

function show_report_error()
{
	$.getScript('http://static2.sanakirja.org/sanakirja/error_report.js', function()
	{
		show_report_error_form();
	});
}

function init_user()
{
	if (location.href.indexOf('sanakirja.org/') != -1)
	{
		USER_URL = SK.base_href + 'user.php?jsoncallback=?';
		init_token_refresh();
	}
}

String.prototype.lcfirst = function()
{
    return this.charAt(0).toLowerCase() + this.substr(1);
};

String.prototype.ucfirst = function()
{
    return this.charAt(0).toUpperCase() + this.substr(1);
};

function global_onunload()
{
	
}

function focus(element)
{
	if ((window.pageYOffset && window.pageYOffset != 0) || (document.body.scrollTop && document.body.scrollTop != 0))
	{
		return;
	}
	
	if (typeof element.hasFocus != 'function' || !element.hasFocus())
	{
		element.focused = false;

		element.hasFocus = function()
		{
			return this.focused;
		};

		element.onfocus=function()
		{
			this.focused = true;
		};

		element.onblur=function()
		{
			this.focused = false;
		};
		
		element.focus();
		
		if (typeof element.select == 'function' || navigator.appName == "Microsoft Internet Explorer")
		{
			element.select();
		}
	}
}

function get_language_by_id(id)
{
	if (language_names_fi[id])
	{
		return language_names_fi[id];
	}
	
	return 'Tuntematon kieli';
}

language_names_fi = new Array();
language_names_fi[1] = 'Bulgaria';
language_names_fi[2] = 'Viro';
language_names_fi[3] = 'Englanti';
language_names_fi[4] = 'Espanja';
language_names_fi[5] = 'Esperanto';
language_names_fi[6] = 'Italia';
language_names_fi[7] = 'Kreikka';
language_names_fi[8] = 'Latina';
language_names_fi[9] = 'Latvia';
language_names_fi[10] = 'Liettua';
language_names_fi[11] = 'Norja';
language_names_fi[12] = 'Portugali';
language_names_fi[13] = 'Puola';
language_names_fi[14] = 'Ranska';
language_names_fi[15] = 'Ruotsi';
language_names_fi[16] = 'Saksa';
language_names_fi[17] = 'Suomi';
language_names_fi[18] = 'Tanska';
language_names_fi[19] = 'Tsekki';
language_names_fi[20] = 'Turkki';
language_names_fi[21] = 'Unkari';
language_names_fi[22] = 'Venäjä';
language_names_fi[23] = 'Hollanti';
language_names_fi[24] = 'Japani';

language_names_to = [
	'',
	'bulgariaksi',
	'viroksi',
	'englanniksi',
	'espanjaksi',
	'esperantoksi',
	'italiaksi',
	'kreikaksi',
	'latinaksi',
	'latviaksi',
	'liettuaksi',
	'norjaksi',
	'portugaliksi',
	'puolaksi',
	'ranskaksi',
	'ruotsiksi',
	'saksaksi',
	'suomeksi',
	'tanskaksi',
	'tsekiksi',
	'turkiksi',
	'unkariksi',
	'venäjäksi',
	'hollanniksi',
	'japaniksi'
];

language_names_to.slice(1);

isocodes = new Array();
isocodes[1] = 'bg';
isocodes[2] = 'et';
isocodes[3] = 'en';
isocodes[4] = 'es';
isocodes[5] = 'eo';
isocodes[6] = 'it';
isocodes[7] = 'el';
isocodes[8] = 'la';
isocodes[9] = 'lv';
isocodes[10] = 'lt';
isocodes[11] = 'no';
isocodes[12] = 'pt';
isocodes[13] = 'pl';
isocodes[14] = 'fr';
isocodes[15] = 'sv';
isocodes[16] = 'de';
isocodes[17] = 'fi';
isocodes[18] = 'da';
isocodes[19] = 'cs';
isocodes[20] = 'tr';
isocodes[21] = 'hu';
isocodes[22] = 'ru';
isocodes[23] = 'nl';

function fancy_text_input(size, attributes, defaultText)
{
	var div = document.createElement('div');
	div.setAttribute('class', 'text_input_wrap ' + size);
	
	var input = textInput = document.createElement('input');
	input.setAttribute('type', 'text');
	input.setAttribute('class', 'fancy_' + size);
	
	if (attributes)
	{
		for (var i in attributes)
		{
			if (i == 'value' && !defaultText)
			{
				input.value = attributes[i];
			}
			else
			{
				input.setAttribute(i, attributes[i]);
			}
		}
	}
	
	if (defaultText)
	{
		input.value = defaultText;
		$(input).attr('defaultText', defaultText);
		
		$(input).focus(function()
		{
			if ($(this).val() == defaultText)
			{
				$(this).val('');
			}
		});
		
		$(input).blur(function()
		{
			if ($(this).val() == '')
			{
				$(this).val(defaultText);
			}
		});
	}
	
	div.appendChild(input);
	return div;
}

function init_login()
{
	if (SK.session)
	{
		return;
	}
	
	$(document.body).append('\
	<div id="login_register" class="dialog"> \
		<div class="dialog-content">\
			<img src="http://static2.sanakirja.org/sanakirja_img/cross.png" class="close" alt="" />\
			<div id="login">\
				<h2>Kirjaudu sisään</h2>\
				<form action="/user.php" method="post">\
				<input type="hidden" name="tkn" value="' + SK['form_token_str'] + '" /><input type="hidden" name="tkn_t" value="' + SK['form_token_time'] + '" />\
				<dl>\
					<dt><label for="username">Käyttäjätunnus</label></dt> \
					<dd>\
						<div class="text_input_wrap normal"><input type="text" class="text fancy_normal" name="username" id="username" /></div> \
					</dd>\
					<dt><label for="password">Salasana</label></dt> \
					<dd>\
						<div class="text_input_wrap normal"><input type="password" class="text fancy_normal" name="password" id="password" /></div> \
					</dd>\
					<dd>\
						<input type="submit" value="" name="login" class="fancy button login" />\
					</dd> \
				</dl>\
				</form> \
			</div>\
			<div id="register"> \
				<h2>Rekisteröidy</h2>\
				<form action="/user.php" method="post">\
				<input type="hidden" name="tkn" value="' + SK['form_token_str'] + '" /><input type="hidden" name="tkn_t" value="' + SK['form_token_time'] + '" />\
				<dl>\
					<dt><label for="username_register">Käyttäjätunnus</label></dt>\
					<dd>\
						<div class="text_input_wrap normal"><input type="text" class="text fancy_normal" name="username" id="username_register" /></div>\
					</dd>\
					<dt><label for="email">Sähköpostiosoite</label></dt>\
					<dd>\
						<div class="text_input_wrap normal"><input type="text" class="text fancy_normal" name="email" id="email" /></div>\
					</dd>\
					<dt><label for="password_register">Salasana</label></dt>\
					<dd>\
						<div class="text_input_wrap normal"><input type="password" class="text fancy_normal" name="password" id="password_register" /></div>\
					</dd>\
					<dt><label for="password_confirm">Salasana uudelleen</label></dt>\
					<dd>\
						<div class="text_input_wrap normal"><input type="password" class="text fancy_normal" name="password_confirm" id="password_confirm" /></div>\
					</dd>\
					<dd>\
						<input type="submit" value="" name="register" class="fancy button register" />\
					</dd>\
				</dl>\
				</form>\
			</div>\
		</div>\
		<div class="dialog-footer">\
			<a href="" class="register">Luo tunnus</a>\
			<a href="" class="login">Kirjaudu sisään</a>\
		</div>\
	</div>');
	
	$('#header a.login').click(function()
	{
		if (SK['form_token_str'] == 'null')
		{
			load_form_tokens();
		}
		
		$('#login_register').show();
		hide_flash();
		login_link.click();
		
		/*
		$.getScript('http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php/fi_FI', function()
		{
			FB.init("8df91f3c1289d8c37d2441fa5d661352", "/xd_receiver.htm");
		});
		*/
		$('#login_register input[type="text"]').first().select();
		
		return false;
	});
	
	var register_link = $('div.dialog-footer a.register');
	var login_link = $('div.dialog-footer a.login');
	
	register_link.click(register_click_handler);
	login_link.click(login_click_handler);
	
	login_link.hide();
	
	$('div.dialog img.close').click(function()
	{
		$(this).closest('div.dialog').hide();
		show_flash();
	});
	
	$('#login form').submit(function()
	{
		loading_overlay($('#login form'));
		$.post(USER_URL, $(this).serialize() + '&login=', login_callback, 'jsonp');
		dialog_hide_message($(this));
		
		return false;
	});
	
	$('#register form').submit(function()
	{
		loading_overlay($('#register form'));
		$.post(USER_URL, $(this).serialize() + '&register=', register_callback, 'jsonp');
		dialog_hide_message($(this));
		
		return false;
	});
	
	$(document.body).click(function(event)
	{
		var parent_dialog = $(event.target).parents('div.dialog');
		
		if (parent_dialog.length == 0 && register_link.css('display') == 'block')
		{
			$('#login_register').hide();
			show_flash();
		}
	});
	
	$(document).keydown(function(e)
	{
		if( e.which == 27)
		{
			$('#login_register').hide();
			show_flash();
		}
	});
}

function register_click_handler()
{
	var register_link = $('div.dialog-footer a.register');
	var login_link = $('div.dialog-footer a.login');
	
	$('#login_register').show();
	hide_flash();
	
	$('#login').hide();
	$('#register').show();
	$('#register input').first().select();
	
	register_link.hide();
	login_link.show();
	scroll_into_view($('#login_register'));
	
	return false;
}

function login_click_handler()
{
	var register_link = $('div.dialog-footer a.register');
	var login_link = $('div.dialog-footer a.login');
	
	$('#login_register').show();
	hide_flash();
	
	$('#register').hide();
	$('#login').show();
	$('#login input').first().select();
	
	register_link.show();
	login_link.hide();
	scroll_into_view($('#login_register'));
	
	return false;
}

function login_callback(response)
{
	$('#login form').find('div.loading-overlay').hide();
	response = response.text;
	
	if (ui_phrases[response])
	{
		var submit = $('#login input[type="submit"]');
		dialog_show_message(submit, ui_phrases[response], 'error');
		return false;
	}
	
	location.href = location.href.replace(/sid=[a-z0-9]+/, '').replace(/#.*/, '');
}

function register_callback(response)
{
	$('#register form').find('div.loading-overlay').hide();
	response = response.text;
	
	if (ui_phrases[response])
	{
		var submit = $('#register input[type="submit"]');
		dialog_show_message(submit, ui_phrases[response], 'error');
		return false;
	}
	
	location.href = 'http://' + location.host + '/';
}

function dialog_show_message(element, message, clazz)
{
	var dialog = element.closest('div.dialog');
	
	if (dialog.css('display') == 'none')
	{
		dialog.show();
		scroll_into_view(dialog);
	}
	
	element.next('p.message').remove();
	element.after('<p class="' + clazz + ' message">' + message + '</p>').next('p').hide().fadeIn();
	
	if (element.is('input') && element.attr('type') == 'text')
	{
		element.select();
	}
	else
	{
		element.closest('form').children('input[type="text"]').first().select();
	}
}

function dialog_hide_message(element)
{
	if (!element.hasClass('dialog'))
	{
		element = element.closest('div.dialog');
	}
	
	element.find('p.message').remove();
}

function scroll_into_view(element)
{
	if (is_fully_visible(element))
	{
		return;
	}
	
	$('html, body').animate({
		scrollTop: element.offset().top
	}, 1000);
}

function is_fully_visible(elem)
{
    var docViewTop = $(window).scrollTop();
    var docViewBottom = docViewTop + $(window).height();

    var elemTop = $(elem).offset().top;
    var elemBottom = elemTop + $(elem).height();

    return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom) && (elemTop >= docViewTop));
}

function format_date(date)
{
	var month = date.getMonth() + 1;
	if (month < 10)
	{
		month = "0" + month;
	}
	
	var day = date.getDate();
	if (day < 10)
	{
		day = "0" + day;
	}
	
	return day + "." + month + "." + date.getFullYear();
}

/****************************/
/****** COOKIE.JS ***********/
/****************************/

/**
 * Copyright (C) 2002-2003, CodeHouse.com. All rights reserved.
 * CodeHouse(TM) is a registered trademark.
 *
 * THIS SOURCE CODE MAY BE USED FREELY PROVIDED THAT
 * IT IS NOT MODIFIED OR DISTRIBUTED, AND IT IS USED
 * ON A PUBLICLY ACCESSIBLE INTERNET WEB SITE.
 * 
 * CodeHouse.com JavaScript Library Module: Cookie Utility Class
 *
 * You can obtain this script at http://www.codehouse.com
 */
function CJL_CookieUtil(name, duration, path, domain, secure)
{
   this.affix = "";
   
   if( duration )
   {   	  
      var date = new Date();
	  var curTime = new Date().getTime();

	  date.setTime(curTime + (1000 * 60 * duration));
	  this.affix = "; expires=" + date.toGMTString();
   }
   
   if( path )
   {
      this.affix += "; path=" + path;
   }
   
   if( domain )
   {
      this.affix += "; domain=" + domain;
   }

   if( secure )
   {
      this.affix += "; secure=" + secure;
   }
   
      
   function getValue()
   {
      var m = document.cookie.match(new RegExp("(" + name + "=[^;]*)(;|$)"));

      return m ? m[1] : null;   
   }
   
   this.cookieExists = function()
   {
      return getValue() ? true : false;
   }
   
   this.value = function()
   {
      return getValue();
   }
      
   this.expire = function()
   {
      var date = new Date();
	  date.setFullYear(date.getYear() - 1);
	  document.cookie=name + "=noop; expires=" + date.toGMTString(); 
   }
        
   this.setSubValue = function(key, value)
   {
      var ck = getValue();

      if( /[;, ]/.test(value) )
      {
         //Mac IE doesn't support encodeURI
		 value = window.encodeURI ? encodeURI(value) : escape(value);
      }

      
      if( value )
      {
         var attrPair = "s" + key + value;

         if( ck )
         {
             if( new RegExp("s" + key).test(ck) )
	         {
		        document.cookie =
				   ck.replace(new RegExp("s" + key + "[^s;]*"), attrPair) + this.affix;
	         }
	         else
	         {
		        document.cookie =
				   ck.replace(new RegExp("(" + name + "=[^;]*)(;|$)"), "$1" + attrPair) + this.affix;
	         }
         }
         else
         {
	        document.cookie = name + "=" + attrPair + this.affix;
         }
      }
      else
      {      
	     if( new RegExp("s" + key).test(ck) )
	     {
	        document.cookie = ck.replace(new RegExp("s" + key + "[^s;]*"), "") + this.affix;
	     }
      }
   }
   
   this.getSubValue = function(key)
   {
      var ck = getValue();

      if( ck )
      {
         var m = ck.match(new RegExp("s" + key + "([^s;]*)"));

	     if( m )
	     {
	        var value = m[1];

	        if( value )
	        { 
	           //Mac IE doesn't support decodeURI
			   return window.decodeURI ? decodeURI(value) : unescape(value);
	        }
	     }
      }
   }
}

var pikavalinnat_cookie = new CJL_CookieUtil("sanakirja_pikavalinnat", (60*24*30*12), '/', cookie_domain);

function track_click(url)
{
	return;
	
	if (typeof urchinTracker == 'function')
	{
		urchinTracker(url);
	}
}

function basename(url)
{
	if (url.indexOf('http://') == 0 && url.lastIndexOf('/') > 8)
	{
		return url.substring(url.lastIndexOf('/') + 1, url.length);
	}
}

function css(url)
{
	$('<link/>').appendTo('head').attr({rel: 'stylesheet', type: 'text/css', href: url});
}

function js(url, callback)
{
	$.getScript(url, callback);
}

function show_cookies_disclaimer()
{
	var content = $('<p>Käyttäjän tietokoneelle voidaan ajoittain siirtää evästeitä ("cookies"). Eväste on pieni tekstitiedosto, jonka avulla voidaan kerätä tietoja siitä, miten ja milloin palvelujamme käytetään: esimerkiksi miltä sivulta käyttäjä on siirtynyt palveluun, milloin ja mitä www-sivujamme käyttäjä on selannut, kenen mainostajan ilmoituksia käyttäjä on sivullamme nähnyt ja klikannut, mitä selainta käyttäjä käyttää, mikä on käyttäjän näytön resoluutio ja käyttöjärjestelmä sekä mikä on käyttäjän tietokoneen IP-osoite. Evästeet ei vahingoita käyttäjän tietokonetta tai tiedostoja.</p><p>Evästeiden käytön tarkoituksena on analysoida ja kehittää palveluamme edelleen käyttäjiä paremmin palveleviksi. Lisäksi Sanakirja.org ja sen yhteistyökumppanit voivat hyödyntää evästeitä palvelun kävijämäärien tilastollisessa seurannassa sekä mitatakseen mainonnan tehoa. Sanakirja.org ja sen yhteistyökumppanit voivat käyttää evästeiden avulla kerättyä tietoa myös kohdennetun mainonnan tuottamiseen.</p><p>Lähtökohtaisesti käyttäjiä ei tunnisteta evästeiden avulla. Yksityisyyden suojaa koskeva lainsäädäntö huomioon ottaen voidaan evästeen avulla saatua tietoa kuitenkin myös liittää käyttäjältä mahdollisesti muussa yhteydessä saatuihin henkilötietoihin.</p><p>Käyttäjällä on mahdollisuus estää evästeiden käyttö muuttamalla selaimensa asetuksia. Evästeiden käytön estäminen saattaa vaikuttaa palvelumme toiminnallisuuteen.</p>');
	var dialog = create_dialog("Evästeet");
	show_dialog(dialog, content, function() { hide_dialog(dialog); return false; });
	scroll_into_view(dialog);
	return false;
}

function loading_overlay(element)
{
	var overlay = element.find('div.loading-overlay');
	if (!overlay.length)
	{
		overlay = $('<div class="loading-overlay" />').prependTo(element);
	}
	
	overlay.css('position', 'absolute');
	overlay.css('width', element.outerWidth());
	overlay.css('height', element.outerHeight());
	overlay.show();
}

function init_ajax()
{
	var x;
	
	try
	{
		x=new ActiveXObject("Msxml2.XMLHTTP");
	}
	catch (e)
	{
		try
		{
			x=new ActiveXObject("Microsoft.XMLHTTP");
		}
		catch (oc)
		{
			x=null;
		}
	}
	
	if(!x && typeof XMLHttpRequest != "undefined")
	{
		x = new XMLHttpRequest();
	}
	
	if (x)
	{
		return x;
	}
}

$(document).ready(global_onload);
window.onunload = global_onunload;

function AjaxConnection(uri)
{
	this.options = 'ajax=1';
	this.setOptions = setOptions;
	this.getOptions = getOptions;
	this.connect = connect;
	this.buildFromForm = buildFromForm;
	this.uri = uri;
	
	function setOptions(opt)
	{
		for(i=0; i < opt.length; i++)
		{
			this.options += "&" + opt[i];
		}
	}

	function getOptions()
	{
		return this.options;
	}

	function connect(return_func)
	{
		with(this)
		{
			x=init_object();
			x.open("POST", uri,true);
			
			x.onreadystatechange = function()
			{
				if (x.readyState != 4)
				{
					return;
				}
				
				return_func(x.responseText);
				delete x;
			}
			
			x.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
			x.send(options);
		}
	}

	function init_object()
	{
		return init_ajax();
	}
	
	function buildFromForm(formObject)
	{
		var nodeTypes = ['input', 'select', 'textarea'];
		var options = new Array();
		
		for (var n in nodeTypes)
		{
			var nodes = formObject.getElementsByTagName(nodeTypes[n]);
			
			for (var i = 0; i < nodes.length; i++) 
			{
				var value = nodes[i].value;
				if (value && nodes[i].getAttribute('defaultText') == value)
				{
					value = '';
				}
				
				if (nodes[i].name && value)
				{
					options.push(nodes[i].name + '=' + encodeURIComponent(value));
				}
			}
		}
		
		this.setOptions(options);
	}
}

if (navigator.userAgent.toLowerCase().indexOf('iphone') != -1)
{
	(function() { var currentWidth = 0;

			function checkOrientAndLocation()
			{
					/* Let's scoll only if we are so high on the screen that the iPhone bar can be visible. */
					y = window.pageYOffset;
					if (window.innerWidth != currentWidth && y < 100)
					{
							currentWidth = window.innerWidth;
							var orient = currentWidth == 320 ? "profile" : "landscape";
							myHeight = window.innerHeight;
							document.body.setAttribute("orient", orient);
							setTimeout(scrollTo, 100, 0, 1);
					}
			}

			addEventListener("load", function(event)
			{
					setTimeout(checkOrientAndLocation, 0);
					checkTimer = setInterval(checkOrientAndLocation, 300);
			}, false);
	})();
}

function show_alert(title, text)
{
	var dialog = create_dialog(title);
	
	var submit = function()
	{
		dialog.remove();
		return false;
	}
	
	show_dialog(dialog, text, submit);
}

function create_dialog(title, className, onClose)
{
	var id = "dialog_" + ("" + Math.random()).substr(4);
	
	if (SK['form_token_str'] == 'null')
	{
		load_form_tokens();
	}
	
	$(document.body).append('<div id="' + id + '" class="dialog"> \
		<div class="dialog-content"> \
			<img src="http://static2.sanakirja.org/sanakirja_img/cross.png" class="close" alt="" /> \
			\
			<div> \
				<h2></h2> \
				<form action="/user.php" method="post"> \
				<dl></dl> \
				<input type="hidden" name="tkn" value="' + SK['form_token_str'] + '" /> \
				<input type="hidden" name="tkn_t" value="' + SK['form_token_time'] + '" /> \
				</form> \
			</div> \
		</div> \
		<div class="dialog-footer" style="height: 23px;"></div> \
	</div>');
	
	var dialog = $('#' + id);
	if (className)
	{
		dialog.addClass(className)
	}
	
	dialog.find('h2').append(title);
	
	if (onClose)
	{
		dialog.bind('close', onClose);
	}
	
	return dialog;
}

function show_template_dialog(dialog, template, callback, dialog_callback, modal)
{
	show_dialog(dialog, ' ', false, modal);
	loading_overlay(dialog);
	
	get(USER_URL, {'template': template}, function(data) {
		dialog.find('div.loading-overlay').hide();
		show_dialog(dialog, $(data.text), dialog_callback, modal);
		callback();
	});
}

function show_dialog(dialog, fields, callback, modal)
{
	$('div.dialog').each(function()
	{
		if (dialog[0] != this && this.id != 'login_register')
		{
			hide_dialog($(this));
		}
	});
	
	if (typeof(fields) == "string")
	{
		dialog.find('dl').empty();
		dialog.find('dl').prepend('<p>' + fields + '</p>');
	}
	else if (typeof(fields) == 'object' && fields.is)
	{
		dialog.find('form').empty();
		dialog.find('form').prepend(fields);
	}
	else
	{
		dialog.find('dl').empty();
		
		for (i in fields)
		{
			var field = fields[i];
			
			dialog.find('dl').append('\
				<dt><label for="' + dialog.attr('id') + field.name + '">' + field.title + '</label></dt> \
				<dd> \
					<div class="text_input_wrap normal"><input type="' + field.type + '" class="text fancy_normal" name="' + field.name + '" id="' + dialog.attr('id') + field.name + '" /></div> \
				</dd>');
		}
	}
	
	var dl = dialog.find('dl');
	if (dl.length == 0)
	{
		dl = $('<dl />').appendTo(dialog.find('form'));
	}
	
	dl.append('<dt class="submit"></dt><dd class="submit"><input type="submit" value="" name="" class="fancy button ok" /></dd>');
	
	dialog.find('img.close').click(function()
	{
		hide_dialog(dialog);
	});
	
	if (!modal)
	{
		$(document.body).click(function(event)
		{
			var parent_dialog = $(event.target).parents('div.dialog');
			
			if (parent_dialog.length == 0)
			{
				hide_dialog(dialog);
			}
		});
	}
	
	$(document).keydown(function(e)
	{
		if( e.which == 27)
		{
			hide_dialog(dialog);
		}
	});
	
	if (callback)
	{
		dialog.find('form').submit(callback);
	}
	else
	{
		dialog.find('dl > dd.submit').remove();
	}
	
	hide_flash();
	dialog.css('display', 'block');
	if (typeof dialog.find('input').first == 'function' && typeof dialog.find('input').first().select == 'function')
	{
		dialog.find('input').first().select();
	}
	
	scroll_into_view(dialog);
	
	return dialog;
}

function hide_dialog(dialog)
{
	dialog.trigger('close');
	dialog.remove();
	show_flash();
}

function get(url, parameters, callback, element)
{
	ajax($.get, url, parameters, callback, element);
}

function post(url, parameters, callback, element)
{
	ajax($.post, url, parameters, callback, element);
}

function ajax(method, url, parameters, callback, element)
{
	if (element)
	{
		loading_overlay(element);
	}
	
	if (url == USER_URL && (!parameters['tkn'] || !parameters['tkn_t']))
	{
		parameters['tkn'] = SK['form_token_str'];
		parameters['tkn_t'] = SK['form_token_time'];
	}
	
	method(url, parameters, function(data)
	{
		callback(data);
		
		if (element)
		{
			element.find('div.loading-overlay').hide();
		}
	}, 'jsonp');
}

function attach_tooltip(element, text)
{
	var xOffset = 10
	var yOffset = 20;
	
	element.hover(function(e)
	{
		$('body').append('<p id="tooltip">'+ text +'</p>');
		
		$('#tooltip').css('top',(e.pageY - xOffset) + 'px')
			.css('left',(e.pageX + yOffset) + 'px')
			.fadeIn('fast');
    },
	function()
	{
		$("#tooltip").remove();
    });
};

/**
* hoverIntent is similar to jQuery's built-in "hover" function except that
* instead of firing the onMouseOver event immediately, hoverIntent checks
* to see if the user's mouse has slowed down (beneath the sensitivity
* threshold) before firing the onMouseOver event.
* 
* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
* 
* hoverIntent is currently available for use in all personal or commercial 
* projects under both MIT and GPL licenses. This means that you can choose 
* the license that best suits your project, and use it accordingly.
* 
* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
* $("ul li").hoverIntent( showNav , hideNav );
* 
* // advanced usage receives configuration object only
* $("ul li").hoverIntent({
*	sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
*	interval: 100,   // number = milliseconds of polling interval
*	over: showNav,  // function = onMouseOver callback (required)
*	timeout: 0,   // number = milliseconds delay before onMouseOut function call
*	out: hideNav    // function = onMouseOut callback (required)
* });
* 
* @param  f  onMouseOver function || An object with configuration options
* @param  g  onMouseOut function  || Nothing (use configuration options object)
* @author    Brian Cherne <brian@cherne.net>
*/
(function($) {
	$.fn.hoverIntent = function(f,g) {
		// default configuration options
		var cfg = {
			sensitivity: 7,
			interval: 100,
			timeout: 0
		};
		// override configuration options with user supplied object
		cfg = $.extend(cfg, g ? { over: f, out: g } : f );

		// instantiate variables
		// cX, cY = current X and Y position of mouse, updated by mousemove event
		// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
		var cX, cY, pX, pY;

		// A private function for getting mouse position
		var track = function(ev) {
			cX = ev.pageX;
			cY = ev.pageY;
		};

		// A private function for comparing current and previous mouse position
		var compare = function(ev,ob) {
			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
			// compare mouse positions to see if they've crossed the threshold
			if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
				$(ob).unbind("mousemove",track);
				// set hoverIntent state to true (so mouseOut can be called)
				ob.hoverIntent_s = 1;
				return cfg.over.apply(ob,[ev]);
			} else {
				// set previous coordinates for next time
				pX = cX; pY = cY;
				// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
				ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
			}
		};

		// A private function for delaying the mouseOut function
		var delay = function(ev,ob) {
			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
			ob.hoverIntent_s = 0;
			return cfg.out.apply(ob,[ev]);
		};

		// A private function for handling mouse 'hovering'
		var handleHover = function(e) {
			// next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
			var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
			while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
			if ( p == this ) { return false; }

			// copy objects to be passed into t (required for event object to be passed in IE)
			var ev = jQuery.extend({},e);
			var ob = this;

			// cancel hoverIntent timer if it exists
			if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }

			// else e.type == "onmouseover"
			if (e.type == "mouseover") {
				// set "previous" X and Y position based on initial entry point
				pX = ev.pageX; pY = ev.pageY;
				// update "current" X and Y position based on mousemove
				$(ob).bind("mousemove",track);
				// start polling interval (self-calling timeout) to compare mouse coordinates over time
				if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}

			// else e.type == "onmouseout"
			} else {
				// unbind expensive mousemove event
				$(ob).unbind("mousemove",track);
				// if hoverIntent state is true, then call the mouseOut function after the specified delay
				if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
			}
		};

		// bind the function to the two event listeners
		return this.mouseover(handleHover).mouseout(handleHover);
	};
})(jQuery);

$.fn.textNodes = function(ignore)
{
	return $(this).contents().filter(function(){ return (this.nodeType == 3 && (!ignore || ignore.indexOf(this.textContent.trim()) == -1)); });
}

/**
 * jQuery.fn.sortElements
 * --------------
 * @param Function comparator:
 *   Exactly the same behaviour as [1,2,3].sort(comparator)
 *   
 * @param Function getSortable
 *   A function that should return the element that is
 *   to be sorted. The comparator will run on the
 *   current collection, but you may want the actual
 *   resulting sort to occur on a parent or another
 *   associated element.
 *   
 *   E.g. $('td').sortElements(comparator, function(){
 *      return this.parentNode; 
 *   })
 *   
 *   The <td>'s parent (<tr>) will be sorted instead
 *   of the <td> itself.
 */
jQuery.fn.sortElements = (function(){
 
    var sort = [].sort;
 
    return function(comparator, getSortable) {
 
        getSortable = getSortable || function(){return this;};
 
        var placements = this.map(function(){
 
            var sortElement = getSortable.call(this),
                parentNode = sortElement.parentNode,
 
                // Since the element itself will change position, we have
                // to have some way of storing its original position in
                // the DOM. The easiest way is to have a 'flag' node:
                nextSibling = parentNode.insertBefore(
                    document.createTextNode(''),
                    sortElement.nextSibling
                );
 
            return function() {
 
                if (parentNode === this) {
                    throw new Error(
                        "You can't sort elements if any one is a descendant of another."
                    );
                }
 
                // Insert before flag:
                parentNode.insertBefore(this, nextSibling);
                // Remove flag:
                parentNode.removeChild(nextSibling);
 
            };
 
        });
 
        return sort.call(this, comparator).each(function(i){
            placements[i].call(getSortable.call(this));
        });
 
    };
 
})();

