function initBehaviours() {
	// DG. Responsible for spinning up all page behaviours that would otherwise
	// remain idle or link to static pages.
	externalLinks();
	
	var behaviour_rules = {
			'.interactive_rating a' : function(element){
				// Add behaviour to interactive ratings that
				// follow mouse movement across the range.
				element.onmouseover = function(){
					// On hover, shade the stars to illustrate the given rating
					setStarRating(numeric.exec(this.id));
				}
				element.onmouseout = function(){
					// When the cursor leaves, shade the stars to their original setting.
					setStarRating(original_star_rating);
				}
			}
		};
	
	Behaviour.register(behaviour_rules);
	Behaviour.apply();
	
	customSearchBox(); 
	fixTabs(); 
	setDefaultSearchValueTo("Search...");
	btn.init();
}

function checkEnter(e, form){
	var characterCode;

	if(e && e.which) { 
		e = e;
		characterCode = e.which; 
	} else {
		e = event;
		characterCode = e.keyCode;
	}

	if(characterCode == 13){ 
		document.forms[form].submit(); 
		return false;
	} else {
		return true;
	}
}

	// GLOBALS
		// Set to true to enable console logging.
		var verbose = false;
		// Numeric regex used to extract object ID's from element ID's
		var numeric = /[0-9]+/;
		// Global animation settings - use these wherever possible in your script.aculo.us calls to keep things consistent.
		var opacityFadeDuration = 0.3;
		var blindDuration = 0.2;
		var slideDuration = 0.2;
	

	// Used by onsubmit handlers to confirm the user's choice with a generated code.
	// Example:
	// form onsubmit="return confirm_with_code('asdf', 'Are you sure you want to banhammer this user?');"
	// Will result in a dialogue reading:
	// Are you sure you want to banhammer this user? Please type the code 'asdf' to confirm your choice, or click 'cancel'....
	function confirm_with_code(code, message) {
		var input = prompt(message+" Please type the code '"+code+"' to confirm your choice, or click 'cancel' to abort.");
		if(input==code) {
			return true;
		} else if(!input) {
			return false;
		} else {
			alert('You entered the wrong confirmation code. Please try again.');
			return false;
		}
	}
	
	// UPLOAD FORM
	// --------------------------------------------------------------------------------------------------
	
		// Triggers front-end validation of a movie upload form for easily-spottable errors before the file is uploaded.
		function validateMovieUpload(file_desc) {
			file_desc = {
				"stored_image": {
									"extensions": $A(["jpg","png","gif"]),
									"name": "thumbnail image",
									"requiredForIdent": false
								},
				"movie": 		{
									"extensions": $A(["flv"]),
									"name": "video file",
									"requiredForIdent": true
								}
			}
			var rval = true; // return value
			var errors = $A([]);
			// TOS			
			if($F('video_construct_terms_of_service')!='1') { errors.push('You must agree to the terms of service before uploading.'); }
			// FILENAMES
			$A(["stored_image", "movie"]).each(function(label) {
				var filename = $F('video_construct_'+label+'_file');
					if(!filename) filename = "";
				var extension = filename.split('.'); extension = extension[extension.length-1];
								
				var file_needed = (file_desc[label]["requiredForIdent"] || (!$('video_construct_publish_type_channel') || $F('video_construct_publish_type_channel')) );
				var file_given = (filename.length > 0);
				var file_valid = (file_desc[label]["extensions"].include(extension.toLowerCase()));
				
				if((file_needed && (!file_given || !file_valid)) || (!file_needed && file_given && !file_valid)) {
					errors.push("File in "+file_desc[label]["name"]+" field is not set or is of an accepted type");
				}
			});
						
			// FINALISE and display
			if(errors.length > 0) {
				rval = false;
				var alertstr = "";
				errors.each(function(err) {
					alertstr += "- "+err+"\n\r";
				});
				alert(alertstr);
			}
			return rval;
		}

	// MOVIE RATINGS
	// --------------------------------------------------------------------------------------------------

		// Changes the star rating for user feedback while moving the mouse over the
		// rating stars.
		function setStarRating(rating, id_prefix) {
			if(!id_prefix) id_prefix = "interactive_rating_star_";
			if(rating) {
				rating = new Number(rating); // < force type
				// Iterate over stars
				$$(".interactive_rating a").each(function(element) {
					r = new Number(numeric.exec(element.id));
					element.addClassName((r <= rating)? "on" : "off");
					element.removeClassName((r <= rating)? "off" : "on");
				});
			}
		}
	
	// SHARE FORMS
	// --------------------------------------------------------------------------------------------------
	
		// "Share this" form - this is the share form nestled within channel browsers, playlists and other such movie-list templates.
		var share_this_form_shown = false;
		var share_this_form_address_book_loaded = false;
		function showShareThisForm(address_book_uri) {
			if(!share_this_form_shown) {
				// Interact with other panels - hide the tag filter
				hideTagFilterPanel();
				// Do animation and set flag
				new Effect.BlindDown('share_this_form', {queue:{position:'end', scope:'channelpanelscope'}, duration: blindDuration});
				// Get address book
				if(address_book_uri && !share_this_form_address_book_loaded) {
					new Ajax.Request(address_book_uri, {asynchronous:true, evalScripts:true});
					share_this_form_address_book_loaded = true;
				}
				share_this_form_shown = true;
			} else {
				// Show a pulse effect to illustrate that the panel is already shown, unless we're in Safari, which freaks out.
				if(!browserIsSafari()) new Effect.Pulsate('share_this_form', {pulses: 2, duration: 1});
			}
			return false;
		}
		function hideShareThisForm() {
			if(share_this_form_shown) {
				new Effect.BlindUp('share_this_form', {queue:{position:'end', scope:'channelpanelscope'}, duration: blindDuration});
				share_this_form_shown = false;
			}
			return false;
		}
		
			// And that address book form has some unique behaviours for adding values to the form field.
			// Some properties of this setup are replicated and shared by the larger share form for sharing individual videos.
			var share_form_email_seperator_char = ",";
			function enumerate_share_form_email_field(field_id) {
				// Takes the field and returns a list of emails in the field
				var enum_result = $A([]);
				var emails = $A($F(field_id).split(","))
				emails.each(function(email) {
					var str = email.strip();
					if(str.length>0) enum_result.push(email.strip());
				})
				return enum_result;
			}
			function add_emails_from_form_field_to_enumerated_form_field(select_id, enumerated_field_id, after_function) {
				// Adds emails from a multiple-selection select element to the desired field
				var existing_emails = $A(enumerate_share_form_email_field(enumerated_field_id));
				var selected_emails = $A($F(select_id));
				selected_emails.each(function(email) {
					existing_emails.push(email.strip());
				});
				var field = $(enumerated_field_id); 
				field.value = "";
				existing_emails.uniq().each(function(email, key) {
					field.value += email;
					say("adding email: "+email);
					if(email!=existing_emails[existing_emails.length-1]) field.value += ", ";
				})
				if(after_function) after_function();
			}

		// When EVIL requires us to hijack the return key, we use this to detect the key press:
		function keycodeDoesMatchReturnKey(e) {
			// IE accesses the keycode through a different and entirely fictional method.
			var keycode = (window.event)? e.keyCode : e.which;
			var key = String.fromCharCode(keycode)
			var returnkeyregex = /\r|\n/;
			return returnkeyregex.test(key);
		}
		
	// TAG FILTER PANELS & SORTING
	// --------------------------------------------------------------------------------------------------
	
		var tag_filter_panel_shown = false;
		var tag_filters_loaded = false;
		var tag_matrix = [];
		
		function applySortForCurrentPage(elem) {
			
			if($('filter_playlist') && $('filter_panel_sort_field')) {
				// If a filter panel is available, it will be munged with the data from the sorting field and told to post.
				$('filter_panel_sort_field').value = $F(elem.id);
				$('filter_playlist').submit();
			} else {
				// If it's not available, we simply reload with params[:sort_by]
				
			}
			return false;
		}
		
		// Shows the filter panel. You should supply a channel id, or a channel/playlist ID pair so that the
		// url for loading the tags can be generated.
		function showTagFilterPanel(url,search_query) {
			if(!tag_filter_panel_shown) {
				// Interact with other panels - close the share form
				hideShareThisForm();
				hideTagFilterBar();
				// Do animation and set flag
				new Effect.BlindDown('tag_filter_panel', {queue:{position:'end', scope:'channelpanelscope'}, duration: blindDuration});
				tag_filter_panel_shown = true;
				// Now load from server
				loadTagFiltersFromApplication(url,search_query);
			} else {
				// Pulse the form a couple times - except in safari, which throws its toys out the pram.
				if(!browserIsSafari()) new Effect.Pulsate('tag_filter_panel', {pulses: 2, duration: 1});
			}
			return false;
		}
		
		// Hides the tag filter panel but does NOT remove any tags that were loaded.
		function hideTagFilterPanel() {
			if(tag_filter_panel_shown) {
				showTagFilterBar();
				// Do animation and set flag
				new Effect.BlindUp('tag_filter_panel', {queue:{position:'end', scope:'channelpanelscope'}, duration: blindDuration});
				tag_filter_panel_shown = false;
				$$(".nav-bar").each(function(element) {
					element.show();
				});				  
			}
			return false;
		}
		
		// For shortening URL masks. Before the form is submitted, the form is scrubbed for inactive
		// inputs
		function removeInactiveInputsInTagFilterPanel() {
			var inputs = $$('#filter_fields_in_tag_filter_panel input');
			inputs.each(function(input) {
				if(input.id && $F(input.id)=="0") {
					input.remove();
				}
			});
			return true;
		}
		
		// queue:{position:'end', scope:'channelpanelscope'}, 
		function showTagFilterBar() {
			new Effect.BlindDown('tag_filter_bar', {queue:{position:'end', scope:'channelpanelscope'}, duration: blindDuration/2});
		}
		
		function hideTagFilterBar() {
			new Effect.BlindUp('tag_filter_bar', {queue:{position:'end', scope:'channelpanelscope'}, duration: blindDuration});
		}
		
		// Populates the tag filter panel with data from the application. Done on-demand to prevent the server from
		// exploding and thus from annoying the sysadmins.
		function loadTagFiltersFromApplication(url,search_query) {
			if(!tag_filters_loaded) {
				new Ajax.Request(url, {asynchronous:true, evalScripts:true});
				tag_filters_loaded = true;
			}
		}
		
		// Toggles the value of a tag in the filter panel, using the value of the tag's form field as
		// the current state.
		function toggleTagInFilterPanel(tag_id) {
			var val = $F('filter_tags_'+tag_id);
			if(val == "0") enableTagInFilterPanel(tag_id);
			else disableTagInFilterPanel(tag_id);
		}
		
		// Specifically ENABLES a tag in the filter panel, meaning the tag will be INCLUDED
		// in the filtered result list.
		function enableTagInFilterPanel(tag_id) {
			// Set field value
			$('filter_tags_'+tag_id).value = "1";
			if($('state_filter_tags_'+tag_id)) $('state_filter_tags_'+tag_id).value = "1";
			// Set visual feedback
			$('filter_panel_tag_label_'+tag_id).className = "on";
			//updateTagFilterPanelButtonText();
		}
		
		// Specifically DISABLES a tag in the filter panel, meaning the tag will be EXCLUDED
		// from the filtered result list.
		function disableTagInFilterPanel(tag_id) {
			// Set field value
			$('filter_tags_'+tag_id).value = "0";
			if($('state_filter_tags_'+tag_id)) $('state_filter_tags_'+tag_id).value = "0";
			// Set visual feedback
			$('filter_panel_tag_label_'+tag_id).className = "off";
			//updateTagFilterPanelButtonText();
		}
		
		// Returns an array of tags which should be EXCLUDED from the filtered list by inspecting the 
		// form element.
		function excludedTagIDsInTagFilterPanel() {
			var fields = $$(".tag_filter_panel_field");
			var exclude_tags = [];
				fields.each(function(field) {
					if($F(field.id) == "0") exclude_tags.push(parseInt(numeric.exec(field.id)));
				})
			return exclude_tags;
		}
		
		// Returns an array of tags which should be INCLUDED by the filtered list by inspecting the 
		// form element.
		function includedTagIDsInTagFilterPanel() {
			var fields = $$(".tag_filter_panel_field");
			var inc_tags = [];
				fields.each(function(field) {
					if($F(field.id) == "1") inc_tags.push(parseInt(numeric.exec(field.id)));
				})
			return inc_tags;
		}
		
		// Returns the number of items in the current movie list that would be EXCLUDED if the current
		// selected tags where to be applied.
		function numberOfItemsExcludedByCurrentTagFilterPanelState() {
			return numberOfItemsInTagFilterPanelMatchingTagIDs(excludedTagIDsInTagFilterPanel());
		}
		
		// Returns the number of items in the current movie list that would be INCLUDED if the current
		// selected tags where to be applied.
		function numberOfItemsIncludedByCurrentTagFilterPanelState() {
			return numberOfItemsInTagFilterPanelMatchingTagIDs(includedTagIDsInTagFilterPanel());
		}
		
		function numberOfItemsInTagFilterPanelMatchingTagIDs(tag_ids) {
			var n_total = tag_matrix.length;
				if(n_total==0) return 0; // No tag matrix defined, so skip this step.
			
			var inc_tags = $A(tag_ids);
			var n_matched = 0;
				$A(tag_matrix).each(function(inner_matrix) {
					// If intersection between excluded tags and tags of this item exists, then
					// we've made a match.
					var this_row_result = false;
						inc_tags.each(function(inc_tag) {	if(!this_row_result && inner_matrix.without(inc_tag).length != inner_matrix.length) this_row_result = true;	})
						if(this_row_result) n_matched += 1;
				});
			return n_matched;
		}
		
		
		// Calculates the results of the current filter and shows it on the "Apply Filter" button residing in the tag filter panel.
		function updateTagFilterPanelButtonText() {
			// DEPRECATED
			//var btn = $$('#filter_playlist_submit span')[0];
			//var n_total = tag_matrix.length;
			//var n_included = numberOfItemsExcludedByCurrentTagFilterPanelState();
			//
			//btn.innerHTML = (n_included > 0)? "Show "+n_included+" of "+n_total+" results" : "Nothing to show";
			return false;
		}
		
	// CHANNEL BROWSER
	// --------------------------------------------------------------------------------------------------
	
		// For switching dynamically between the splash page and the movies in a channel that has a splash page.
		var channelSplashToggleStates = {};
		function toggleSplashAndMoviesForChannel(channel_id, toggle_to, movies_label, splash_label) {
			if(!channelSplashToggleStates[channel_id]) {
				var hiding = (toggle_to=='movies')? 'splash' : 'movies';
			} else {
				var hiding = (channelSplashToggleStates[channel_id]=='movies')? 'movies' : 'splash';
				toggle_to = (hiding=="movies")? "splash" : "movies";
			}
			// Store state
			channelSplashToggleStates[channel_id] = toggle_to;
			// change button label
			$('splash_toggle_for_channel_'+channel_id).firstChild.innerHTML = (hiding=='splash')? splash_label : movies_label;
			// Affect transition
			Effect.BlindUp(hiding);
			Effect.BlindDown(toggle_to);
			// Exit stage left
			return false;
		}
	
		// When trying to rename a playlist in the channel browser, use this custom request:
		function tryToRenameChannelFromChannelBrowser(source_field, request_uri) {
			var newname = $F(source_field);
			new Ajax.Request(request_uri+"&name="+newname, {asynchronous:true, evalScripts:true});
			return false;
		}
		// And when trying to create a playlist, we can do the same:
		function tryToCreatePlaylistInChannelFromChannelBrowser(source_field, request_uri) {
			// We're just proxying this function for now.
			return tryToRenameChannelFromChannelBrowser(source_field, request_uri);
		}
		// Likewise for new owner channels
		function tryToCreateOwnerChannelFromChannelBrowser(source_field, request_uri) {
			// We're just proxying this function for now.
			return tryToRenameChannelFromChannelBrowser(source_field, request_uri);
		}
		
		// Opens a tree in the channel browser for a specific channel id, by loading data if necessary
		// and animating the transition. If data is found to have been loaded for this tree (i.e. no
		// list item with the no-playlists class is found, but the UL element for this tree still has
		// elements within it) then the list will be re-opened without fetching any data from the server.
		// Requires that the url for the channel by supplied.
		function openChannelInChannelBrowser(browser_id, channel_id, url_for_channel) {
			// Check for loaded data - we want MORE THAN 1 li element in the sublist for that channel,
			// or JUST 1 li with the no-playlists class applied.
			var li_count = $$("#playlists_in_channel_"+channel_id+"_browser_"+browser_id+" li").length;
			var np_count = $$("#playlists_in_channel_"+channel_id+"_browser_"+browser_id+" li.no-playlists").length;
			var loaded = (li_count > 0 && np_count < 1) || (np_count == 1);
			
			// Collapse others
			collapseAllChannelsInChannelBrowser(browser_id, channel_id)
			
			if(loaded) {
				// If data is loaded, trigger the animation function.
				animateOpeningOfChannelInChannelBrowser(browser_id, channel_id); 
			} else { 		
				// If not, trigger the load and exit, and the response will be calling the animation function.
				new Ajax.Request(url_for_channel, { asynchronous:true, evalScripts:true }); 
			}			
			return false;
		}
		
		// Animates the opening of a channel in the channel browser.
		function animateOpeningOfChannelInChannelBrowser(browser_id, channel_id) {
			if(browserIsIE()) {
				Element.show('playlists_in_channel_'+channel_id+'_browser_'+browser_id);
			} else {
				new Effect.BlindDown('playlists_in_channel_'+channel_id+'_browser_'+browser_id, {duration: blindDuration}); 
			}
			var toggle = $('channel_browser_toggle_for_channel_'+channel_id+'_browser_'+browser_id);
			toggle.addClassName("open");
			toggle.removeClassName("closed");
			return false;
		}
		
		
		// Collapses a tree in the channel browser for a specific channel id, by animating the transition
		// but leaving the elements present in the document. The elements are not removed so that the
		// tree may be opened again without reloading any data.
		function collapseChannelInChannelBrowser(browser_id, channel_id) {
			new Effect.BlindUp('playlists_in_channel_'+channel_id+'_browser_'+browser_id, {duration: blindDuration}); 
			$('channel_browser_toggle_for_channel_'+channel_id+'_browser_'+browser_id).className='closed';
			return false;
		}
		
		// Collapses ALL trees in the channel browser by iterating over found top-level elements
		// and calling collapseChannelInChannelBrowser for each. You may specify a channel ID
		// as an argument which will NOT be collapsed.
		function collapseAllChannelsInChannelBrowser(browser_id, except_channel_id) {
			$$('.tree_list_for_browser_'+browser_id).each(function(element) {
				var channelId = new RegExp("channel_([0-9]+)").exec(element.id)[1];
					if(parseInt(channelId) != except_channel_id) collapseChannelInChannelBrowser(browser_id, channelId);
			});
		}
		
	// PAGINATION/PAGINATOR BARS
	// --------------------------------------------------------------------------------------------------
	
		function hideAllPaginators() {
			$$('.nav_bar').each(function(element) {
				element.hide();
			});
			return false;
		}
	
		function showAllPaginators() {
			$$('.nav_bar').each(function(element) {
				element.show();
			});
			return false;
		}
		
	
	// EXPANDED VIDEO LISTINGS (HORIZONTAL LISTS)
	// --------------------------------------------------------------------------------------------------
	
		var expandedListingInstanceStates = {};
	
		function registerExpandedListing(listing_id, starting_page, num_pages) {
			expandedListingInstanceStates[listing_id] = {current_page: starting_page, num_pages: num_pages};
		}
	
		// Changes the page of an expanded listing bar by animating the transition and detecting the available pages.
		function expandedListingToPage(listing_id, page) {
			var inst = expandedListingInstanceStates[listing_id];
				if(page > inst.num_pages-1) page = 1;
				if(page < 1) page = inst.num_pages-1;
			// Fade current page
			Effect.BlindUp('elist_'+listing_id+'_page_'+inst.current_page, {duration: opacityFadeDuration, queue: {scope: 'elistscope', position: 'end'}});
			
			// Fade up new page list
			Effect.BlindDown('elist_'+listing_id+'_page_'+page, {duration: opacityFadeDuration, queue: {scope: 'elistscope', position: 'end'}});
			
			// Set state
			expandedListingInstanceStates[listing_id].current_page = page;
			return false;
		}
		
		function expandedListingToNextPage(listing_id) {
			var inst = expandedListingInstanceStates[listing_id];
			expandedListingToPage(listing_id, inst.current_page+1);
			return false;
		}
		
		function expandedListingToPreviousPage(listing_id) {
			var inst = expandedListingInstanceStates[listing_id];
			expandedListingToPage(listing_id, inst.current_page-1);
			return false;
		}
		
	// SMART PROMOS
	// ------------------------------------------------------------------------------------------------------------

		// Activated by changing the value of the 'width' dropdown on the smart promo config form.
		// Replaces the contents of the 'height' dropdown with a fresh set of options pertaining to 
		// the selected width.
		function applySmartPromoDimensionsToHeightField(selected_width, sizes_hash) {
			elem = $('smart_promo_height');
			elem.innerHTML = "";
			
			for(var i=0; i<sizes_hash[selected_width].length; i++) {
				var hs = sizes_hash[selected_width][i];
					elem.innerHTML += "<option value="+hs+">"+((hs=="inf")? "Automatic" : hs+" pixels")+"</option>";
			}
			
			return false;
		}
	
		// Used in the Smart Promo creation form. Updates the Movie field with movies from the currently-selected channel or playlist.
		function updateVideoConstructFieldWithContentsOfChannelOrPlaylistInSmartPromoForm() {
			// Get values
			var channel_id = $F('smart_promo_channel_id');
			// Empty dropdown to show loading...
			$('smart_promo_video_construct_id').disable();
			$('smart_promo_video_construct_id').innerHTML = '<option value="" selected="selected">Loading Movies...</option>';
			// Load from server
			new Ajax.Request('/my/promos/serve_owned_movies_in_channel.js?channel_id='+channel_id, {asynchronous:true, evalScripts:true});
			return false;
		}
		
		
	// PLAYER PAGE
	// ------------------------------------------------------------------------------------------------------------
	
		var currentPlayerPageTab = "about-this-movie"; // "advertisement", "comments", "about-this-movie"
		var playerPageTabs = ["advertisement", "comments", "about-this-movie"];
		function switchToPlayerPageTab(tabname) {
			if(tabname!=currentPlayerPageTab) {
				// Disable all tabs
				$$('#player-tabbed-sub-nav li').each(function(elem) {
					elem.removeClassName("current");
				});
				// Re-enable specific tab
				$(tabname+"-nav-tab").addClassName("current");
				// Switcharoo
				new Effect.BlindUp(currentPlayerPageTab+"-wrapper", {duration: blindDuration});//, queue: {scope: 'playerTabScope', position: 'end'}});
				new Effect.BlindDown(tabname+"-wrapper", {duration: blindDuration});//, queue: {scope: 'playerTabScope', position: 'end'}});
				currentPlayerPageTab = tabname;
			}
			return false;
		}
		
		var currentPlayerPageFunction;
		var currentPlayerPageFunctionLoadedAddressBook = false;
		function switchToPlayerPageFunction(funcname, address_book_uri) {
			if(funcname!=currentPlayerPageFunction) {
				// Hide others
				hideAllPlayerFunctions();
				// Show desired
				new Effect.BlindDown("player_function_"+funcname, {duration: blindDuration, queue: {position: 'end', scope: "playerFunctionFormScope"}});
				// Highlight corresponding button
				$('player_function_btn_'+funcname).addClassName("active");
				$('player_function_btn_'+funcname).removeClassName("neutral");
				currentPlayerPageFunction = funcname;
				// Now do clever things for the share form in particular
				if(funcname=="share" && address_book_uri && !currentPlayerPageFunctionLoadedAddressBook) {
					new Ajax.Request(address_book_uri, {asynchronous:true, evalScripts:true});
					currentPlayerPageFunctionLoadedAddressBook = true;
				}
			}
			return false;
		}
		
		function hideAllPlayerFunctions() {
			currentPlayerPageFunction = 0;
			$$('.player_function_form').each(function(form) { 
				if(form.visible()) new Effect.BlindUp(form.id, {duration: blindDuration, queue: {position: 'end', scope: "playerFunctionFormScope"}});
			});
			$$('.player_function_button').each(function(button) {
				button.removeClassName("active");
				button.addClassName("neutral");
			});
			return false;
		}
		
		function hideVideoShareForm() {
			hideAllPlayerFunctions();
			return false;
		}
		
	// MY CHANNEL - RENAMING and DRAG/DROP ORGANISATION
	// ------------------------------------------------------------------------------------------------------------
	var channelManagersInSortMode = {};
	
	// Switches a channel manager into sortable mode.
	function startOrganisingChannelsInChannelManager(manager_id, url) {
		// Flag that this manager is sorting
		channelManagersInSortMode[manager_id] = {sorting: true, responder_url: url, old_toolbar: $(manager_id+"_toolbar_reorder").innerHTML};
		// Switch toolbars over
		Element.show(manager_id+"_toolbar_reorder");
		Element.hide(manager_id+"_toolbar_idle");
		// Add sortable class
		$(manager_id+'_channel_list').addClassName("draggable");
		// Fire up the sortable library
		var sort = Sortable.create(manager_id+'_channel_list', {
						constraint: 'vertical', 
						revert: true,
						onupdate: function() {}
					});
	}
		// conversely:
		function finishOrganisingChannelsInChannelManager(manager_id) {
			// Switch toolbars over
			Element.hide(manager_id+"_toolbar_reorder");
			Element.show(manager_id+"_toolbar_idle");
			$(manager_id+"_toolbar_reorder").innerHTML = "<p>Saving...</p>";
			var toolbar_refill = channelManagersInSortMode[manager_id].old_toolbar;
			// Send request
			new Ajax.Request(channelManagersInSortMode[manager_id].responder_url, {
							asynchronous:true, 
							evalScripts:true,
							parameters: Sortable.serialize(manager_id+'_channel_list'),
							onComplete: function() {
								$(manager_id+"_toolbar_reorder").innerHTML = toolbar_refill;
								$(manager_id+'_channel_list').removeClassName("draggable");
							}
						});
			// Destroy sortable
			Sortable.destroy(manager_id+'_channel_list');
			// Flag that this manager is NOT sorting
			channelManagersInSortMode[manager_id] = false;
			return false;
		}
	
	// Starts renaming a channel in any channel manager on the page, by hiding
	// the 'read' markup and showing the 'write'.
	function startRenamingChannelInChannelManager(channel_id, manager_id) {
		var id_prefix = "channel_manager_channel_"+channel_id;
		Element.show(id_prefix+"_write");
		Element.hide(id_prefix+"_read");
		$(id_prefix).addClassName("editable");
		return false;
	}
			// conversely:
			function finishRenamingChannelInChannelManager(channel_id, manager_id) {
				var id_prefix = "channel_manager_channel_"+channel_id;
				Element.hide(id_prefix+"_write");
				Element.show(id_prefix+"_read");
				$(id_prefix).removeClassName("editable");
				return false;
			}
			
	// Shows the "create channel" form in a given channel manager.
	function showChannelCreateFormInChannelManager(manager_id) {
		var id = "channel_manager_"+manager_id+"_add_form_holder";
		(browserIsSafari())? Element.show(id) : Effect.Appear(id, {duration: opacityFadeDuration});
		var field = "channel_manager_"+manager_id+"_add_form_name";
		$(field).focus();
		return false;
	}
	
	// Prepares all channel browsers in the page for a dropped list of movies
	function makeAllChannelManagersInPageDroppable(onDropFunction) {
		$$('.channel-list li').each(function(elem) {
			// don't apply to "no playlists" items
			if(!elem.hasClassName('no-playlists')) {
				Droppables.add(elem.id, {
					hoverclass: "drop", 
					onDrop: onDropFunction
				});
				if(browserIsIE() && ieVersionIs(6)) {
					elem.undoPositioned();
				}
			}
		});
	}
	
	// Event callback from dropping a movie list on a playlist
	function addSelectedMoviesToChannelInChannelManager(draggable, droppable, event) {
		var movie_manager_id = draggable.id.substr(0, draggable.id.indexOf("_object_list"));
		var selected_movies = objectManagersInCopyMode[movie_manager_id].selected;
		var channel_id = parseInt(numeric.exec(droppable.id));
		// All dragged items will carry the current channel - in this case, take the referring channel from the first item.
		var v = selected_movies[0];
		var referring_channel = $F('resident_channel_for_video_construct_'+v+'_in_manager_'+movie_manager_id);
		var referring_playlist = $F('resident_playlist_for_video_construct_'+v+'_in_manager_'+movie_manager_id);
		var uri = "/channels/utils/add_video_batch_to_channel/"+channel_id+".js?video_constructs="+selected_movies.join(",")+"&referring_channel="+referring_channel+"&referring_playlist="+referring_playlist;
		new Ajax.Request(uri, {asynchronous:true, evalScripts:true});
		return false;
	}

	// Event callback from dropping a movie list on a playlist
	function addSelectedDocumentsToChannelInChannelManager(draggable, droppable, event) {
		var manager_id = draggable.id.substr(0, draggable.id.indexOf("_object_list"));
		var selected_documents = objectManagersInCopyMode[manager_id].selected;
		var channel_id = parseInt(numeric.exec(droppable.id));
		// All dragged items will carry the current channel - in this case, take the referring channel from the first item.
		var d = selected_documents[0];
		var referring_channel = $F('resident_channel_for_video_construct_'+d+'_in_manager_'+manager_id);
		var referring_playlist = $F('resident_playlist_for_video_construct_'+d+'_in_manager_'+manager_id);
		var uri = "/channels/utils/add_document_batch_to_channel/"+channel_id+".js?documents="+selected_documents.join(",")+"&referring_channel="+referring_channel+"&referring_playlist="+referring_playlist;
		new Ajax.Request(uri, {asynchronous:true, evalScripts:true});
		return false;
	}
	
	// OBJECT MANAGERS
	// ------------------------------------------------------------------------------------------------------
	
	// Shows the upload form for the current channel in
	var objectManagersUploadingLogos = {};
	function startUploadingLogoInMovieManager(manager_id) {
		// Flag this manager
		objectManagersUploadingLogos[manager_id] = {};
		// Switch bar and form
		Element.show(manager_id+"_logo_upload_form_wrapper");
		Element.hide(manager_id+"_toolbar_idle");
		return false;
	}
		function finishUploadingLogoInMovieManager(manager_id) {
			
		}
		function cancelUploadingLogoInMovieManager(manager_id) {
			// Clear properties
			objectManagersUploadingLogos[manager_id] = false;
			// Do switcharound
			Element.hide(manager_id+"_logo_upload_form_wrapper");
			Element.show(manager_id+"_toolbar_idle");
			return false;
		}
		
		function showIdentManagerPanelInObjectManager(manager_id) {
			Element.show(manager_id+"_ident_manager_panel");
			return false;
		}
		
		function hideIdentManagerPanelInObjectManager(manager_id) {
			Element.hide(manager_id+"_ident_manager_panel");
			return false;
		}
	
	var objectManagersInSortMode = {};
	// Kicks any object manager into sortable mode.
	function startOrganisingObjectsInObjectManager(manager_id, url, list_id_suffix) {
		// Flag that this manager is sorting
		objectManagersInSortMode[manager_id] = {sorting: true, responder_url: url, old_toolbar: $(manager_id+"_toolbar_reorder").innerHTML, list_suffix: list_id_suffix};
		// Switch toolbars over
		Element.show(manager_id+"_toolbar_reorder");
		Element.hide(manager_id+"_toolbar_idle");
		// Add sortable class
		$(manager_id+'_'+list_id_suffix).addClassName("draggable");
		// Fire up the sortable library
		var sort = Sortable.create(manager_id+"_"+list_id_suffix, {
						constraint: 'vertical', 
						revert: true,
						onupdate: function() {}
					});
		return false;
	}
		// and conversely:
		function finishOrganisingObjectsInObjectManager(manager_id) {
			// Switch toolbars over
			Element.hide(manager_id+"_toolbar_reorder");
			Element.show(manager_id+"_toolbar_idle");
			$(manager_id+"_toolbar_reorder").innerHTML = "<p>Saving...</p>";
			var toolbar_refill = objectManagersInSortMode[manager_id].old_toolbar;
			// Send request
			new Ajax.Request(objectManagersInSortMode[manager_id].responder_url, {
							asynchronous:true, 
							evalScripts:true,
							parameters: Sortable.serialize(manager_id+'_'+objectManagersInSortMode[manager_id].list_suffix),
							onComplete: function() {
								$(manager_id+"_toolbar_reorder").innerHTML = toolbar_refill;
								$(manager_id+'_'+objectManagersInSortMode[manager_id].list_suffix).removeClassName("draggable");
							}
						});
			// Destroy sortable
			Sortable.destroy(manager_id+"_"+objectManagersInSortMode[manager_id].list_suffix);
			$(manager_id+'_'+objectManagersInSortMode[manager_id].list_suffix).removeClassName("draggable");
			// Flag that this manager is NOT sorting
			objectManagersInSortMode[manager_id] = false;
			return false;
		}
		
	// Kicks any object manger into 'copy/move' mode
	var objectManagersInCopyMode = {};
	var objectManagerUnSelectedAlpha = 0.3;
	var objectManagerSelectedAlpha = 1.0;
	var objectManagerKeyPressHandlerBinding;
	var shiftKeyWasPressedLastEvent = false;
	var ctrlKeyWasPressedLastEvent = false;
	var altKeyWasPressedLastEvent = false;
	var cmdKeyWasPressedLastEvent = false;
	function startCopyingObjectsInObjectManager(manager_id, url, list_id_suffix, onDropFunction) {
		// Set vars:
		objectManagersInCopyMode[manager_id] = {selected: $A([]), draggable: false, dragging: false, list_suffix: list_id_suffix};
		// Switch toolbars over
		Element.show(manager_id+"_toolbar_batchcopy");
		Element.hide(manager_id+"_toolbar_idle");
		$(manager_id+'_'+list_id_suffix).addClassName("organise");
		// Spin up the onclick observer for each list item
		$$('#'+manager_id+'_'+list_id_suffix+' li').each(function(elem) {
			var binding = doSelectionOfObjectForBatchCopyInObjectManager.bindAsEventListener(elem, elem, manager_id);
			Event.observe(elem, 'click', binding);
		});
		// Spin up the draggable element
		objectManagersInCopyMode[manager_id].draggable = new Draggable(manager_id+"_"+list_id_suffix, {
			constraint: false,
			ghosting: true,
			revert: true,
			scroll: window,
			scrollSensitivity: 50,
			onStart: copyDragWasStartedInMovieManager,
			onDrag: copyDragMovedInMovieManager,
			onEnd: copyDragWasEndedInMovieManager
		});
		// Spin up the droppables
		makeAllChannelManagersInPageDroppable(onDropFunction);
		return false;
	}
		// and conversely:
		function finishCopyingObjectsInObjectManager(manager_id) {
			// Switch toolbars over
			Element.hide(manager_id+"_toolbar_batchcopy");
			Element.show(manager_id+"_toolbar_idle");
			$(manager_id+'_'+objectManagersInCopyMode[manager_id].list_suffix).removeClassName("organise");
			// Kill observer
			Event.stopObserving(window, 'click', objectManagerKeyPressHandlerBinding)
			// Clear vars:
			objectManagersInCopyMode[manager_id] = false;
			return false;
		}

		// Marks a movie as selected using the appropriate key modifier to choose a selection method. This is the 'root' event function called
		// when a list element is clicked.
		function doSelectionOfObjectForBatchCopyInObjectManager(event, movie_element, manager_id) {
			// Stash key modifiers from event
			shiftKeyWasPressedLastEvent = 	((event.shiftKey)? true : false);
		   	ctrlKeyWasPressedLastEvent = 	((event.ctrlKey)? true : false);
			altKeyWasPressedLastEvent = 	((event.altKey)? true : false);
			cmdKeyWasPressedLastEvent = 	((event.metaKey)? true : false);
			// Only does anything if the given manager is in copy mode but NOT being dragged right now -
			if(objectManagersInCopyMode[manager_id] && !objectManagersInCopyMode[manager_id].dragging) {
				// Get object_id from element id
				var object_id = parseInt(numeric.exec(movie_element.id));
				// Now decide which type of select to perform
				if(shiftKeyWasPressedLastEvent) {
					// Sequential select
					selectObjectForBatchCopyWithExistingRangeInObjectManager(object_id, manager_id);
				} else if(ctrlKeyWasPressedLastEvent || cmdKeyWasPressedLastEvent) {
					// Specific toggle
					toggleSelectionOfObjectForBatchCopyInObjectManager(object_id, manager_id);
				} else {
					// Clear selection and specifically re-select
					absolutelySelectObjectForBatchCopyInObjectManager(object_id, manager_id)
				}				
			}
			//say("Selected items: "+objectManagersInCopyMode[manager_id].selected);
			return false;
		}
			// Selects a SINGLE movie and no others - clears existing selection
			function absolutelySelectObjectForBatchCopyInObjectManager(object_id, manager_id) {
				// Desired for normal click events with no key modifier.
				clearSelectionInObjectManager(manager_id);
				selectObjectInObjectManager(object_id, manager_id);
			}
			function toggleSelectionOfObjectForBatchCopyInObjectManager(object_id, manager_id) {
				// Desired for when the COMMAND or CTRL key is pressed when clicking a movie - specifically selects or deselects a given movie.
				if(objectIsSelectedInObjectManager(object_id, manager_id)) deselectObjectInObjectManager(object_id, manager_id);
				else selectObjectInObjectManager(object_id, manager_id);
			}
			function selectObjectForBatchCopyWithExistingRangeInObjectManager(object_id, manager_id) {
				// Instinct would say that when selecting a range, the start point should be derived from a point in the selection
				// array closest-upwards to the clicked item. And that'll work, to a degree. The way we're actually going to do it,
				// is by drawing the range from the item *which was most recently selected* to the clicked item.
				// In this manner one can have finer control over the ranges drawn with minimum unpredictable list-chaos caused.
				var lastSelectedMovieID = objectManagersInCopyMode[manager_id].selected.last();
					// In the event that no movie was selected prior to this one, convert this selection to a single-absolute one.
					if(!lastSelectedMovieID) { 
						absolutelySelectMovieForBatchCopyInObjectManager(object_id, manager_id);
						return false;
					}
				// Assuming that didn't happen though, we're going to loop over the elements top to bottom and when we find either the start or end element, we will begin selecting elements
				// until the start/end counterpart is found.
				var startElementEncountered = false; var startElementID = manager_id+"_object_"+lastSelectedMovieID;
				var endElementEncountered = false; var endElementID = manager_id+"_object_"+object_id;
				$$('#'+manager_id+'_object_list li').each(function(elem) {
					// If we encountered both elements, then return early.
					if(endElementEncountered && startElementEncountered) return false;
					// Are we encountering an element?
					if(elem.id==endElementID) endElementEncountered = true;
					else if(elem.id==startElementID) startElementEncountered = true;
					// If we've encountered only one of the elements so far, begin marking as selected. Note to W3C people: please give JS a logical XOR operator. kthxbye.
					if((!endElementEncountered && startElementEncountered) || (endElementEncountered && !startElementEncountered) || elem.id==endElementID) {
						var movie_id = parseInt(numeric.exec(elem.id));
						selectObjectInObjectManager(movie_id, manager_id);
					}
				});
				return false;
			}

			// This is the granular function for selecting a specific movie. The third argument, elem_id, is optional and will be
			// used as the item's ID if given.
			function selectObjectInObjectManager(object_id, manager_id, elem_id) {
				var elem = $((elem_id)? elem_id : manager_id+"_object_"+object_id);
				// Apply class
				elem.removeClassName("unselected");
				// Fades up to 100% opacity
				//new Effect.Opacity(elem.id, {from: objectManagerUnSelectedAlpha, to: objectManagerSelectedAlpha, duration: 0})
				// And adds to selection array
				objectManagersInCopyMode[manager_id].selected.push(object_id);
				objectManagersInCopyMode[manager_id].selected = objectManagersInCopyMode[manager_id].selected.uniq();
			}
			// And conversely, for deselecting a specific movie:
			function deselectObjectInObjectManager(object_id, manager_id, elem_id) {
				var elem = $((elem_id)? elem_id : manager_id+"_object_"+object_id);
				// Strip class
				elem.addClassName("unselected");
				// Fades down to unselected opacity
				//new Effect.Opacity(elem.id, {from: objectManagerSelectedAlpha, to: objectManagerUnSelectedAlpha, duration: 0})
				// And removes from selection array
				objectManagersInCopyMode[manager_id].selected = objectManagersInCopyMode[manager_id].selected.without(object_id);
			}

			// Returns a boolean for whether the given movie is selected in the scope of the given manager_id
			function objectIsSelectedInObjectManager(object_id, manager_id) {
				return !$(manager_id+"_object_"+object_id).hasClassName('unselected');
			}

			// Clears the selection in a movie manager
			function clearSelectionInObjectManager(manager_id) {
				objectManagersInCopyMode[manager_id].selected = $A([]);
				$$('#'+manager_id+'_object_list li').each(function(elem) {
					elem.addClassName("unselected");
					//new Effect.Opacity(elem.id, {from: objectManagerSelectedAlpha, to: objectManagerUnSelectedAlpha, duration: 0})
				});
			}
		
		// Event handlers for movie manager draggable
		function copyDragWasStartedInMovieManager(draggable, event) {
			// By now the element should have been cloned by ghosting, so getting the list element by ID will return
			// the ghost. This is good, because we're now going to hide all the unselected items in it to demonstrate the list that's being moved.
			// Get manager id
			var manager_id = draggable.element.id.substr(0, draggable.element.id.indexOf("_object_list"));
			// Flag that a drag has started
			objectManagersInCopyMode[manager_id].dragging = true;
			
			var ghost = $$(".draggable_object_list").last();
			var ghost_items = $A(ghost.getElementsByTagName("li"));
			
			var selected_counter = 0;
			ghost_items.each(function(elem) {
				if(!elem.hasClassName('unselected')) {
					selected_counter += 1;
				} else {
					elem.hide();
				}
			});
			// Determine necessary padding - it is the combined height of all unselected elements before the very last selected element.
			var height_collector = 0;
			var encountered_selected = 0;
			ghost_items.each(function(elem) {
				if(encountered_selected >= selected_counter) return false;
				if(elem.hasClassName('unselected')) {
					height_collector += elem.getHeight();
				} else {
					encountered_selected += 1;
				}
			});
			ghost.setStyle({"padding-top": height_collector+"px"});
		}
		
		function copyDragMovedInMovieManager(draggable, event) {
			var manager_id = draggable.element.id.substr(0, draggable.element.id.indexOf("_object_list"));
			var ghost = $$(".draggable_object_list").last();
				console.log(ghost.getStyle("top"))
				ghost.setStyle({"top": "100px"});
		}
	
		function copyDragWasEndedInMovieManager(draggable, event) {
			// Get manager id
			var manager_id = draggable.element.id.substr(0, draggable.element.id.indexOf("_object_list"));
			// Flag that a drag has started
			$$(".draggable_object_list").each(function(elem) {
				elem.setStyle({"padding-top": "0"});
			});
			objectManagersInCopyMode[manager_id].dragging = false;
			$$(".draggable_object_list li").each(function(elem) {
				elem.show();
			});
		}
	

