/**
 * WebTV Core javascript library.
 * 
 * Version 3.10.525
 * 
 * History:
 * 	3.10.525:	v3 generic round-up, ready to fly.
 * 
 * 	1.10.517:	Fixed failure when optional args argument was not passed to navigate_to()
 * 
 * 	1.10.511:	Fixed: load_shadowbox() did not provide current state.
 * 
 * 	1.10.505:	Minor rewrite to navigate_to to support replacing current location.
 * 
 * 	1.10.505:	Misc. bugfixing and cleanup.
 * 
 * 	1.10.426:	Killed post_render_hooks. Using live() and livequery instead.
 * 
 * 	1.10.420:	Wrapped post-document-ready shadowboxes in a setTimeout event, as it
 * 				seems Shadowbox barfs otherwise... Hmmm...
 * 
 * 	1.10.419:	IE6 PNG fixing has been removed from the core script and added as a post
 * 				render hooks via theme_init.js files on v1 and v2 based themes, both
 * 				generic and enterprise. No more IE6 support in v3+.
 * 				
 * 	1.10.415:	Well, the Shadowbox update in v1.9.1015 brought with it an interesting
 * 				side-effect. The fact that script blocks inside the Shadowbox content is
 * 				now evaluated on some browsers (ATM only FF AFAICT, and this might not
 * 				have to do with the Shadowbox update at all but rather with a FF update),
 * 				meant that some shadowboxes created unexpected results, such as the email
 * 				subscription box which would submit twice and give weird messages to the
 * 				user. A check before hacking has been made, adding a Shadowbox variable,
 * 				"Shadowbox.scriptsHaveEvaluated", which we attempt to set to true
 * 				inside the Shadowbox content (see load_shadowbox()). In
 * 				Shadowbox.onFinish(), if this variable is seen to be not false, we know
 * 				that script blocks inside the loaded content *have* been evaluated, and
 * 				we don't apply the hack.
 * 				
 * 				Comment approval and subscribe/unsubscribe shadowboxes are now triggered
 * 				in javascript on document ready state rather than in the main template.
 * 				
 * 				Some form ajaxification bugs have been fixed, the post_render_hooks thing
 * 				now does pretty much all DOM manipulation, Shadowboxes can be closed with
 * 				ESC, and much more. See function histories for more details.
 * 	
 * 	1.10.315:	Introduced post rendering hooks. Addded functions here will be executed
 * 				whenever the page or a template is loaded.
 *  
 * 	1.9.1218:	navigate_to() simplified, now only rewrites relative urls and no longer
 * 				kills language tokens on the request path.
 *  
 * 	1.9.1217:	More iframe fixes, enabling proper crawling by 3rd party search engines.
 * 
 * 	1.9.1027:	Fixed iframe bug with navigate_to().
 * 
 * 	1.9.1015:	Update to new Shadowbox major version 3. This one has a different setup,
 * 				and now only needs re-injection of script tags after content loading in
 * 				some browsers (e.g. IE and Opera). Hm. The event-based CSS hacking via 
 * 				the init object has been replaced (mostly) by a custom CSS which also
 * 				removed the need for the "mute" lang file. Also, this release is IE8 
 * 				compliant, so we can remove the meta hack.
 * 
 * (c) Copyright 2008-2010 Arkena A/S
 * 
 * @author daniel@arkena.dk
 */




function trigger_error(message)
{
	if (execution_mode != 'production')
		alert('Core Error: '+message);
}




/**
 * Scan the DOM/scope for links and process these to make sure they work with iframed frontends,
 * by prepending the site_root_url base, which routes to the location of the iframe embed logic,
 * and making sure links are opened in the parent, not in the iframe.
 * 
 * We fix relative urls and urls which are explicitly targeted at the Arkena site's hostname.
 * 
 * Formerly named function, "iframify_elements(scope)".
 * 
 * Version 1.10.426
 * 
 * History:
 * 	1.10.426:	No longer uses post_render_hooks. No longer tags elements to keep track of already
 * 				processed elements. Now uses livequery for automagic processing of loaded elements.
 * 				
 * 	1.10.415:	Is now scoped for being post_render_hook'ed.
 */
if (iframed)
{
	// In case we came here via e.g. a search engine and are rendered without a parent, reload:
	if (parent === self)
		reload_page();
	
	$('a').livequery(function() {
		element = $(this);
		href = element.attr('href');
		
		// So, only links with hrefs that aren't anchors or javascript: or mailto: hrefs, get the treatment:
		if (!is_empty(href) && href.toLowerCase().substr(0, 11) != 'javascript:' && href.toLowerCase().substr(0, 7) != 'mailto:' && href.substr(0, 1) != '#')
		{
			target = element.attr('target');
			
			// Any target not _blank is changed to _parent...
			if (target != '_blank')
				element.attr('target', '_parent');
			
			// ...but only relative URLs are actually rewritten by prepending the iframe url:
			if (href.indexOf('://') == -1)
				element.attr('href', site_root_url+href);
		}
	});
	
	// Almost same deal with forms:
	$('form').livequery(function() {
		element = $(this);
		
		// Only forms that don't POST data (like the search form) are processed:
		if (element.attr('method').toLowerCase() != 'post')
		{
			href = element.attr('action');
			target = element.attr('target');
			
			// Any target not _blank is changed to _parent...:
			if (target != '_blank')
				element.attr('target', '_parent');
			
			// ...but only relative URLs are actually rewritten by prepending the iframe url:
			if (href.indexOf('://') == -1)
				element.attr('action', site_root_url+href);
		}
	});
}




/**
 * Extract template meta data packages dropped by the html template rendering engine
 * and forward their info to the appropriate template surrounding element.
 * 
 * After processing, droppers and blockers are removed from the DOM.
 * 
 * Consult the PHP renderering class documentation for further information.
 * 
 * Formerly named function, "catch_template_meta_data()".
 * 
 * Version 1.10.503
 * 
 * History:
 * 	1.10.503:	Added check for empty templates.
 * 	
 * 	1.10.426:	No longer uses post_render_hooks, or used explicitly. Now uses livequery
 * 				for automagic processing of loaded elements.
 * 
 */