// UTILITY METHODS - CAN BE CALLED ANYWHERE, ANYTIME TO GET USEFUL INFORMATION OR PERFORM
// CERTAIN ACTIONS IN A BROWSER-SAFE MANNER
// ------------------------------------------------------------------------------------------------

function findElementPosition(obj) {
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		curleft = obj.offsetLeft;
		curtop = obj.offsetTop;
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft;
			curtop += obj.offsetTop;
		}
	}
	return [curleft,curtop];
}

// Returns the effective viewport dimensions *minus scrollbars*
function getWindowDimensions() {
	// Find the 'naked' window dimensions
	var _w = (browserIsIE()? document.body.clientWidth : window.innerWidth);
	var _h = (browserIsIE()? document.body.clientHeight : window.innerHeight);
	var sState = getBrowserScrollingState(_w,_h);
	return {
		w: 	_w - ((sState.y_scroll && browserIncludesScrollBarsInWindowDimensions())? getBrowserScrollBarSize() : 0),
		h: 	_h - ((sState.x_scroll && browserIncludesScrollBarsInWindowDimensions())? getBrowserScrollBarSize() : 0)
	};
}

// Returns the effective document size
function getDocumentDimensions() {
	// We know that we're going to be in STRICT mode for all browsers.
	// Based on this, IE 6, Safari, KHTML and MOZ will respond to document.body.clientHeight/clientWidth to generate the document size
	// Opera will want document.documentElement.clientHeight/clientWidth
	var o = (browserIsOpera()? document.documentElement : document.body);
	return {
		w: o.clientWidth,
		h: o.clientHeight
	};
}