// Any element with a title attribute containing 'wtvmetapackage' is targeted:
$('[title^=wtvmetapackage]').livequery(function() {
	// Copy the wtvmeta data from the data dropper element to the immediately following
	// element (this is by convention; all template contents must be wrapped inside an
	// outer bounding element). Make sure though, that something has actually been
	// rendered in the template, i.e. the next element cannot be the blocker div:
	if ($(this).next().attr('title') != 'wtvmetablocker')
		$(this).next().attr('wtvmeta', $(this).attr('title').substr(14));
	
	// Remove the first blocker div encountered when traversing:
	$(this).nextAll('[title^=wtvmetablocker]:first').remove();
	
	// Remove the meta data dropper itself from the DOM:
	$(this).remove();
});	




// Set up the page once the document is ready:
$(document).ready(function() {
	// We disable keys in Shadowbox init so that copy/paste and stuffw works, but we'd still like to be able
	// to close the Shadowboxes via ESC, so here's a function we can bind/unbind for that purpose:
	var close_shadowbox_on_esc = function(event)
	{
		if ((event.which == 27) || (event.keyCode == 27))
		{
			$(document).unbind('keypress', close_shadowbox_on_esc);
			Shadowbox.close();
		}
	};
	
	// Set up Shadowbox behavior:
	Shadowbox.init({
		players				: ['html'],
		showMovieControls	: false,
		displayNav			: false,
		enableKeys			: false,
		animate				: false,
		resizeDuration		: 0.15, // We've hidden the loading box via CSS, and we've said animate:false, so this really has no effect.
		fadeDuration		: 0.25,
		useSizzle			: false,
		onFinish			: function() {
			// #sb-wrapper content wrapper is hidden because Shadowbox places its box vertically centered.
			// This is bad news for iframed sites (window height is always oversized), so we've hidden the
			// wrapper via CSS and need to show it after we've repositioned it to a more safe location:
			$('#sb-wrapper').css('top', '100px').show();
			
			/* With shadowboxes, javascript inside their content is ignored on some browsers (most, actually,
			 * it seems only newer Firefoxes have started to actually evaluate script blocks here), presumably
			 * because the content is hidden while loaded or somehow outside the DOM? Either way, this little
			 * hack just reinserts every script block if the "Shadowbox.scriptsHaveEvaluated" is false 
			 * (a script block that assigns it a non-null value is appended to the content retrieved in 
			 * load_shadowbox(), which on browsers that don't evaluate it is ignored) and voila, javascript 
			 * code blocks are evaluated...
			 */
			if (!Shadowbox.scriptsHaveEvaluated)
			{
				$('#sb-content script').each(function() {
					$(this).html('<script type="'+$(this).attr('type')+'">'+$(this).html()+'</script>');
				});
			}
			// We reset to false regardless, as the hack above means that scriptsHaveEvaluated is now true:
			Shadowbox.scriptsHaveEvaluated = false;
			
			// Make the ESC key close the shadowbox:
			$(document).bind('keypress', close_shadowbox_on_esc);
			
			// Do we have any additional code to execute? onFinish() is fine for Shadowbox-specific and generic stuff,
			// but it is limiting that we cannot bind more functions to onFinish to do custom stuff like modifying the
			// completely rendered Shadowbox elements (e.g. for automating email subscriptions), so we've added an
			// onCompletion array which may contain functions or literal code to execute on completion:
			if (!is_empty(Shadowbox.onCompletion))
			{
				for (i in Shadowbox.onCompletion)
					if (typeof(Shadowbox.onCompletion[i]) == 'function')
						Shadowbox.onCompletion[i]($('#sb-content'));
					else
						eval(Shadowbox.onCompletion[i]);
				
				// Reset:
				Shadowbox.onCompletion = [];
			}
		}
	});
	// Se above in onFinish() for an explanation to these:
	Shadowbox.scriptsHaveEvaluated	= false;
	Shadowbox.onCompletion			= [];
	
	// Get request vars, and handle comment moderation and subscriptions if instructed
	// (TODO: Comment moderation needs to go to the backend)
	var request_vars = get_request_get_vars(location.href);
	
	// Beats me... Should work, but it seems the document isn't *that* ready. At least not for Shadowbox! Again with the shadowbox :)
	setTimeout(function() {
		// Comment moderation:
		if (!is_empty(request_vars['challenge_code']))
		{
			load_shadowbox({
				width		: 591,
				height		: 552,
				template	: 'shadowbox_comment_moderation.php',
				vars		: { redecide: request_vars['redecide'], challenge_code: request_vars['challenge_code'] }
			});
		}
		
		// If we have a subscribe or unsubscribe request and a challenge code on the request path, we trigger a shadowbox to handle it immediately:
		if (!is_empty(request_vars['subscribe']) || !is_empty(request_vars['unsubscribe']))
		{
			load_shadowbox({
				width		: 591,
				height		: 552,
				template	: 'shadowbox_'+(is_empty(request_vars['subscribe']) ? 'unsubscribe' : 'subscribe')+'_confirmation.php',
				// The replace regexp gets rid of "undefined" from the undefined var, and also trailing crap introduced by badly written email templates: 
				vars		: { challenge_code: (request_vars['subscribe']+request_vars['unsubscribe']).replace(/[^0-9]/g, '') }
			});
		}
	}, 1000);
	
	// If there's a query in the request, find all matching inputs and update them with this value:
	if (!is_empty(request_vars['q']))
		$('input[name=q]').val(request_vars['q']);
});




/**
 * Helps iframed sites with doing iframe-safe scripted navigation that goes through the embedding parent page,
 * while not having to worry about the internals. I.e. just provide a URL to this here baby and you always end
 * up doing the right thing. (With iframed sites this is also true for regular hrefs, btw., as they're "fixed"
 * automagically once encountered in the DOM.)  
 * 
 * E.g. when browsing for instance http://some.cool.tv having chosen English, navigate_to('/') will navigate to
 * http://some.cool.tv/en/. Similarly, if this site were iframe embedded, navigate_to('/search/') would send the
 * user to http://some.embedding.page/webtv.aspx?webtv=/en/search/.
 * 
 * Urls that aren't relative will produce undefined results.
 * 
 * Version 1.10.517
 * 
 * History:
 * 	1.10.517:	Fixed failure when no args argument was passed. Ooops... Thought it was optional? ;)
 * 
 * 	1.10.507:	Now takes args as optional argument, and supports replacing the current page rather than just
 * 				navigating to the URL provided.
 * 
 * 	1.9.1218:	Hmmm... Seems I was an idiot last time, as that approach stripped the language key token from the
 * 				url, too... And either way, having replaced the iframe link logic completely in version 1.9.1217,
 * 				we can now use much more simpler rules, as described above.
 * 
 * 	1.9.1027:	Hmmm... Seems we got all we need to strip the path in the location object :)
 * 				(http://www.xul.fr/javascript/tutorial/window-location.php)
 * 
 * @param	{string}	url							The url path to navigate to.
 * @param	{object}	args				(OPT)
 * 				{mixed}		.vars			(OPT)	An array or object with var/val pairs to pass on via GET.
 * 				{bool}		.replace_history(OPT)	If true, the target url will replace the current history
 * 													location rather than appending it to the history.
 */