function browserIncludesScrollBarsInWindowDimensions() {
	// Returns TRUE if the browser returns the window dimensions as if the scroll bars were not present.
	if(browserIsSafari()) return false;
	if(browserIsMozilla()) return true;
	// IE
}

//Returns the scrollbar dimensions dependent on the current browser
function getBrowserScrollBarSize() {
	return 18;
}

// Indicates if the document will be scrolling inside the browser on either dimension
// (in order words, is the document larger than the viewport?)
// If you are calling this function from another that may cause recursion problems, you may include the window width/height
// as arguments to prevent the getWindowDimensions() function from being called
function getBrowserScrollingState(winW, winH) {
	var docDims = getDocumentDimensions();
	var winDims = (winW && winH)? {w: winW, h: winH} : getWindowDimensions();
	return {
		x_scroll: (docDims.w>winDims.w),
		y_scroll: (docDims.h>winDims.h)
	};
}

// Retrieves the current scrolling offset in the document
function getBrowserScrollingOffset() {
	return {
		top: 	(browserIsIE() ? document.documentElement.scrollTop :  window.pageYOffset),
		left:	(browserIsIE() ? document.documentElement.scrollLeft : window.pageXOffset)	
	};
}

// BROWSER DETECTION
function browserIsIE(version) {
	return (navigator.appName.indexOf("Microsoft")!=-1) && (!version);
}

	function ieVersionIs(version) {
		return version == ieVersion();
	}
	
	function ieVersionIsAtLeast(version) {
		return version >= ieVersion();
	}

	function ieVersion() {
	    var ua = navigator.userAgent;
	    var MSIEOffset = ua.indexOf("MSIE ");

	    if (MSIEOffset == -1) {
	        return 0;
	    } else {
	        return parseFloat(ua.substring(MSIEOffset + 5, ua.indexOf(";", MSIEOffset)));
	    }
	}