function navigate_to(url, args)
{
	// Use empty object if no args provided:
	if (arguments.length < 2)
		args = {};
	
	// We produce a relative url by stripping away the current location protocol & host, if present, or the
	// site's absolute root url, if present, or the iframe parent base url, if present, then prepend whatever's
	// the appropriate root url (always different from current host for iframes, for instance) and open it in
	// the parent frame (which automagically resolves to our own frame if we're not iframed). We specifically do
	// NOT just strip away any protocol and host name, because it might be an external URL, and also, that wouldn't
	// strip away any tokens on the base path, such as the two-letter language designator:
	
	// Add/replace/remove GET vars:
	url = set_request_get_vars(url, args.vars);
	
	// Assemble the current host for use in a second:
	var current_host = window.location.protocol+'//'+window.location.host;
	
	if (url.substr(0, site_root_url.length) == site_root_url)		// Strip absolute site/iframe root url, if this is the prefix...
		url = site_root_url + url.substr(site_root_url.length);
	else if (url.substr(0, current_host.length) == current_host)	// Otherwise, if the link link is absolute and local, i.e. the
		url = site_root_url + url.substr(current_host.length);		// absolute current host root, strip that...
	else if (!is_empty(site_root_path) && url.substr(0, site_root_path.length) == site_root_path)	// Otherwise, if not absolute,
		url = site_root_url + url.substr(site_root_path.length);	// we strip the relative site root path (e.g. '/en/') and add prefix.
	
	// If no matches were found above but URL's relative, prefix with the site root url (for iframed sites particularly):
	if (url.indexOf('://') == -1)
		url = site_root_url + url;
	
	// Open the final URL in our parent, optionally replacing the item in the history object rather than appending:
	if (args.replace_history)
		parent.location.replace(url);
	else
		parent.location.href = url;
}




/**
 * Iframe safe page reload that takes optional vars to pass on to the next page as GETs.
 * 
 * @param	{mixed}		args												An array or object with args to pass on to navigate_to.
 */
function reload_page(args)
{
	navigate_to(window.location.href, args);
}




/**
 * Loads the specified template into a the specified element(s), passing on the .vars arguments members as GET parameters, if supplied.
 * 
 * If nothing overridden by the .vars argument, page request state is retained
 * (i.e. the state brought by the most recent page request).
 * 
 * Version 1.10.608
 * 
 * History:
 * 	1.10.608:	Well, stuff happens with the libs that you sometimes miss out on :) In the time since first
 * 				version, a .replaceWith() jQuery method has been added, so I replace all the old anchor code 
 * 				stuff that accomplished the same thing.
 * 	
 * 	1.10.505:	No longer merges vars with the request_state object. It is now the responsibility of
 * 				template designers to define state dependencies appropriately in the template configuration,
 * 				and to pass along any additional transient vars to load_template() and derivates.
 * 				
 * 	1.10.426:	No longer depends on post_render_hooks, as livequery is now used throughout for
 * 				automagical processing of loaded elements.
 * 				
 * 	1.9.1006:	Wrapped the vars object that is passed in object_without_null_members()
 * 				to remove all null members.
 * 
 * @param	{string}	args.template										The name of the template to load. Must be initialized in the
 * 																			frontend configuration.
 * @param	{mixed}		args.target									(OPT)	The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * 																			If omitted, the function will not inject the response, but
 * 																			you can still pass a function reference to args.method to
 * 																			process it externally.
 * @param	{mixed}		args.vars									(OPT)	Array of, or object with members representing, GET variables to
 * 																			add, replace, or remove to/in/from the generated request.
 * @param	{mixed}		args.method									(OPT)	How to process the retrieved data. If a string and one of
 * 																			'replace' (default), 'prepend', or 'append', tells how to
 * 																			inject the response data into the DOM relative to .target.
 * 																			If a function, this function will be called with the data
 * 																			being passed to it.
 * @return	{bool}															True if successful, false otherwise.
 */
function load_template(args)
{
	// Check arguments:
	if (is_empty(arguments) || !is_defined(args.template) || (is_defined(args.target) && is_empty(args.target = jquerify_element(args.target))))
	{
		trigger_error("load_template(): Missing or invalid arguments. I need to know at least the template ('template') and a valid target element to load into ('target').");
		return false;
	}
	
	$(document).ready(function()
	{
		// Load the template, passing along the request state object merged with any vars that we might have had passed to us:
		$.get('/html/'+args.template, object_without_null_members(args.vars), function(response)
		{
			// If we have no target(s) but have a method function, we just call that:
			if (is_empty(args.target) && args.method instanceof Function)
			{
				args.method(response);
			}
			// Otherwise, we inject/process the content as requested into all chosen targets:
			else args.target.each(function() {
				if (args.method instanceof Function)
					args.method(response);
				else
				{
					switch (args.method)
					{
						case 'prepend':
							$(this).before(response);
							break;
						case 'append':
							$(this).after(response);
							break;
						default: // 'replace'
							$(this).replaceWith(response);
							break;
					}
				}
			});
		});
	});
	
	return true;
}




/**
 * Reloads the template(s) rendered in the specified DOM target(s). This will only work for templates explicitly loaded through the core.
 * If a target itself is not a template, the DOM tree is conversed to find the nearest parent template. This way you can do stuff like,
 * onclick="reload_template({target:this});"
 * 
 * Version 1.9.1007
 * History:
 * 	1.9.1007: Added args.replace_vars (Note @ 1.10.505: This was only used briefly by the V2 theme, and has since been abandoned).
 * 
 * @param	{mixed}		args.target											The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * @param	{mixed}		args.vars									(OPT)	Array of, or object with members representing, GET variables
 * 																			to add, replace, or remove to/in/from the generated request.
 * @param	{bool}		args.replace_vars							(OPT)	If true, the args.var passed to this function  is not merged
 * 																			with the original vars from the meta package, but replaces
 * 																			them entirely.
 * 
 * @return	{bool}															True if successful, false otherwise.
 */