function browserIsOpera(version) {
	return (window.opera)? true : false;
}
function browserIsMozilla(version) {
	return (navigator.appName=="Netscape" && !browserIsSafari());
}
function browserIsSafari(version) {
	return (navigator.vendor && navigator.vendor.indexOf("Apple")!=-1);
}

// RAILS FORM UTILITIES

	// Takes a rails form field name, e.g. object[property] and returns an ID for that field - e.g. object_property
	function railsFormFieldNameToID(name) {
		name = name.split("[");
		return name[0]+"_"+name[1].split("]")[0];
	}
	// Takes a rails form field ID, e.g. object_property and returns a name for that field - e.g. object[property]
	// In this case, the object identifier (e.g. 'object') must be given as some object identifiers contain their own underscores - 
	// for instance, video_construct_name cannot be reliably parsed without knowing that the object identifier is video_construct and not just video.
	function railsFormFieldIDToName(object_identifier, id) {
		return (object_identifier)+"["+(id.split(object_identifier+"_")[1])+"]"
	}

// Causes external links to launch in a new window, and adds a note of this behaviour
// to the link's title attribute.
function externalLinks() {	
	if (document.getElementsByTagName) {		
		var anchors = document.getElementsByTagName('a');		
		for (var i = 0; i < anchors.length; i++) {			
			if (anchors[i].getAttribute('href') && anchors[i].getAttribute('rel') == 'external') {
				anchors[i].target = '_blank';
				if (anchors[i].getAttribute('title')) {
					anchors[i].title += ' (Opens in a New Window)';
				}
			}
		}		
	}
}