function reload_template(args)
{
	// Check arguments:
	if (is_empty(arguments) || !is_defined(args.target) || is_empty(args.target = jquerify_element(args.target)))
	{
		trigger_error("reload_template(): Missing or invalid arguments. You need to pass at least the target element ('target') containing the template to reload.");
		return false;
	}
	
	args.target.each(function()
	{
		// If the target itself does not contain a meta object, find the closest parent that has one:
		target = $(this).closest('*[wtvmeta]');
		
		var meta;
		
		if (target.length)
			meta = target.attr('wtvmeta');
		
		if (!is_empty(meta))
			eval('meta = ' + meta + ';');
		
		if (is_empty(meta) || typeof(meta) != 'object' || is_empty(meta.template))
		{
			trigger_error("reload_template(): Neither the specified target nor any parent of it contains the necessary meta information. Are you sure that '"+$(this).attr('id')+"' is the correct target, and that it contains or exists within a template that was loaded by the WebTV Core?");
			return false;
		}
		else // Reload the template:
		{
			// Wipe the existing meta vars if requested to do so:
			if (args.replace_vars == true)
				meta.vars = null;
			
			return load_template(merge_objects(meta, args, {target: target, method: 'replace'}));
		}
	});
	
	return true;
}




/**
 * Loads a registered template via AJAX and renders the content inside a Shadowbox.
 * 
 * Version 1.10.511
 * 
 * History:
 * 	1.10.511:	Added state vars to the request so that dependencies are met (clip, language, etc.).
 * 
 * 	1.10.415:	Appended the Shadowbox.scriptsHaveEvaluated assignment to retrieved content to disable the "script
 * 				blocks inside Shadowbox content not executed" workaround that no longer applies to all browsers.
 * 				Also added the optional on_completion argument to allow execution of custom script(s) upon complete
 * 				and ready shadowboxing. This code will be executed after the shadowbox has opened and all post render
 * 				hoooks have been executed.
 * 
 * @param	{string}	args.template				The name of the registered template to load.
 * @param	{int}		args.width					The pixel width of the Shadowbox to open.
 * @param	{int}		args.height					The pixel height of the Shadowbox to open.
 * @param	{mixed}		args.vars			(OPT)	Array of, or object with, members to merge with the current state
 * 													vars when requesting the template.
 * @param	{mixed}		args.on_completion	(OPT)	Literal code, function, or array of such to execute on final
 * 													completion of opening the Shadowbox.
 * 
 * @return	{bool}									False on invalid arguments, or whatever load_template() returns.
 */
function load_shadowbox(args)
{
	if (is_empty(arguments) || !is_defined(args.template) || !is_defined(args.width) || !is_defined(args.height))
	{
		trigger_error("load_shadowbox(): Missing or invalid arguments. You need to pass at least the name of the template to load ('template'), and the dimensions of the Shadowbox to inject it to ('width' and 'height').");
		return false;
	}
	
	// Load the template, adding transient data, merged with/overridden by any args that we might have had passed to us,
	// and open the returned content in a shadowbox.
	return load_template(merge_objects({vars:request_state}, args, {
		method: function(response)
		{
			// Got any on_completion stuff to do? If so, add it:
			if (!is_empty(args.on_completion))
			{
				if ($.isArray(args.on_completion))
					Shadowbox.onCompletion = Shadowbox.onCompletion.concat(args.on_completion);
				else
					Shadowbox.onCompletion.push(args.on_completion);
			}
			
			Shadowbox.open({
				player:		'html',
				// We add this script snippet to the response html to accomodate those browsers that actually do evaluate the
				// <script> elements that are injected into Shadowboxes (ATM only new Firefoxes):
				content:	response+'<script type="text/javascript">Shadowbox.scriptsHaveEvaluated = true;</script>',
				width:		args.width,
				height:		args.height
			});
		}
	}));
}




/**
 * Will turn a regular form, or forms, into AJAX form(s).
 * 
 * Version 1.10.415
 * 
 * History:
 * 	1.10.415:	When clear_texts_on_first_focus is requested, the clearing is now also applied onchange to accomodate
 * 				scripting input. Also, when marking input fields as erroneous after an unsuccessful submit, the scope
 * 				is now the form, not the entire DOM (oups!). Finally, a return value of false is added to the bound
 * 				click code for submit buttons so that programmatic submitting of the form does not result in a regular
 * 				submit instead of the AJAX one.
 * 
 * @param	{mixed}		args.target											The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * @param	{string}	args.action									(OPT)	Where to submit to. If omitted, the value of the form's
 * 																			"action" attribute will be used.
 * @param	{function}	args.on_submit								(OPT)	A function to call before submitting the form, after the form
 * 																			submission sequence has been initiated.
 * @param	{function}	args.on_response							(OPT)	A function to call after receiving a response from the server,
 * 																			with the server response object passed on.
 * @param	{function}	args.on_success								(OPT)	A function to call if receiving a success response from the
 * 																			server, with the server response object passed on.
 * @param	{function}	args.on_failure								(OPT)	A function to call if receiving a failure response from the
 * 																			server, with the server response object passed on.
 * @param	{function}	args.on_cancel								(OPT)	A function to call when the user clicks button(s) of type
 * 																			"reset" in the form.
 * @param	{bool}		args.track_input_states						(OPT)	Use this if you plan on changing input field values, e.g. to
 * 																			report back input errors to the user.
 * 																			The value, style, and class of each input field will be tracked
 * 																			and restored on field focus. Applies to <input>s and <textarea>s.
 * @param	{bool}		args.clear_texts_on_first_focus				(OPT)	If true, input fields will have their values cleared the first
 * 																			time they receive focus.
 * 																			Applies to <input>s and <textarea>s.
 * @param	{object}	args.post_data								(OPT)	Extra data to pass along with the data written in the form.
 * @return	{bool}															True on success, false on failure.
 */
function ajaxify_form(args)
{
	if (is_empty(arguments) || !is_defined(args.target) || is_empty(args.target = jquerify_element(args.target)))
	{
		trigger_error("ajaxify_form(): Not enough arguments provided. I need to know at least the DOM id of the form, or the form object itself, to ajaxify ('target').");
		return false;
	}
	
	args.target.each(function() {
		var form						= $(this);
		var action						= is_defined(args.action) ? args.action : $(form).attr('action');
		var submit_buttons				= form.find(":submit");
		var cancel_buttons				= form.find(":reset") ? form.find(":reset")[0] : null;
		var track_input_states			= args.track_input_states			== true;
		var clear_texts_on_first_focus	= args.clear_texts_on_first_focus	== true;
		// This is the array of data being posted on submit. We initialize it with our state data object:
		var post_data					= request_state;
		
		// IE6 (and possibly other retarded browsers) go weirdo with
		// AJAX'ed forms and the return key. To avoid them submitting
		// regularly (i.e. sending the user to a new page with JSON
		// data printed out in all its glory) when the enter key is
		// pressed, we simply add onsubmit="return false;" to the form:
		form.submit(function() { return false; });
		
		// To be able to do several things on the same event, we build an
		// array of functions to fire on each event. Then, finally we let
		// the event triggers traverse their respective function arrays to
		// fire any and all events on demand. Neat :)
		if (track_input_states || clear_texts_on_first_focus)
		{
			var functions_focus = [], functions_change = [];
			
			// This one to make sure (IE) that the caret isn't placed
			// at the beginning of the text in an input field after
			// doing manipulation of the text via one of our functions.
			place_caret_at_text_end = function(element)
			{
				if (element.setSelectionRange) /* DOM */
				{
					setTimeout( /* hack for select delay */
						function(t)
						{
							t.setSelectionRange(t.value.length,t.value.length);
						}
						, 0, element
					);
				}
				else if (element.createTextRange) /* IE */
				{
					r=element.createTextRange();
					r.collapse(false);
					r.select();
				}
			};
			
			/**
			 * Clearing texts on first is used when some kind of description is entered into the
			 * field itself, usually something like "Enter your e-mail address here." Of course,
			 * this applies only to text and textarea type elements.
			 */
			if (clear_texts_on_first_focus)
			{
				clear_text_on_first_focus = function(element)
				{
					var me = $(element);
					if ((me.attr('type') == 'text' || me.attr('type') == 'textarea' ) && me.attr('text_cleared_on_first_focus') != 'yes')
					{
						me.attr('text_cleared_on_first_focus', 'yes');
						me.val('');
					}
				};
				// Also on change, in case we're being "remote controlled", e.g. by using the direct subscription box in v3:
				functions_change.push(clear_text_on_first_focus);
				
				functions_focus.push(clear_text_on_first_focus);
			}
			
			if (track_input_states)
			{
				store_input_state = function(element)
				{
					var me = $(element);
					// Never on error elements, and if clear_texts_on_first_focus is enabled, we need to make sure we're not tracking the value to be cleared:
					if (!me.hasClass('error')) me.attr('cached_val', (clear_texts_on_first_focus && me.attr('text_cleared_on_first_focus') != 'yes') ? '' : me.val());
				};
				
				restore_input_state = function(element)
				{
					var me = $(element);
					if (me.hasClass('error')) me.val(me.attr('cached_val'));
				};
				
				functions_focus.push(restore_input_state);
				
				functions_change.push(store_input_state);
				
				form.find('input[type="text"], textarea').each(function(event) { store_input_state(this); });
			}
			
			form.find('input[type="text"], textarea').focus(function(event)		{ for(var i=0; i<functions_focus.length; ++i)	functions_focus[i]((this)); place_caret_at_text_end(this); $(this).removeClass('error').addClass('focus'); });
			form.find('input[type="text"], textarea').blur(function(event)		{ $(this).removeClass('focus'); });
			form.find('input[type="text"], textarea').change(function(event)	{ for(var i=0; i<functions_change.length; ++i)	functions_change[i]((this)); });
		}
		
		/**
		 * The buttons pass [this] to the submit_function, so that if there are several
		 * submit buttons, we can tell about which one was clicked.
		 */
		var submit_function = function(button_clicked)
		{
			// We don't submit if any of our fields are in a non-initial state (with regards to
			// style & class), which is synonymous to erroneous.
			if (track_input_states)
			{
				var okay_to_submit = true;
				form.find('input[type="text"], textarea').each(function(event) { if ($(this).hasClass('error')) okay_to_submit = false; });
				if (!okay_to_submit) { return false; }
			}
			
			// Store all input states on the form's text areas if we're tracking states. We don't
			// have to do any extra checks now, since we're borking out above, if any input field
			// is in an erroneous state:
			if (track_input_states) form.find('input[type="text"], textarea').each(function() { store_input_state(this); this.blur(); });
			
			if (is_defined(args.on_submit))
				args.on_submit();
			
			// Collect post data (select every element with a name attribute in the form, except for radio buttons for which we only get the checked one):
			form.find('[name][type!=radio],input:radio:checked', form).each(function() {
				var element = $(this);
				// Note the last check - if we're supposed to be clearing text and textarea captions on first focus, we check if we have done so, otherwise we send a blank value (instead of sending the caption)
				post_data[element.attr('name')] = (element.attr('type') == 'checkbox') ? ((element.attr('checked') == true) ? 1 : 0) : ((clear_texts_on_first_focus && (element.attr('type') == 'text' || element.attr('type') == 'textarea') && element.attr('text_cleared_on_first_focus') != 'yes') ? '' : element.val());
			});
			
			// And, since we know which button was clicked, tell about it:
			post_data['submit_button_clicked'] = $(button_clicked).attr('name');
			
			// In case there's more than one submit button, we need to make sure we include the right value:
			if (!is_defined($(this).attr('name'))) post_data[$(this).attr('name')] = $(this).val();
			
			// Post:
			$.post(action,
				post_data,
				function(response)
				{
					
					// Clear any checkboxes if requested to do so:
					if (!is_empty(response.clearable_checkboxes)) // Skip this check and IE6 breaks.
					{
						$.each(response.clearable_checkboxes, function(index, box_name) {
							$('input[name="'+box_name+'"]').removeAttr('checked');
						});
					}
					
					// If there are errors on specific fields, we notify the user by adding an error class to those fields and writing a notification inside the fields.
					if (!is_empty(response.erroneous_fields))
					{
						// Change the state of bad input field to erroneous:
						$.each(response.erroneous_fields, function(field_name, message) {
							// Only for those that exist:
							if (!is_empty($(':input[name="'+field_name+'"]'), form))
								$(':input[name="'+field_name+'"]', form).val(message).addClass('error');
						});
					}
					
					// Do callbacks:
					if (is_defined(args.on_response))
					{
						if ($.isFunction(args.on_response))
							args.on_response(response);
						else
							eval(args.on_response);
					}
					
					if (response.success && is_defined(args.on_success))
					{
						if ($.isFunction(args.on_success))
							args.on_success(response);
						else
							eval(args.on_success);
					}
					
					if (!response.success && is_defined(args.on_failure))
					{
						if ($.isFunction(args.on_failure))
							args.on_failure(response);
						else
							eval(args.on_failure);
					}
				},
				'json'
			);
		};
		
		// Override the regular submit behavior on the submit buttons:
		$(submit_buttons).click(function() { submit_function(this); return false; });
		
		// Configure the cancel button, if requested:
		if (is_defined(args.on_cancel)) $(cancel_buttons).click(args.on_cancel);
	});
	
	return true;
}