function beta_feature_warning() { 
	alert("We're currently still in beta and this part of the site isn't open yet.  We appreciate you bearing with us while construction on the site is taking place."); 
}

// FIXES.JS INCLUSION
// --------------------------------------------------------------------------------------------------------------------------

/*  --------------------------------------------------------

    Finds any Button, Submit button or anchor elements
	with the 'btn' class applied and adds some extra html 
	to hang css classes off. Submit buttons need to include 
	the id of their containing form in their own id with 
	'_submit' appended to it, otherwise they will no 
	longer submit the form.

 -------------------------------------------------------- */


var btn = {
	
    init : function() {
		
        if (!document.getElementById || !document.createElement || !document.appendChild) return false;
        anchors = btn.getElementsByClassName('btn(.*)');
		
        for (i = 0; i < anchors.length; i++) {
	
			// Check to see if the button was already processed. That makes the routine safe to run more than once.
			if(anchors[i].getElementsByTagName("i").length > 0) continue;
			
            if ((anchors[i].tagName.toLowerCase() == "input" && anchors[i].type.toLowerCase() == "submit") || anchors[i].tagName.toLowerCase() == "button") {
				
				
				
				if (anchors[i].tagName.toLowerCase() == "button") {
					var text = anchors[i].firstChild;
				} else {
					var text = document.createTextNode(anchors[i].value);
				}
				
                var new_anchor = document.createElement("a");
				
                new_anchor.className = anchors[i].className;
				new_anchor.rel = 'nofollow';
                new_anchor.id = anchors[i].id;
                anchors[i] = anchors[i].parentNode.replaceChild(new_anchor, anchors[i]);
                anchors[i] = new_anchor;
                anchors[i].style.cursor = "pointer";
				
				
				anchors[i].onclick = function() {
					var form_id = this.id.replace('_submit', '');
					var f = $(form_id);
					var onsubmitresponse;
						// If the form has an onsubmit handler, trigger it and check the response
						if(f.onsubmit) onsubmitresponse = f.onsubmit();
						if(!f.onsubmit || onsubmitresponse) f.submit();
						return false;
				};
				
				
            } else if (anchors[i].tagName == "A") {
				
                var text = anchors[i].firstChild;				
				
            } else { 
			
				return false;
				
			};
			
            var i_tag = document.createElement('i');
            var span_tag = document.createElement('span');
			
			var cls = String(text.className);
			
			if(cls.substring(0, 4) == "icon") {
					
				// there's an icon before the text
				// keep image for later
				var img = text;
				// use next node along for text
				text = text.nextSibling;
			}
				
			if (img) {
				// there's an icon - insert it into the span
				span_tag.appendChild(img);
				img = null;
			}
			
            span_tag.appendChild(text);
            anchors[i].appendChild(span_tag);
            anchors[i].appendChild(i_tag);
			
        }
		
		
		
    },
	
	
	
    addEvent : function(obj, type, fn) {
		
        if (obj.addEventListener) {
			
            obj.addEventListener(type, fn, false);
			
        } else if (obj.attachEvent) {
			
            obj["e"+type+fn] = fn;
            obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
            obj.attachEvent("on"+type, obj[type+fn]);
			
        }
		
    },
	
	
	
    getElementsByClassName : function(className, tag, elm) {
		
        var testClass = new RegExp("(^|\s)" + className + "(\s|$)");
        var tag = tag || "*";
        var elm = elm || document;
        var elements = (tag == "*" && elm.all)? elm.all : elm.getElementsByTagName(tag);
        var returnElements = [];
        var current;
        var length = elements.length;
		
        for(var i=0; i<length; i++){
			
            current = elements[i];
			
            if(testClass.test(current.className)){
                returnElements.push(current);
            }
			
        }
        return returnElements;
		
    }
	
}










/*  --------------------------------------------------------

    If visitor is using Safari, this will add the 'search' 
	field attributes to the mini search text field. If 
	using any other browser, it will add the extra span 
	tags to style the search field to look like the Safari 
	search box.
	
	-> <span id="sbl"></span>
	-> <span id="sb"><input... /></span>
	-> <span id="sbr"></span>

 -------------------------------------------------------- */

function customSearchBox() {
	
	if (!document.getElementById || !document.createElement || !document.appendChild) return false;
	var search_field = document.getElementById('search_query');
	
	if(search_field) {

		var parent = search_field.parentNode;

		var left_end = document.createElement('span');
		var right_end = document.createElement('span');
		var middle = document.createElement('span');

		left_end.id = 'sbl';
		right_end.id = 'sbr';
		middle.id = 'sb';

		middle.appendChild(search_field);
		parent.appendChild(left_end);
		parent.appendChild(middle);
		parent.appendChild(right_end);

	}
						
	
	if(search_field && navigator.userAgent.indexOf('Opera') != -1) {
		// if using Opera, padding in the search field will be all out of whack
		search_field.style.padding = '0px 0px 0px 2px';
	}
	
	
	if (search_field && navigator.userAgent.toLowerCase().indexOf('safari') > 0) {
		// visitor is using safari - fix the dodgy padding
		search_field.style.padding = '1px 3px 0 3px';
	}
		
}