/**
 * Inserts a captcha code image in the specified DOM element.
 * 
 * @param	{string}	args.target											The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * @param	{mixed}		args.bgcol_red								(OPT)	8-bit red value of the background color of the image (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.bgcol_green							(OPT)	8-bit green value of the background color of the image (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.bgcol_blue								(OPT)	8-bit blue value of the background color of the image (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.lines_brightness_min					(OPT)	8-bit minimum value of the obfuscation lines luminance (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.lines_brightness_max					(OPT)	8-bit maximum value of the obfuscation lines luminance (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.chars_brightness_min					(OPT)	8-bit minimum value of the text character luminance (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{mixed}		args.chars_brightness_max					(OPT)	8-bit maximum value of the text character luminance (decimals
 * 																			and hexadecimals (prefixed by # or Ox) allowed).
 * @param	{int}		args.width									(OPT)	The width, in pixels, of the image.
 * @param	{int}		args.height									(OPT)	The height, in pixels, of the image.
 * @param	{int}		args.chars									(OPT)	The number of captcha characters to write.
 * @param	{int}		args.font_size								(OPT)	The font size.
 * @param	{int}		args.text_indentation						(OPT)	The number of pixels (from the left) to indent the captcha code.
 * @return	{bool}															True on success, false on failure.
 */
function insert_captcha_image(args)
{
	if (is_empty(arguments) || !is_defined(args.target) || is_empty(args.target = jquerify_element(args.target)))
	{
		trigger_error("insert_captcha_image(): Not enough arguments provided. I need to know at least the DOM id of to load the image element into ('target').");
		return false;
	}
	
	// Let's assume the captcha image in question has already been loaded once by us,
	// in which case we've stored the request string in a tag on it:
	var request_string = args.target.attr('requeststring');
	
	// If we were wrong, let's start a new request string:
	if (is_empty(request_string)) request_string = '/png/core_png_captcha.php';
	
	// Let's add any (new) arguments to the request:
	if (is_defined(args.bgcol_red))				request_string = set_request_get_vars(request_string, { bgr:	parseInt(correct_hex_prefix(args.bgcol_red)) });
	if (is_defined(args.bgcol_green))			request_string = set_request_get_vars(request_string, { bgg:	parseInt(correct_hex_prefix(args.bgcol_green)) });
	if (is_defined(args.bgcol_blue))			request_string = set_request_get_vars(request_string, { bgb:	parseInt(correct_hex_prefix(args.bgcol_blue)) });
	if (is_defined(args.lines_brightness_min))	request_string = set_request_get_vars(request_string, { lmin:	parseInt(correct_hex_prefix(args.lines_brightness_min)) });
	if (is_defined(args.lines_brightness_max))	request_string = set_request_get_vars(request_string, { lmax:	parseInt(correct_hex_prefix(args.lines_brightness_max)) });
	if (is_defined(args.chars_brightness_min))	request_string = set_request_get_vars(request_string, { cmin:	parseInt(correct_hex_prefix(args.chars_brightness_min)) });
	if (is_defined(args.chars_brightness_max))	request_string = set_request_get_vars(request_string, { cmax:	parseInt(correct_hex_prefix(args.chars_brightness_max)) });
	if (is_defined(args.width))					request_string = set_request_get_vars(request_string, { w:		parseInt(args.width) });
	if (is_defined(args.height))				request_string = set_request_get_vars(request_string, { h:		parseInt(args.height) });
	if (is_defined(args.chars))					request_string = set_request_get_vars(request_string, { ch:		parseInt(args.chars) });
	if (is_defined(args.font_size))				request_string = set_request_get_vars(request_string, { fs:		parseInt(correct_hex_prefix(args.font_size)) });
	if (is_defined(args.text_indentation))		request_string = set_request_get_vars(request_string, { ti:		parseInt(correct_hex_prefix(args.text_indentation)) });
	
	// Always add a (semi-)random var, so that we don't fetch old captchas from the browser's cache:
	request_string = set_request_get_vars(request_string, { no_cache: get_random_id() });
	
	// Make sure we don't add the onclick function more than once ;):
	if (is_empty(args.target.attr('requeststring')))
		args.target.click(function() { insert_captcha_image({ target: args.target }); this.blur(); return false; });
	
	args.target.attr('src', request_string).attr('requeststring', request_string);
	
	return false;
}




/**
 * Renders an AJAX star rater in the specified DOM element.
 * 
 * Version 1.9.1006
 * History:
 * 	1.9.1006: Wrapped the vars object that is posted in object_without_null_members() to remove any member that is nullified.
 * 
 * @param	{mixed}		args.target											The target DOM object, jQuery object, string of the id of the
 * 																			DOM object, or jQuery-compatible selector statement string.
 * @param	{integer}	args.clip_id										The id of the clip for which to create a rater.
 * @param	{integer}	args.current_rating									The current clip rating.
 * @param	{integer}	args.max_rating										The maximum possible rating, and therefore also the number
 * 																			of stars to generate.
 * @param	{string}	args.img_class										CSS class to assign the created <img> elements.
 * @param	{string}	args.img_on_url										The url for the star image to signify the currently achieved
 * 																			rating of the clip.
 * @param	{string}	args.img_off_url									The url for the star image to signify the currently non-
 * 																			achieved rating of the clip.
 * @param	{string}	args.img_over_url									The url for the star image when entering mouseover event.
 * @param	{string}	args.img_captions							(OPT)	Optional tooltip captions to display for each of the stars
 * 																			on mouseover.
 * @return	{bool}															True on success, false on failure.
 */
function create_star_rater(args)
{
	if (is_empty(arguments) || !is_defined(args.target) || is_empty(args.target = jquerify_element(args.target)) || !is_defined(args.clip_id) || !is_defined(args.current_rating) || !is_defined(args.max_rating) || !is_defined(args.img_class) || !is_defined(args.img_on_url) || !is_defined(args.img_off_url) || !is_defined(args.img_over_url))
	{
		trigger_error("create_star_rater(): Not enough arguments provided. I need to know the target dom id to create stars inside ('target'), the id of the clip in question ('clip_id'), the current rating of the clip ('current_rating'), the maximum possible rating ('max_rating'), the CSS class to assign each image ('img_class'), and the image urls for the on, off, and over states ('img_on_url', 'img_off_url', and 'img_over_url').");
		return false;
	}
	
	// Utility functions:
	ajax_rater_over = function(args)
	{
		var element_id	= $(args.element).attr('id');
		var base_id		= element_id.replace(/_.+$/, '');
		var star_num	= parseInt(element_id.substring(base_id.length+1, element_id.length), 10);
		
		for (var i=1; i<=star_num; ++i)
		{
			var star = $('#'+base_id+'_'+i);
			star.attr('old_src', star.attr('src'));
			star.attr('src', args.over);
		}
	};
	ajax_rater_out = function(args)
	{
		var element_id	= $(args.element).attr('id');
		var base_id		= element_id.replace(/_.+$/, '');
		var star_num	= parseInt(element_id.substring(base_id.length+1, element_id.length), 10);
		
		for (var i=1; i<=star_num; ++i)
		{
			var star = $('#'+base_id+'_'+i);
			star.attr('src', star.attr('old_src'));
		}
	};
	ajax_rater_click = function(args)
	{
		ajax_rater_out(args);
		
		var element_id	= $(args.element).attr('id');
		var base_id		= element_id.replace(/_.+$/, '');
		var star_num	= parseInt(element_id.substring(base_id.length+1, element_id.length), 10);
		
		// Post:
		$.post('/json/core_rate_clip.php',
			object_without_null_members(merge_objects(request_state, {clip_id:args.clip_id, rating:star_num})),
			function(response)
			{
				// If successful we update the rating:
				if (response.success && is_defined(response.updated_rating))
				{
					for (var i=1; 1==1; ++i)
					{
						var star_id = '#'+base_id+'_'+i;
						// Break out if this element doesn't exist:
						if ($(star_id).length == 0) break;
						var star = $('#'+base_id+'_'+i);
						var new_state = ((i<=response.updated_rating) ? args.on : args.off);
						star.attr('src',		new_state);
						star.attr('old_src',	new_state);
					}
				}
				alert(response.message);
			},
			'json'
		);
	};
	
	for(var i=1, base_id=get_random_id(); i<=args.max_rating; ++i)
	{
		var element = $('<acronym></acronym>');
		
		if ($.isArray(args.img_captions)) element.attr('title', args.img_captions[i-1]);
		
		var image = $('<img />');
		
		image.attr('id', base_id+'_'+i);
		
		image.attr('src', ((i <= args.current_rating) ? args.img_on_url : args.img_off_url));
		
		image.attr('class', args.img_class);
		
		image.mouseover(function() {
			ajax_rater_over({ element:this, clip_id:args.clip_id, on:args.img_on_url, off:args.img_off_url, over:args.img_over_url });
		});
		
		image.mouseout(function() {
			ajax_rater_out({ element:this, clip_id:args.clip_id, on:args.img_on_url, off:args.img_off_url, over:args.img_over_url });
		});
		
		image.click(function() {
			ajax_rater_click({ element:this, clip_id:args.clip_id, on:args.img_on_url, off:args.img_off_url, over:args.img_over_url });
		});
		
		element.append(image);
		
		args.target.append(element);
	}
	
	return true;
}





var collapsible_box_exclusive_groups = new Array();

/**
 * Registers a box (that is, a combination of a control element and a collapsible (hideable) element) as
 * 'collapsible'. This adds an onclick event to the control element that will toggle the collapsed state
 * of (i.e. show or hide) the collapsible element.
 * 
 * If you have an onclick event of your own, this will be kept and fired AFTER the toggle event when the
 * control element is clicked.
 * 
 * If you have a group of elements in which only one element may be expanded at a time, you can use the
 * optional .exclusive_group parameter to join together boxes in groups. You may create as many groups as
 * you wish.
 * 
 * If cookies are enabled, box state is kept across page views and sessions.
 * 
 * @param	{bool}		args.defaults_to_expanded							Whether, if no state has been recorded in the user's cookie, to show the box expanded.
 * @param	{string}	args.control_element_id								The dom id of the control element of the box whose state to toggle.
 * @param	{string}	args.control_element_class_expanded					The class to apply to the control element when the box is expanded.
 * @param	{string}	args.control_element_class_collapsed				The class to apply to the control element when the box is collapsed.
 * @param	{string}	args.collapsible_element_id							The dom id of the content element to collapse.
 * @param	{string}	args.collapsible_element_class_expanded				The class to apply to the content element when the box is expanded.
 * @param	{string}	args.collapsible_element_class_collapsed			The class to apply to the content element when the box is collapsed.
 * @param	{string}	args.control_element_tooltip_expanded		(OPT)	A tooltip caption to attach to the control element when expanded.
 * @param	{string}	args.control_element_tooltip_collapsed		(OPT)	A tooltip caption to attach to the control element when collapsed.
 * @param	{string}	args.exclusive_group						(OPT)	A group name in which only one box may be expanded at any one time.
 * @return	{bool}															True on success, false on failure.
 */