/*  --------------------------------------------------------

    Sets the Search field to the default value specified. 
	Toggle between default value and blank on blur/focus 
	respectively. Does nothing if value has been changed 
	by user. 

 -------------------------------------------------------- */


function setDefaultSearchValueTo(default_value) {
		
	if (!document.getElementById || !document.createElement || !document.appendChild) return false;
	var search_field = document.getElementById('search_query');
	
	if(search_field) {
		
		search_field.onclick = function() { search_field.focus(); };
		search_field.onfocus = function() {
			if(search_field.value == default_value) {
				search_field.value = "";
			}
		};
		search_field.onblur = function() {
			if(search_field.value == "") {
					search_field.value = default_value;
			}
		};
		search_field.value = default_value;
	
	}
	
}
	






/*  --------------------------------------------------------

    Main navigation tabs need some extra hooks to hang 
	styles off. Wrap the anchor text in a span tag and 
	append an empty i tag to all links in the main menu
	
	-> <a href="#">
	->   <span>Link Text</span>
	->   <i></i>
	-> </a>

 -------------------------------------------------------- */

function fixTabs() {
	
	if (!document.getElementById || !document.createElement || !document.appendChild) return false;
	tab_list = document.getElementById('main_nav');
	
	if(tab_list) {
		
		for (i = 0; i < tab_list.childNodes.length; i++) {
			
			if (tab_list.childNodes[i].nodeName == "LI") {
				
				var i_tag = document.createElement('i');
				var span_tag = document.createElement('span');
			
				var link_text = tab_list.childNodes[i].firstChild.firstChild;
				
				span_tag.appendChild(link_text);
				tab_list.childNodes[i].firstChild.appendChild(span_tag);
				tab_list.childNodes[i].firstChild.appendChild(i_tag);

			}
			
		}
		
	}
	
}


// SCRIPT.ACULO.US EXTENSIONS
// --------------------------------------------------------------------------------------------------------------------------

Effect.ResizeTo = Class.create(); // Credit to by Jake Richardson {jaecob.gmail.com} - thanks Jake!
Object.extend(Object.extend(Effect.ResizeTo.prototype, Effect.Base.prototype), {
  	initialize: function(element, toWidth, toHeight) {

	    this.element      = $(element);
	    this.toWidth      = toWidth;
	    this.toHeight     = toHeight;

	    this.originalWidth  = parseFloat(Element.getStyle(this.element,'width')  || 0);
	    this.originalHeight = parseFloat(Element.getStyle(this.element,'height') || 0);

	    this.effectiveWidth = this.toWidth 
	                        - parseFloat(Element.getStyle(this.element,'margin-left') || 0) 
	                        - parseFloat(Element.getStyle(this.element,'margin-right') || 0) 
	                        - (document.compatMode == 'BackCompat' ? 0 : // height includes padding & border in IE BackCompat mode
	                            parseFloat(Element.getStyle(this.element,'padding-left') || 0) 
	                            + parseFloat(Element.getStyle(this.element,'padding-right') || 0) 
	                            + parseFloat(Element.getStyle(this.element,'border-left-width') || 0)
	                            + parseFloat(Element.getStyle(this.element,'border-right-width') || 0));

	    this.effectiveHeight = this.toHeight
	                        - parseFloat(Element.getStyle(this.element,'margin-top') || 0) 
	                        - parseFloat(Element.getStyle(this.element,'margin-bottom') || 0) 
	                        - (document.compatMode == 'BackCompat' ? 0 : // height includes padding & border in IE BackCompat mode
	                            parseFloat(Element.getStyle(this.element,'padding-top') || 0) 
	                            + parseFloat(Element.getStyle(this.element,'padding-bottom') || 0) 
	                            + parseFloat(Element.getStyle(this.element,'border-top-width') || 0)
	                            + parseFloat(Element.getStyle(this.element,'border-bottom-width') || 0));

	    this.options = arguments[3] || {};

	    if (this.effectiveWidth < 0) this.effectiveWidth = 0;
	    if (this.effectiveHeight < 0) this.effectiveHeight = 0;

	    //if (this.originalWidth  this.effectiveWidth &&
	    //    this.originalHeight  this.effectiveHeight) {
	    //  return;
	    //}
		this.start(this.options);
	},
	update: function(position) {
	  	widthd  = this.effectiveWidth * (position) + this.originalWidth * (1 - position);
	  	heightd = this.effectiveHeight * (position) + this.originalHeight * (1 - position);
	  	this.setPosition(widthd, heightd);
	},
	setPosition: function(widthd, heightd) {
	  	this.element.style.width = widthd+'px';
	  	this.element.style.height = heightd+'px';
	}
});

// DEBUG
// --------------------------------------------------------------------------------------------------------------------------
		
		function say(str) {
			if(verbose && console) console.log(str);
			return false;
		}

// FIRE!
// --------------------------------------------------------------------------------------------------------------------------
		
		window.onload = initBehaviours;