function make_collapsible_box(args)
{
	if (is_empty(arguments) || !is_defined(args.control_element_id) || !is_defined(args.collapsible_element_id) || !is_defined(args.control_element_class_expanded) || !is_defined(args.control_element_class_collapsed) || !is_defined(args.collapsible_element_class_expanded) || !is_defined(args.collapsible_element_class_collapsed))
	{
		trigger_error("make_collapsible_box(): Not enough arguments provided. I need to know at least the control element to add an onclick event to ('control_element_id'), the element to collapse or expand when the control element is clicked ('collapsible_element_id'), and the CSS classes to to toggle between ('control_element_class_expanded', 'control_element_class_collapsed', 'collapsible_element_class_expanded', and 'collapsible_element_class_collapsed')");
		return false;
	}
	
	// Always make sure we don't try to fiddle with the DOM before it's finished loading:
	$(document).ready(function() {
		
		// Attach a collapse state toggle to the onclick event of the control element:
		$('#'+args.control_element_id).click( function(e) { toggle_collapsible_box(args); } );
		
		// Make sure the box state is restored:
		restore_collapsible_box_state(args);
	});
	
	return true;
}




/**
 * Expands a collapsed box, or collapses an expanded box. If 'exclusive_group' is defined, then only one of the boxes in
 * exclusive_group is allowed to be open at any one time. Upon expanding one, the rest are automatically collapsed.
 * 
 * You wouldn't normally call this function directly, but instead initialize your boxes with make_collapsible_box().
 * 
 * @param	{string}	args.control_element_id								The dom id of the control element of the box whose state to toggle.
 * @param	{string}	args.control_element_class_expanded					The class to apply to the control element when the box is expanded.
 * @param	{string}	args.control_element_class_collapsed				The class to apply to the control element when the box is collapsed.
 * @param	{string}	args.collapsible_element_id							The dom id of the content element to collapse.
 * @param	{string}	args.collapsible_element_class_expanded				The class to apply to the content element when the box is expanded.
 * @param	{string}	args.collapsible_element_class_collapsed			The class to apply to the content element when the box is collapsed.
 * @param	{string}	args.control_element_tooltip_expanded		(OPT)	A tooltip caption to attach to the control element when expanded.
 * @param	{string}	args.control_element_tooltip_collapsed		(OPT)	A tooltip caption to attach to the control element when collapsed.
 * @param	{string}	args.exclusive_group						(OPT)	A group name in which only one box may be expanded at any one time.
 * @return	{bool}															True on success, false on failure.
 */
function toggle_collapsible_box(args)
{
	if (is_empty(arguments) || !is_defined(args.control_element_id) || !is_defined(args.collapsible_element_id) || !is_defined(args.control_element_class_expanded) || !is_defined(args.control_element_class_collapsed) || !is_defined(args.collapsible_element_class_expanded) || !is_defined(args.collapsible_element_class_collapsed))
	{
		trigger_error("toggle_collapsible_box(): Not enough arguments provided. I need to know at least the control element that's been clicked ('control_element_id'), the element to collapse or expand ('collapsible_element_id'), and the CSS classes to to toggle between ('control_element_class_expanded', 'control_element_class_collapsed', 'collapsible_element_class_expanded', and 'collapsible_element_class_collapsed')");
		return false;
	}
	
	var collapse = $('#' + args.control_element_id).is('.' + args.control_element_class_expanded);
	
	if (collapse)
	{
		collapse_collapsible_box(args);
	}
	else
	{
		if (is_defined(args.exclusive_group))
    		for (id in collapsible_box_exclusive_groups[args.exclusive_group])
    			collapse_collapsible_box(collapsible_box_exclusive_groups[args.exclusive_group][id]);
		
		expand_collapsible_box(args);
	}
	
	return false;
}

/**
 * Internal utility functions, you shouldn't call these directly. Use toggle or restore instead.
 */
function collapse_collapsible_box(args)
{
	// Switcharoo:
	$('#' + args.control_element_id).removeClass(args.control_element_class_expanded).addClass(args.control_element_class_collapsed);
	$('#' + args.collapsible_element_id).removeClass(args.collapsible_element_class_expanded).addClass(args.collapsible_element_class_collapsed);
	
	// Store in a domain- and site-wide cookie:
	$.cookie(site_hostname + '_' + args.control_element_id + '_expanded', 'no', {expires:90, path:'/', domain:'.'+site_hostname});
	
	if (is_defined(args.control_element_tooltip_collapsed)) $('#' + args.control_element_id).attr('title', args.control_element_tooltip_collapsed);
}
function expand_collapsible_box(args)
{
	// Switcharoo:
	$('#' + args.control_element_id).removeClass(args.control_element_class_collapsed).addClass(args.control_element_class_expanded);
	$('#' + args.collapsible_element_id).removeClass(args.collapsible_element_class_collapsed).addClass(args.collapsible_element_class_expanded);
	
	// Store in a domain- and site-wide cookie:
	$.cookie(site_hostname + '_' + args.control_element_id + '_expanded', 'yes', {expires:90, path:'/', domain:'.'+site_hostname});
	
	if (is_defined(args.control_element_tooltip_collapsed)) $('#' + args.control_element_id).attr('title', args.control_element_tooltip_expanded);
}
function restore_collapsible_box_state(args)
{
	if (is_defined(args.exclusive_group))
	{
		// Create the exclusive group if it doesn't exist:
		if (!$.isArray(collapsible_box_exclusive_groups[args.exclusive_group]))
			collapsible_box_exclusive_groups[args.exclusive_group] = [];
		
		// Add this box to the group (implicitly if it's not registered already (happens on template reloads, for instance, why is also why we index the grouped boxes using their dom id)):
		collapsible_box_exclusive_groups[args.exclusive_group][args.control_element_id] = args;
	}
	
	var cookie_value		= $.cookie(site_hostname + '_' + args.control_element_id + '_expanded');
	var should_be_expanded	= cookie_value == 'yes' || (cookie_value == null && args.defaults_to_expanded);
	var is_expanded			= $('#' + args.control_element_id).is('.' + args.control_element_class_expanded);
	
	if (is_defined(args.control_element_tooltip_collapsed) && !is_expanded)	$('#' + args.control_element_id).attr('title', args.control_element_tooltip_collapsed);
	if (is_defined(args.control_element_tooltip_expanded) && is_expanded)	$('#' + args.control_element_id).attr('title', args.control_element_tooltip_expanded);
	
	if (should_be_expanded != is_expanded) toggle_collapsible_box(args);
}




