/*
	Recipe Nut
	Spring 2010
	
	The functions in this file are intended to be available on every page. Most of them rely on jQuery 1.3.2.
*/

/*
	Functions to call when the DOM is ready.
*/
$(document).ready(function() {
	show_hide_all_categories();
	form_text_default();
	form_password_default();
	legal_blossoms();
	login_blossom();
	ra_recipe_actions();
	large_checkbox();
	multiselects_into_blossoms();
	require_1_category();
	reply_to_review();
	recipe_ingredients();
	recipe_directions();
	nav();
	tab_nav();
	recipe_print_button();
	//x_chars_remaining();
	save_recipe();
	follow_user();
	sort_dropdown();
	mobile_safari_hoverable();
	animate_hazel();
	viewport_cta();
});


/*
	form_text_default()
	
	Use a text input's (or textarea's) title attribute as a default value
	Pass an input or array of inputs
	Pass nothing to initialize all text inputs and textareas on the page
*/ 
function form_text_default(element) {
	
	if (element == undefined) {
		element = $('input[type="text"], textarea');
	}
	
	$(element).each(function() {
		// on focus, if the value and title match, clear the value
		$(this).focus(function() {
			if (this.value == this.title) {
				this.value = '';
			}
		});
		// on blur, if the value is empty, and the default value matches the title,
		// set the value to the title
		$(this).blur(function() {
			if (this.value == '' && this.defaultValue == this.title) {
				this.value = this.title;
			}
		});
		// if an element's values equals its title, clear it before submitting its form
		$(this.form).submit(function(event) {
			for (var i=0,j=this.length;i<j;i++) {
				if (this.elements[i].value == this.elements[i].title) {
					this.elements[i].value = '';
				}
			}
		});
	});
}

/*
	form_password_default()
	
	Similar to form_text_default(), but for password inputs
	Instead of swapping the value, it creates a duplicate text input to hold the title value
*/
function form_password_default(element) {
	if (element == undefined) {
		element = $('input[type="password"]');
	}
	$(element).each(function() {
		if (this.title != '' && this.value == '') {
			this.duplicate = createAppend('input',{
				'type' : 'text',
				'className' : this.className,
				'id' : this.id + '_duplicate',
				'value' : this.title,
				'original' : this
			});
			$(this.duplicate).insertAfter(this);
			$(this).hide();
			$(this.duplicate).focus(function() {
				$(this).hide();
				$(this.original).show();
				this.original.focus();
			});
			$(this).blur(function() {
				if (this.value == '') {
					$(this).hide();
					$(this.duplicate).show();
				}
			});
		
		}
	});
}


/*
	Animations
	
	Hazel (the squirrel) is animated in a couple places on the homepage. First, in the sign up CTA that logged-out users see. Second, in the main logo.
*/

function animate_hazel() {
	// Only animate Hazel if this is the homepage.
	if (document.location.pathname == '/' && document.location.search == '') {
		// Load the sprite for the main logo animation, so it is ready when we start the logo animation.
		var hazel_sprite = new Image();
		hazel_sprite.sprite_path = '/images/hazel_sprite.png';
		hazel_sprite.src = hazel_sprite.sprite_path;
		
		// If the CTA exists, animate Hazel in it. Otherwise, just animate her in the logo.
		// Wait two seconds before animating.
		setTimeout(function() {($('#ani_small_body')[0]) ? animate_hazel_cta(hazel_sprite) : animate_hazel_logo(hazel_sprite)},2000);
	}
}

function animate_hazel_cta(hazel_sprite) {
	// Find the existing small hazel graphic in the CTA.
	var hazel_small = $('#ani_small_body')[0];
	// Animate!
	$(hazel_small).animate(
		// Hazel disappears from the CTA.
		{top: '50'},3000,'linear',
		// After Hazel disappears from the CTA, she reappears in the logo.
		function() {
			animate_hazel_logo(hazel_sprite);
		}
	);
}

function animate_hazel_logo(hazel_sprite) {
	// Prep the existing static elements so we can position our animation.
	// wrapper
	var wrapper = $('.wrapper')[0];
	// nav
	var nav = $('.nav')[0];
	nav.style.zIndex = 1119;
	
	// Create a stage for the animation (and make it link to the same place as the logo).
	var stage = createAppend('a',{href: $('.logo a')[0], className: 'logo_js'},wrapper);
	$(stage).css({
			'width':'100px',
			'height':'105px',
			'overflow':'hidden',
			'position':'absolute',
			'top':'0',
			'left':'-26px'
		}
	);
	
	// Create all the animatable parts of Hazel, using create_animation_cell().
	nut = create_animation_cell(45,58, 0,-81, 48,16, 1118, hazel_sprite.sprite_path, stage);
		// Add a class to the logo p element that hides the nut so we can animate over it
		$('.logo').addClass('logo_hide_nut');
	nut_tilt = create_animation_cell(46,52, -45,-81, 51,22, 1116, hazel_sprite.sprite_path, stage);
		$(nut_tilt).hide();
	arm = create_animation_cell(23,49, -92,-81, 76,92, 1014, hazel_sprite.sprite_path, stage);
	hands = create_animation_cell(61,12, 0,0, 38,92, 1112, '', stage);
		left_hand = create_animation_cell(14,12, -93,0, 0,0, 0, hazel_sprite.sprite_path, hands);
		right_hand = create_animation_cell(14,12, -93,-12, 47,0, 0, hazel_sprite.sprite_path, hands);
	hazel = create_animation_cell(93,81, 0,0, 0,92, 1008, hazel_sprite.sprite_path, stage);
		// Determine which icon to use, depending on the season (set by PHP)
		// apron_nut = create_animation_cell(12,15, -93,-55, 63,66, 1110, hazel_sprite.sprite_path, hazel);
		if (typeof window.ani_season != 'string') var ani_season; // In case ani_season wasn't set by PHP
		switch (window.ani_season) {
			case 'winter':
				apron_snow = create_animation_cell(13,12, -93,-31, 63,67, 1110, hazel_sprite.sprite_path, hazel);
				break;
			case 'fall':
				apron_leaf = create_animation_cell(21,11, -93,-70, 58,67, 1110, hazel_sprite.sprite_path, hazel);
				break;
			case 'valentines':
				apron_valentines = createAppend('span', {className: 'ani_cell ani_icon_valentines'}, hazel);
				break;
			default:
				apron_flower = create_animation_cell(13,12, -93,-43, 63,67, 1110, hazel_sprite.sprite_path, hazel);
		}
	
	// Animate!
	$(arm).animate({top: '-=35'},3000,'linear',function() {
			$(arm).animate({top: '+=75'},400,'linear');
			$(nut).hide();
			$(nut_tilt).show();
			$(nut_tilt).animate({top: '+=80'},400,'linear',function() {
					setTimeout(function() {
						$(hands).animate({top: '-=12'},200,'linear',function() {
									hands.style.zIndex = '1120';
									$(hands).animate({top: '+=5'},100,'linear');
									$(hazel).animate({top: '-=82'},600);
								}
							);
						},
					'500')
				}
			);
		}
	);
}

// Create a div that represents an animation cell, using a CSS sprite background. Optionally append the sprite to a parent node.
function create_animation_cell(w,h, bl,bt, l,t, z, sprite_path, parent) {
	parent = parent || null;
	var cell = createAppend('span',null,parent);
	$(cell).css({
			'width': w + 'px',
			'height': h + 'px',
			'background-position': bl + 'px ' + bt + 'px',
			'left': l + 'px',
			'top': t + 'px',
			'z-index': z,
			'background-image': 'url(' + sprite_path + ')',
			'background-repeat': 'no-repeat',
			'position': 'absolute',
			'display': 'block'
		});
	//cell.style.backgroundColor = 'rgba(255,0,0,.5)'; // diagnostic
	return cell;
}


/*
	Blossoms
	
	Blossoms are layers that are placed over the content to show an inline form, list of actions, block of text, etc.
	All blossom functions use bl_create_blossom_container() and related functions prefixed with bl_
*/


/*
	login_blossom()
	
	Creates a blossom containing a login form (for the "Log In" link in the header)
	The form is loaded with AJAX when the page loads so that it is available when the user clicks the link
*/
function login_blossom() {
	
	// Look for the first login form on the page (should be the "Log In" link in the header)
	link = $('a[href^="/login"]')[0];
	
	// If the link exists, then the user is logged out
	if (link) {
		// bl_create_blossom_container() needs these link params, so set them manually
		link.params = [];
		link.params.type = 'login';
		link.params.id = 'header';
		
		// Create the blossom
		var blossom = bl_create_blossom_container(link);
		
		// Hide the blossom immediately: only show it when the user clicks the log in link
		$(blossom).hide();
		
		// Get the login form from the server right away, but don't show it until the user clicks the link
		$.ajax({
			url: link.href,
			method: 'get',
			success: function(data) {
					try {
						// Evaluate the JSON sent by the server
						var response = eval('(' + data + ')');
						
						// Add a class to the blossom
						$(blossom).addClass('blossom_login');
						
						// Create the blossom title
						blossom.h1 = createAppend('h1',{innerHTML : response['title']},blossom.contents);
						
						// Create a close link
						blossom.close = createAppend('p',{className : 'blossom_close'},blossom.contents);
						blossom.closeA = createAppend('a',{href : '#login'},blossom.close);
							blossom.closeA.innerHTML = 'Close';
						$(blossom.closeA).click(function(event) {
							event.preventDefault();
							// Close the blossom
							blossom.wilt();
						});
						
						// Add the form
						$(response['form']).appendTo(blossom.contents);
						
						// Reposition the blossom
						bl_place_blossom_horizontal(blossom,blossom.link);
					}
					catch(e) {
						// If we couldn't create the login form, destroy the blossom so that the link click will be handled normally
						blossom.decompose();
					}
				},
			error: function(data,e) {
				// If we couldn't create the login form, destroy the blossom so that the link click will be handled normally
				blossom.decompose();
			}
		});
		
		$(link).click(function(event) {
			// Only prevent the link from firing if the blossom was loaded successfully (otherwise, let the browser handle the link click normally)
			if (this.blossom) {
				event.preventDefault();
				$(this.blossom).show();
				// Focus on the first input
				var fields = $(blossom).find('input[type!="hidden"], select, textarea');
                fields[0].focus();
			}
		});
	}
}

/*
	legal_blossoms()
	
	Creates blossoms containing the Terms of Use or Privacy content when certain links are clicked
	(mostly in forms, when we don't want the user to leave the page)
*/
function legal_blossoms(element) {
	
	if (!element) element = $('.register a[href^="/terms"], .register a[href^="/privacy"], .form_share a[href^="/privacy"], .publish a[href^="/terms"]');
	
	$(element).click(function(event) {
		event.preventDefault();
		
		if (this.blossom) {
			$(this.blossom).show();
		}
		else {
			this.params = [];
			this.params.type = this.pathname.replace('/','');
			this.params.id = this.params.type;
			
			var blossom = bl_create_blossom_container(this);
			
			// A message for while the form is loaded with AJAX
			blossom.loader = createAppend('p',{className : 'blossom_loader', innerHTML : 'One sec…'},blossom.contents);
			
			// Get the form for this recipe and action from the server
			$.get(this.href, function(data) {
				// Hide the loader
				$(blossom.loader).hide();
				
				// Add a class to the blossom
				$(blossom).addClass('blossom_terms');
				
				// Evaluate the JSON sent by the server
				var response = eval('(' + data + ')');
				
				// Create the blossom title
				blossom.h1 = createAppend('h1',{innerHTML : response['title']},blossom.contents);
				
				// Create a close link
				blossom.close = createAppend('p',{className : 'blossom_close'},blossom.contents);
				blossom.closeA = createAppend('a',{href : '#' + blossom.link.params.id},blossom.close);
					blossom.closeA.innerHTML = 'Close';
				
				// Add the contents
				blossom.tos_contents = createAppend('div',{className : 'blossom_terms_contents'},blossom.contents);
				$(response['contents']).appendTo(blossom.tos_contents);
				
				// Find links that point back to the calling div and make those links close the blossom
				$(blossom).find('a[href$="#' + blossom.link.params.id + '"]').click(function(event) {
					event.preventDefault();
					// Close the blossom
					blossom.wilt();
				});
					
			});
		}
	});
	
}


/*
	Recipe action functions (prefix = ra_)
	
	These functions create blossoms for recipe "Share" and "Flag" links
*/

// recipes[] will contain a list of recipes, indexed by recipe id
var recipes = [];

/*
	ra_recipe_actions()
	
	Find the links that share or flag a recipe, and make them use blossoms
*/
function ra_recipe_actions() {
	$('.recipe_short_actions a[href^="/recipe/share"], .note a[href^="/recipe/share"], a[href^="/recipe/flag"], a[href^="/recipe/comment-flag"]').each(function(i) {
		
		// params will hold the type and recipe id
		this.params = ra_parse_action(this.href);
		
		// Make a new object in the array recipes keyed to this recipe id
		recipes[this.id] = {};
		// Allow this link to refer back to its recipe object
		this.recipe = recipes[this.id];
		// Create an array to contain this recipe's blossoms
		this.recipe.blossoms = [];
		
		$(this).click(function(event) {
			event.preventDefault();
			if (this.recipe.blossoms[this.params.type]) {
				// If the blossom for this link type exists, show it
				$(this.recipe.blossoms[this.params.type]).show();
				// Focus on the first input
				if($(this.recipe.blossoms[this.params.type]).find('input[type!="hidden"], select, textarea').length) {
					$(this.recipe.blossoms[this.params.type]).find('input[type!="hidden"], select, textarea')[0].focus();
				}
			}
			else {
				// Otherwise, create the blssom for this link type
				var blossom = bl_create_blossom_container(this);
				// Add the new blossom to this recipe's blossoms array
				this.recipe.blossoms[this.params.type] = blossom;
				blossom.recipe = this.recipe;
				
				// Add contents to the blossom depending on the type of link
				switch (this.params.type) {
					case 'share':
						ra_create_share_links(this);
					break;
					default:
						ra_get_form(this);
				}
			}
			// Position the blossom
			bl_place_blossom(this.recipe.blossoms[this.params.type],this);
		});
	});
	
	ra_recipe_actions_email();
}

function ra_recipe_actions_email() {
	$('.recipe_social a[href^="/recipe/share"]').each(function(i) {
		
		// params will hold the type and recipe id
		this.params = ra_parse_action(this.href);
		
		// Make a new object in the array recipes keyed to this recipe id
		recipes[this.id] = {};
		// Allow this link to refer back to its recipe object
		this.recipe = recipes[this.id];
		// Create an array to contain this recipe's blossoms
		this.recipe.blossoms = [];
		
		$(this).click(function(event) {
			event.preventDefault();
			if (this.recipe.blossoms[this.params.type]) {
				// If the blossom for this link type exists, show it
				$(this.recipe.blossoms[this.params.type]).show();
				// Focus on the first input
				$(this.recipe.blossoms[this.params.type]).find('input[type!="hidden"], select, textarea')[0].focus();
			}
			else {
				// Otherwise, create the blssom for this link type
				var blossom = bl_create_blossom_container(this);
				$(blossom).addClass('blossom_share');
				// Add the new blossom to this recipe's blossoms array
				this.recipe.blossoms[this.params.type] = blossom;
				blossom.recipe = this.recipe;
				
				ra_get_form(this);
			}
			// Position the blossom
			bl_place_blossom(this.recipe.blossoms[this.params.type],this);
		});
	});
}

// Parses a path to get the link type (i.e., its action within the recipe controller) and recipe id
function ra_parse_action(path) {
	var params = [];
	params.type = path.match(/\/recipe\/([^\/]+)\//)[1];
	params.id = path.match(/.+\/(\d+).*/)[1];
	params.slug = path.match(/\/recipe\/[^\/]+\/([^\/]+)\//)[1];
	return params;
}

// Creates a list of social links and appends it to a blossom
function ra_create_share_links(link) {
	var blossom = link.recipe.blossoms[link.params.type];
	// Create a ul for the links
	blossom.social = createAppend('ul',{className : 'blossom_social_links', id : 'recipe_' + link.params.id + '_social_links'},blossom.contents);
	// The beginning of a URL with the recipe controller
	var url_recipe = document.location.protocol + '//' + document.location.host + '/recipe';
	// The URL for this recipe's view action
	var url_view = escape(url_recipe + '/' + link.params.slug);
	var result_uri = (!document.location.pathname.match('recipe/create')) ? document.location.pathname + document.location.search : url_view;
	// The list of social links, their types (classes), and URLs
	var social_links = {
		'facebook' : {'text' : 'Post', 'url' : 'http://www.facebook.com/sharer.php?u=' + url_view, 'target' : '_blank', 'title' : 'Opens in a new window'},
		'twitter' : {'text' : 'Tweet', 'url' : 'http://twitter.com/share?text=I’m a recipe nut. Check this one out: ' + url_view + ' %23RecipeNut', 'target' : '_blank', 'title' : 'Opens in a new window'},
		'email' : {'text' : 'Email', 'url' : url_recipe + '/share/' + link.params.slug + '/' + link.params.id}
	}
	// For each social link, create a list item and link
	for (social_link in social_links) {
		var li = createAppend('li',{className : 'recipe_' + social_link},blossom.social);
		var a = createAppend('a',{href : social_links[social_link]['url'], innerHTML : social_links[social_link]['text']},li);
		if (social_links[social_link]['target']) a.target = social_links[social_link]['target'];
		if (social_links[social_link]['title']) a.title = social_links[social_link]['title'];
		a.link = link;
		// Make the Email link use AJAX to get the email recipe form
		if (social_link == 'email') {
			$(a).click(function(event) {
				event.preventDefault();
				// Make the list of social links invisible
				$(blossom.social).css('visibility','hidden');
				if (blossom.form_container) {
					$(blossom.form_container).show();
					if (blossom.social) $(blossom.social).hide();
				}
				else {
					ra_get_form(this.link);
				}
			});
		}
	}
	// Create a close link
	blossom.close = createAppend('p',{className : 'blossom_close_small'},blossom.social);
	blossom.closeA = createAppend('a',{href : '#' + blossom.link.params.id},blossom.close);
		blossom.closeA.innerHTML = 'Close';
	$(blossom.closeA).click(function(event) {
		event.preventDefault();
		// Close the blossom
		blossom.wilt();
	});
}

// Get a form from the server and append it to a blossom
function ra_get_form(link) {
	var blossom = link.recipe.blossoms[link.params.type];
	
	// A message for while the form is loaded with AJAX
	blossom.loader = createAppend('p',{className : 'blossom_loader', innerHTML : 'One sec…'},blossom.contents);
	
	// Get the form for this recipe and action from the server
	$.ajax({
		url: link.href,
		success: function(data) {
			// Hide the loader
			$(blossom.loader).hide();
			
			// If there is a list of social links, hide it
			if (blossom.social) $(blossom.social).hide();
			
			// Add a class to the blossom
			$(blossom).addClass('blossom_has_form');
			
			// Evaluate the JSON sent by the server
			try {
				var response = eval('(' + data + ')');
			}
			catch(e) {
				bl_ajax_error(blossom,e);
			}
			
			// Create a container for the form
			blossom.form_container = createAppend('div',{id : 'recipe_' + link.params.id + '_' + link.params.type},blossom.contents);
			
			// Create the blossom title
			blossom.h1 = createAppend('h1',{innerHTML : response['title']},blossom.form_container);
			
			// Create a close link
			blossom.close = createAppend('p',{className : 'blossom_close'},blossom.form_container);
			blossom.closeA = createAppend('a',{href : response['result_uri']},blossom.close);
				blossom.closeA.innerHTML = 'Close';
			
			// If the server sent note text, create a paragraph for it
			if (response['note']) {
				blossom.note = createAppend('p',null,blossom.form_container);
					blossom.note.innerHTML = response['note'];
			}
			
			// Add the form
			$(response['form']).appendTo(blossom.form_container);
			
			// Focus on the first input
			if ($(blossom.form_container).find('input[type!="hidden"], select, textarea').length > 0) $(blossom.form_container).find('input[type!="hidden"], select, textarea')[0].focus();
			
			//
			legal_blossoms();
			
			// Find links that point back to the recipe's div and make those links close the blossom
			$(blossom).find('a[href="' + response['result_uri'] + '"]').click(function(event) {
				event.preventDefault();
				// Close the blossom
				blossom.wilt();
			});
		},
		error: function(data) {
			bl_ajax_error(blossom,data);
		}
	});
}


/*
	large_checkbox()
	
	This is a JS + CSS + PNG replacement for input raido buttons, although the design looks like a checkbox (sigh).
	All the bound events are intended to mimic native browser radio button behavior.
	It also looks for checked checkboxes when first fired, though preferably this is handled in the HTML & CSS.
*/
function large_checkbox() {
	$('.large_checkbox input[type="radio"]').bind('change focus',function() {
		check_large_checkbox(this);
	});
	$('.large_checkbox input[type="radio"]').bind('focus',function() {
		focus_large_checkbox(this);
		$(this).blur(function() {
			blur_large_checkbox(this);
		});
	});
	$('.large_checkbox input[type="radio"]').each(function() {
		var labels = $('label[for="' + this.id + '"]');
		$(labels).bind('mousedown',function() {
			focus_large_checkbox($('#' + this.htmlFor));
			$(this).bind('mouseover.focus',function() {
				focus_large_checkbox($('#' + this.htmlFor));
			});
			$(this).bind('mouseout.focus mouseup.focus',function() {
				blur_large_checkbox($('#' + this.htmlFor));
			});
		});
		$(document).bind('mouseup',function() {
			blur_large_checkbox($('#' + labels.htmlFor));
			$(labels).unbind('.focus');
		});
		check_large_checkbox(this);
	});
}
function check_large_checkbox(input) {
	$(input.form).find('input[name="' + input.name + '"]').each(function() {
		if (this.checked) {
			$(this).parent('.large_checkbox').addClass('checked');
		}
		else {
			$(this).parent('.large_checkbox').removeClass('checked');
		}
	});
}
function focus_large_checkbox(input) {
	$(input).parent('.large_checkbox').addClass('focus');
}
function blur_large_checkbox(input) {
	$(input).parent('.large_checkbox').removeClass('focus');
}


/*
	multiselects_into_blossoms()
	
	Takes a select element with multiple="multiple" ("multiselects") and turns it into a blossom.
	The blossom has a list of checkbox inputs for each option in the select.
	Since the blossom is created outside the original form, the checkboxes simply update the (now hidden) select element.
	The select element is hidden and replaced with a text list of the selected options, plus links to add/edit selections.
*/
function multiselects_into_blossoms() {
	
	$('#fieldset-categoriesGrouping select[multiple!="false"]').each(function() {
		
		// If this is the fourth multiselect (first on the second row), clear left so rows wrap properly
		if($(this).parents('p').is(":nth-child(4)")) $(this).parents('p').css('clear','left');
		
		this.label = $('label[for="' + this.id + '"]')[0];
		
		$(this).hide();
		
		this.preview = createAppend('span',{className : 'publish_categorize_preview'},this.parentNode);
		
		this.editLink = createAppend('a',{href : '#' + this.id + '_multiple', innerHTML : '+ Add'},this.parentNode);
		this.editLink.input = this;
		
		
		var currentCats = [];
		$(this).find('option').each(function() {
			if (this.selected == true) {
				currentCats.push(this.innerHTML);
			}
		});
		this.preview.innerHTML = currentCats.join(', ') + ' ';
		this.editLink.innerHTML = (currentCats.length > 0) ? 'Edit' : '+ Add';
		
		$(this.editLink).click(function(event) {
			event.preventDefault();
			
			if (this.blossom) {
				$(this.blossom).show();
			}
			else {
				this.params = [];
				this.params.type = 'multiselect';
				this.params.id = this.input.id;
				
				var blossom = bl_create_blossom_container(this);
				
				$(blossom).addClass('blossom_categories');
				
				blossom.h1 = createAppend('h1',{innerHTML : 'Select ' + this.input.label.innerHTML + ' Categories'},blossom.contents);
				
				var opts = this.input.options;
				var quad = Math.ceil(opts.length/4);
				var o = 1;
				for (var i = 1, j = 5; i < j; i++) {
					if (opts[o]) {
						var ul = createAppend('ul',null,blossom.contents);
						while (o < quad * i + 1) {
							if (opts[o]) {
								var li = createAppend('li',null,ul);
								var id = 'category_' + opts[o].value;
								var label = createAppend('label',{htmlFor : id,className : 'checkbox'},li);
								var checkbox = createAppend('input',{type : 'checkbox',value : opts[o].value,id : id},label);
								
								checkbox.option = opts[o];
								
								$(label).append(opts[o].innerHTML);
							}
							o++
						}
					}
				}
				
				blossom.form_actions = createAppend('p',{className : 'form_actions'},blossom.contents);
				blossom.cancel = createAppend('input',{type : 'button', className : 'cancel', value : 'Cancel', input : this.input},blossom.form_actions);
				blossom.save = createAppend('input',{type : 'submit', className : 'submit', value : 'Save', input : this.input},blossom.form_actions);
				
				$(blossom).css({
					left: '50%',
					marginLeft: -($(blossom).width()/2) + 'px'
				});
				
				// Find the buttons in this blossom, and make the close the blossom
				$(blossom).find('input[type="button"], input[type="submit"]').click(function() {
					
					// If this is the save button, apply the checkboxes' values to their corresponding options in the multiselect
					if (this.type == 'submit') {
						var currentCats = [];
						$(blossom).find('input[type="checkbox"]').each(function() {
							this.option.selected = this.checked;
							if (this.checked == true) {
								currentCats.push(this.option.innerHTML);
							}
						});
						this.input.preview.innerHTML = currentCats.join(', ') + ' ';
						this.input.editLink.innerHTML = (currentCats.length > 0) ? 'Edit' : '+ Add';
					}
					
					// Close the blossom
					blossom.wilt();
				});
			}
			
			$(this.blossom).find('input[type="checkbox"]').each(function() {
				this.checked = this.option.selected;
			});
		});
		
	});
	
}

/*
	require_1_category()
	
	On the add a recipe forms, require at least one categry to be checked.
*/
function require_1_category() {
	$('#fieldset-categoriesGrouping').parents('form').submit(function(event){
		var categorized = false;
		$('#fieldset-categoriesGrouping select[multiple!="false"]').each(function() {
			if (this.value) categorized = true;
		});
		if (!categorized) {
			$('#fieldset-categoriesGrouping .input_description').addClass('errors');
			var from_top = $('#fieldset-categoriesGrouping').offset().top - parseInt($('#fieldset-categoriesGrouping').css('margin-bottom'),10);
			$(window).scrollTop(from_top);
			event.preventDefault();
		}
	});
	$('.categorize').parents('form').submit(function(event){
		var categorized = false;
		$('.categorize select').each(function() {
			if (this.value) categorized = true;
		});
		if (!categorized) {
			$('#fieldset-categorize .hint').addClass('errors');
			var from_top = $('#fieldset-categorize').offset().top - parseInt($('#fieldset-categorize').css('margin-bottom'),10);
			$(window).scrollTop(from_top);
			event.preventDefault();
		}
	});
	$('.categorize select').bind('change',function() {
		if (this.value) $('#fieldset-categorize .hint').removeClass('errors');
	});
}


/*
	bl_create_blossom_container(link)
	
	Creates the container for a blossom. Pass the link so the blossom knows which link it belongs to.
	Relies on other blossom functiosn, prefixed with bl_
*/
function bl_create_blossom_container(link) {
	// All blossoms have class 'blossom'
	var b_class = 'blossom';
	// Add a class for this type of blossom
	b_class += ' blossom_' + link.params.type;
	// Create an id for this blossom
	var b_id = 'blossom_' + link.params.id + '_' + link.params.type;
	// Build the blossom divs, and append to the page's wrapper div
	var blossom = createAppend('div',{className : b_class, id : b_id},$('.wrapper')[0]);
	blossom.link = link;
	link.blossom = blossom;
	blossom.border = createAppend('div',{className : 'blossom_border'},blossom);
	blossom.contents = createAppend('div',{className : 'blossom_contents'},blossom.border);
	// Place the blossom next to the link that called it
	bl_place_blossom(blossom,link);
	
	// Method to close the blossom (some blossom types need special attention when closing)
	blossom.wilt = function() {
		// If this was a blossom with a list of social links...
		if (this.social) {
			// Hide the email form
			if (this.form_container) $(this.form_container).hide();
			// Show the list of social links
			$(this.social).show().css('visibility','visible');
			$(this).removeClass('blossom_has_form');
		}
		// Hide the blossom
		$(this).hide();
		if (this.error_container) {
			this.decompose();
		}
	}
	
	// Method to remove blossom from existence
	blossom.decompose = function() {
		// Remove this blossom from from recipe's array of blossoms
		if (this.recipe) this.recipe.blossoms[this.link.params.type] = null;
		if (this.link) this.link.blossom = null;
		// Remove the blossom from the DOM
		$(this).remove();
	}
	
	// If the esc key is pressed, close all open blossoms
	$(document).keyup(function(e) {
		if (e.keyCode == 27) {
			$('.blossom:visible').each(function() {
				this.wilt();
			});
		}
	});
	
	// If the user clicks away from the blossom (to anywere but a link), close all open blossoms
	$(document).bind('click', function(event) {
		if (!event.target.tagName.match('A|BUTTON') && !$(event.target).is('.blossom') && !$(event.target).parents('.blossom').length) {
			$('.blossom:visible').each(function() {
				this.wilt();
			});
		}
	});
	
	return blossom;
}

// Positions a blossom next to the element that called it
function bl_place_blossom(blossom,b_caller) {
	var top = $(b_caller).offset().top - $('.wrapper').offset().top;
	var left = $(b_caller).offset().left - $('.wrapper').offset().left + $(b_caller).width();
	$(blossom).css({'top' : top, 'left' : left});
}

// Positions a blossom centered below the element that called it
function bl_place_blossom_horizontal(blossom,b_caller) {
	var top = $(b_caller).offset().top - $('.wrapper').offset().top + $(b_caller).height();
	var left = $(b_caller).offset().left - $('.wrapper').offset().left;
	var marginLeft = -($(blossom).width() - $(b_caller).width())/2;
	$(blossom).css({'top' : top, 'left' : left, 'marginLeft' : marginLeft});
}

// Call if AJAX request failed, or evaluating data failed
function bl_ajax_error(blossom,data) {
	// Hide the loader
	if (blossom.loader) $(blossom.loader).hide();
	
	// If there is a list of social links, hide it
	if (blossom.social) $(blossom.social).hide();
	
	// Add a class to the blossom
	$(blossom).addClass('blossom_has_error');
	
	// Create a container for the error
	blossom.error_container = createAppend('div',{id : 'error'},blossom.contents);
	
	// Create the blossom title
	blossom.h1 = createAppend('h1',{innerHTML : 'Aw, nuts!'},blossom.error_container);
	
	// Create a close link
	blossom.close = createAppend('p',{className : 'blossom_close'},blossom.error_container);
	blossom.closeA = createAppend('a',{href : '.',innerHTML : 'Close'},blossom.close);
		$(blossom.closeA).click(function() {
			event.preventDefault();
			// Remove the blossom from existence
			blossom.decompose();
		});
	
	blossom.note = createAppend('p',{innerHTML: 'Sorry, but something went wrong. If this keeps happening, please <a href="/contact">let us know</a>.'},blossom.error_container);
	
	if (data['status']) blossom.note.innerHTML += ' (The HTTP status was ' + data['status'] + ', by the way.)';
}


/*
	recipe_print_button()
	
	Shows the hidden print button and makes clicking it open the browser's print dialogue
*/

function recipe_print_button() {
	$('#recipe_print').show();
	$('#recipe_print').click(function() {
		window.print();
	});
}


/*
	reply_to_review()
	
	Get reply to review form with asynchronously
*/
function reply_to_review() {
	$('a[href*="/review/"]').each(function() {
		$(this).click(function(event) {
			event.preventDefault();
			
			review_id = this.href.match(/.+\/review\/(\d+).*/)[1];
			
			// See if the reply form already exists
			if (!$('#reply_to_' + review_id)[0]) {
				// Create a container for the reply form and append the form containter after the review
				this.form_container = createAppend('div',{className : 'recipe_review recipe_reply',id : 'reply_to_' + review_id});
					this.form_container.loader = createAppend('p',{className : 'blossom_loader', innerHTML : 'One sec…'},this.form_container);
					$(this.form_container).insertAfter('#review_' + review_id);
				
				// Get the form to reply to this review
				var link = this;
				$.ajax({
					url: this.href,
					success: function(data) {
						// Evaluate the JSON sent by the server
						try {
							var response = eval('(' + data + ')');
							// Insert the form HTML 
							link.form_container.innerHTML = response['reply']['form'];
							$(link.form_container).find('a[href*="/view/"]').click(function(event) {
								event.preventDefault();
								$(this).parents('.recipe_reply').hide();
							});
						}
						catch(e) {
							link.form_container.loader.innerHTML = 'Sorry, but something went wrong. If this keeps happening, please <a href="/contact">let us know</a>.';
							//link.form_container.loader.innerHTML += '<br />' + e + '<br />' + data;
						}
					},
					error: function(data) {
						link.form_container.loader.innerHTML = 'AJAX call unsuccessful.<br />' + data;
					}
				});
			}
			else {
				// Reply form already exists, so show it
				$('#reply_to_' + review_id).show();
			}
		});
	});
}


/*
	recipe_ingredients()
	
	Allows user to add and delete ingredients when creating a personal recipe
*/
var ingredients = [];
function recipe_ingredients() {
	$('*[id^="ingredient_row_"]').each(function(i) {
		ingredients.push($(this)[0]);
	});
	
	$('#addIngredientElement').click(function(event) {
		event.preventDefault();
		
		var lastIngredient = ingredients[ingredients.length-1];
		
		var newIngredient = recipe_ingredients_parse_ingredient($(lastIngredient).clone()[0]);
		
		recipe_ingredients_update_ingredient(newIngredient,newIngredient.number + 1);
		newIngredient.ingredient_id.value = '';
		$(newIngredient).find('input, select, textarea').each(function() {
			this.value = '';
			this.selectedIndex = 0;
		});
		
		$(newIngredient.input_delete).click(function(event) {
			event.preventDefault();
			recipe_ingredients_delete_ingredient(this);
		});
		
		$(newIngredient).insertAfter(lastIngredient);
		
		ingredients.push(newIngredient);
	});
	
	$('.delete_ingredient').click(function(event) {
		event.preventDefault();
		recipe_ingredients_delete_ingredient(this);
	});
	
	// We'll have to disable submitting the form with the return/enter key, because it triggers the first submit input, which is an input that deletes an ingredient
	// TODO - is there a way around disabling enter->submit?
	if ($('.delete_ingredient').length>0) {
		$('#recipe_form').keypress(function(e) {
			if (e.srcElement.tagName != 'TEXTAREA') {
				if (e.keyCode == 13) {
					// The user pressed return
					return false;
				}
			}
		});
	}
}

function recipe_ingredients_parse_ingredient(ingredient) {
	ingredient.number = parseInt(ingredient.id.match(/\d+/));
	ingredient.ingredient_id = $(ingredient).find('input[name^="ingredient_id_"]')[0];
	ingredient.ingredient_qty = $(ingredient).find('input[name^="ingredient_qty_"]')[0];
	ingredient.ingredient_unit = $(ingredient).find('select[name^="ingredient_unit_"]')[0];
	ingredient.ingredient_name = $(ingredient).find('input[name^="ingredient_name_"]')[0];
	ingredient.input_delete = $(ingredient).find('input[id^="delete_ingredient_"]')[0];
	return ingredient;
}

function recipe_ingredients_delete_ingredient(input_delete) {
	var number = parseInt(input_delete.id.match(/\d+/));
	if (ingredients.length > 1) {
		ingredients.splice(number-1,1);
		$('#ingredient_row_' + number).remove();
	}
	else {
		var ingredient = recipe_ingredients_parse_ingredient(ingredients[0]);
		recipe_ingredients_update_ingredient(ingredient,1);
	}
	
	for (i=0,j=ingredients.length;i<j;i++) {
		var ingredient = recipe_ingredients_parse_ingredient(ingredients[i]);
		recipe_ingredients_update_ingredient(ingredient,i+1);
	}
}

function recipe_ingredients_update_ingredient(ingredient,newNumber) {
	ingredient.number = newNumber;
	ingredient.id = ingredient.id.replace(/\d+/,ingredient.number);
	$(ingredient).find('input, select, textarea').each(function(i) {
		if (this.name) this.name = this.name.replace(/\d+/,ingredient.number);
		if (this.id) this.id = this.id.replace(/\d+/,ingredient.number);
	});
}


/*
	recipe_directions()
	
	Allows user to add and delete steps when creating a personal recipe
*/
var directions = [];
function recipe_directions() {
	$('#direction_form > p').each(function(i) {
		directions.push($(this)[0]);
	});
	
	$('#addStepElement').click(function(event) {
		event.preventDefault();
		
		var lastStep = directions[directions.length-1];
		
		var newStep = recipe_directions_parse_step($(lastStep).clone()[0]);
		
		recipe_directions_update_step(newStep,newStep.number + 1);
		newStep.textarea.innerHTML = '';
		
		$(newStep.input_delete).click(function(event) {
			event.preventDefault();
			recipe_directions_delete_step(this);
		});
		
		$(newStep).insertAfter(lastStep);
		
		directions.push(newStep);
	});
	
	$('.delete_direction').click(function(event) {
		event.preventDefault();
		recipe_directions_delete_step(this);
	});
}

function recipe_directions_parse_step(step) {
	step.number = parseInt(step.id.match(/\d+/));
	step.label = $(step).find('label')[0];
	step.input_order = $(step).find('input[name^="direction_order_"]')[0];
	step.textarea = $(step).find('textarea')[0];
	step.input_delete = $(step).find('input.delete_item')[0];
	return step;
}

function recipe_directions_delete_step(input_delete) {
	var number = parseInt(input_delete.id.match(/\d+/));
	if (directions.length > 1) {
		directions.splice(number-1,1);
		$('#direction_row_' + number).remove();
	}
	else {
		$('#direction_row_' + number).find('textarea')[0].value = '';
	}
	
	for (i=0,j=directions.length;i<j;i++) {
		var step = recipe_directions_parse_step(directions[i]);
		recipe_directions_update_step(step,i+1);
	}
}

function recipe_directions_update_step(step,newNumber) {
	step.number = newNumber;
	step.id = step.id.replace(/\d+/,step.number);
	step.label.innerHTML = step.label.innerHTML.replace(/\d+/,step.number);
	step.input_order.id = step.input_order.id.replace(/\d+/,step.number);
	step.input_order.name = step.input_order.name.replace(/\d+/,step.number);
	step.input_order.value = step.number - 1;
	step.textarea.id = step.textarea.id.replace(/\d+/,step.number);
	step.textarea.name = step.textarea.name.replace(/\d+/,step.number);
	step.input_delete.id = step.input_delete.id.replace(/\d+/,step.number);
}


/*
	show_hide_all_categories()
	
	On category pages, hides full list of subcategories, then creates show/hide links to toggle
*/
function show_hide_all_categories() {
	if ($('#categories_popular')[0] && $('#categories_all')[0]) {
		// Hide the full list of subcategories by default
		$('#categories_all').hide();
		
		// Create the hide link
		var hide = createAppend('p',{className: 'categories_show_hide',id: 'categories_hide'},$('#categories_popular')[0]);
		$(hide).hide(); // hide this link by default
		hide.link = createAppend('button',{innerHTML: 'Hide all'},hide);
		
		// Create the show link
		var show = createAppend('p',{className: 'categories_show_hide',id: 'categories_show'},$('#categories_popular')[0]);
		show.link = createAppend('button',{innerHTML: 'Show all'},show);
		
		hide.link.counterpart = show;
		show.link.counterpart = hide;
		
		$(hide.link).click(function() {
			$('#categories_all').hide();
			$(this).parent().hide();
			$(this.counterpart).show();
		});
		
		$(show.link).click(function() {
			$('#categories_all').show();
			$(this).parent().hide();
			$(this.counterpart).show();
		});
	}
}


/*
	nav()
	
	Put a slight delay on opening the main nav fly-out menus (uses hoverIntent plugin).
*/
function nav() {
	$('.nav').addClass('nav_js');
	$('.nav > ul > li').hoverIntent({
		sensitivity: 10, // number = sensitivity threshold (must be 1 or higher)
		interval: 100,   // number = milliseconds of polling interval
		over: showNav,  // function = onMouseOver callback (required)
		timeout: 0,   // number = milliseconds delay before onMouseOut function call
		out: hideNav    // function = onMouseOut callback (required)
	});
}

function showNav(e) {
	$(e.currentTarget).addClass('over');
}

function hideNav(e) {
	$(e.currentTarget).removeClass('over');
}


/*
	tab_nav()
	
	Takes a group of internal links and makes them work like tabs (that is, show the link's target element and hide the other links' target elements)
*/
var tab_groups = []; // Contains all tab groups on the page
tab_groups.tabs_master_list = {}; // Contains all tabs on the page for easier lookup
function tab_nav(list) {
	if (!list) list = $('.tab_nav');
	
	$(list).each(function(i) {
		
		// Give the tab list container an id if it doesn't have one
		if (this.id == '') this.id = 'tab_group_' + tab_groups.length + 1;
		
		// Create an object to contain the tab group
		var tab_group = {};
		
		// Add this tab group to the main list, keyed by the link container's index
		tab_groups[this.id] = tab_group;
		
		// Create an array to hold all the tabs in this tab group
		tab_group.tabs = [];
	
		// We'll use this to set a minumum height on all the sections, so they're all as tall as the tallest section
		// TODO - 2010-06-11 work in progress
		//tab_group.minHeight = 0;
		
		// Get all the links that point to an element by its ID
		$(this).find('a[href^="#"]').each(function() {
			var id = this.href.substr(this.href.indexOf('#')+1);
			if ($('#' + id)[0]) {
				// Create an object to contain the tab
				var tab = {};
				
				// Add this tab to its group's list, keyed by the tab's index
				tab_group.tabs.push(tab);
				// Also add it to the master list, so we can look it up quickly
				tab_groups.tabs_master_list[id] = tab;
				
				// The tab's link
				tab.link = this;
				
				// The tab's content
				tab.content = $('#' + id)[0];
				
				// The tab's group
				tab.group = tab_group;
				
				// For every link on the page that points at this tab's content, use JS to show the tab
				$('a[href="#' + id + '"]').click(function(event) {
					event.preventDefault();
					tab_nav_show_hide(this.href.substr(this.href.indexOf('#')+1));
				});
				
				//if ($(tab.content).height() > tab_group.minHeight) tab_group.minHeight = $(tab.content).height();
			}
		});
		
		/*for (tab in tab_group.tabs) {
			var content = tab_group.tabs[tab].content;
			var minHeight = tab_group.minHeight;
			
			minHeight -= parseInt($(content).css('paddingTop'));
			minHeight -= parseInt($(content).css('paddingBottom'));
			
			content.style.minHeight = minHeight + 'px';
		}*/
		
		// If this is the social_right module, set softHide to true on the first call to tab_nav_show_hide().
		var softHide = ($(this).parents('.social_right').length > 0) ? true : false;
		
		// By default, show just the first tab's content
		tab_nav_show_hide(tab_group.tabs[0].content.id, softHide);
	});
}

function tab_nav_show_hide(id, softHide) {
	if (tab_groups.tabs_master_list[id]) {
		var selected_class = 'tab_nav_selected';
		var tab = tab_groups.tabs_master_list[id];
		$(tab.group.tabs).each(function(i) {
			if (tab.group.tabs[i] != tab) {
				// If softHide == true, hide the other tabs with slideUp() instead of hide()
				(softHide) ? $(this.content).slideUp() : $(this.content).hide();
				$(this.link).parent().removeClass(selected_class);
			}
		});
		$(tab.content).show();
		$(tab.link).parent().addClass(selected_class);
	}
}


/*
	x_chars_remaining()
	
	Adds "x characters remaining" notice to recipe name as user types or pastes
*/
function x_chars_remaining() {
	if ($('#recipe_name')[0]) {
		var input = $('#recipe_name')[0];
		input.notice = createAppend('p',{className: 'required_notice', innerHTML: remaining_text(input.maxLength)});
		$(input.notice).hide().insertBefore(input);
		$(input).focus(function() {
			$(this.notice).fadeIn('fast');
		});
		$(input).blur(function() {
			$(this.notice).fadeOut('fast');
		});
		$(input).bind('keyup change paste input',function() {
			var remaining = this.maxLength - this.value.length;
			$(this.notice).text(remaining_text(remaining));
		});
	}
}
	
function remaining_text(chars) {
	return (chars == 1) ? chars + ' character remaining' :  chars + ' characters remaining';
}


/*
	save_recipe()
	
	Asynchronously saves a recipe from a list view.
	Clicking a save link fades it halwfay and fires the AJAX call.
	If the call returns successfully, the save link finishes fading out and is replaced by a "Saved" strong tag, which fades in.
	If the AJAX call or save action fail, the user is sent to the original href.
	// TODO - update "saves" and recipe box totals
	// TODO - unsave links
*/
function save_recipe() {
	$('a[href*="/save/"]').bind('click',function(event) {
		event.preventDefault();
		$(this).fadeTo(500,.5);
		$.ajax({
			context: this,
			url: this.href,
			method: 'get',
			success: function(data) {
					try {
						var response = eval('(' + data + ')');
						if (response == 'saved') {
							var strong = createAppend('strong',{innerHTML: 'Saved'});
							$(strong).fadeTo(0,0);
							$(this).fadeTo(500,0,function() {
								$(this).replaceWith(strong);
								$(strong).fadeTo(1000,1);
							});
						}
						else {
							document.location = this.href;
						}
					}
					catch(e) {
						document.location = this.href;
					}
				}
		});
		
		// Trigger Agilone's tracking
		/*var scriptSrc = oProtocol
		+ "hormelstats.agilone.com/NextOne/Track/NextOne.aspx?SiteGuid=90hrml96-us11-456h-iml-4730764122cc&ContainerID=1"
		+ "&page="+ escape(this.href) + "&keyword="+ escape(NextOne_Keyword)
		+ "&userid=" + escape(NextOne_UserID);
		var scriptElement = createAppend('script',{type: 'text/javascript', src: scriptSrc});
		//document.getElementsByTagName('table')[0].insertBefore(scriptElement);
		$.get(scriptSrc,function(data,status) {
			console.log(data);
			console.log(status);
		});*/
	});
}


/*
	follow_user()
	
	Asynchronously follow or unfollow another user. Similar to save_recipe().
	TODO - Make new button fade in instead of appearing at once.
*/
function follow_user() {
	$('a[href*="/user/follow"], a[href*="/user/unfollow"]').bind('click',function(event) {
		event.preventDefault();
		$(this).parents('p').fadeTo(500,.5);
		$.ajax({
			context: $(this).parents('p'),
			url: this.href,
			method: 'get',
			success: function(data) {
					try {
						var response = eval('(' + data + ')');
						if (response.success == true) {
							$(this.context).fadeTo(500,0,function() {
								var button = $(this).replaceWith(response.button)[0];
								follow_user();
							});
						}
						else {
							// If the AJAX didn't return succesfully, go to the original HREF
							document.location = $(this.context).find('a')[0].href;
						}
					}
					catch(e) {
						// If the eval doesn't work, go to the original HREF
						document.location = $(this).find('a')[0].href;
					}
				}
		});
	});
}


/*
	sort_dropdown()
	
	Finds dropdowns for sorting recipes, and positions them so when they're open the selected option is inline.
*/
function sort_dropdown() {
	$('.sort_dropdown').each(function() {
		var li = $(this).find('strong a').parents('li');
		var top = li.height() * ($(this).find('li').index(li));
		$(this).css({
			marginTop: '-' + top + 'px',
			paddingTop: top + 'px'
		});
	});
}


/*
	mobile_safari_hoverable()
	
	Mobile Safari (iPhone, iPad, etc.) gives clickable elements a :hover state when tapped, but we need some non-clickable elements to have a :hover state.
	This function makes specified elements clickable in Mobile Safari.
*/
function mobile_safari_hoverable() {
	if (navigator.userAgent.match(/ AppleWebKit.+ Mobile/)) {
		// An empty function
		var empty_handler = function() {};
		// Bind the empty function to this element's click event
		$('.account_add_recipe, .section_add_recipe, .top_account_logged_in').bind('click',empty_handler);
	}
}


/*
	viewport_cta()
	
	For registration CTA A/B testing (2010-09).
*/
function viewport_cta() {
	// Make the CTA on the right of the viewport open and close when the tab is clicked.
	$('#register_cta_viewport_right .register_cta_viewport_right_tab').click(function() {toggle_viewport_cta(this);});
	// The CTA is visible by default. Close it three seconds after the page loads, unless the user has already put the cursor in the input.
	if ($(document.activeElement).parents('#register_cta_viewport_right').length == 0) {
		var close_ctavr = setTimeout(function(){
			toggle_viewport_cta($('#register_cta_viewport_right .register_cta_viewport_right_tab')[0]);
		},3000);
		// If the user clicks on the CTA before it closes, keep it open.
		$('#register_cta_viewport_right').click(function() {
			clearTimeout(close_ctavr);
		});
	}
}
function toggle_viewport_cta(tab) {
	var move = $(tab).parent().width();// - $(cta).width();
	move *= (parseInt($(tab).parent().css('right')) >= 0) ? -1 : 0;
	$(tab).parent().animate({right: move},700,'swing',function() {
		(move >= 0) ? $(tab).removeClass('tab_arrow_left') : $(tab).addClass('tab_arrow_left');
	});
}



/*
	
	Other useful functions
	
*/


/*
	createAppend()
	
	creates an element, of tag type 'name', with specified 'attributes' (e.g., class, id, href), and appends to 'parent'
*/
function createAppend(name,attributes,parent) {
	var element = document.createElement(name);
	for (attribute in attributes) {
		element[attribute] = attributes[attribute];
	}
	if (parent) parent.appendChild(element);
	return element;
}



/*
	
	jQuery extensions
	
*/


/*
	urlEncode
	
	http://0061276.netsolhost.com/tony/javascript/urlEncode.js
*/
$.extend({URLEncode:function(c){var o='';var x=0;c=c.toString();var r=/(^[a-zA-Z0-9_.]*)/;
  while(x<c.length){var m=r.exec(c.substr(x));
    if(m!=null && m.length>1 && m[1]!=''){o+=m[1];x+=m[1].length;
    }else{if(c[x]==' ')o+='+';else{var d=c.charCodeAt(x);var h=d.toString(16);
    o+='%'+(h.length<2?'0':'')+h.toUpperCase();}x++;}}return o;},
URLDecode:function(s){var o=s;var binVal,t;var r=/(%[^%]{2})/;
  while((m=r.exec(o))!=null && m.length>1 && m[1]!=''){b=parseInt(m[1].substr(1),16);
  t=String.fromCharCode(b);o=o.replace(m[1],t);}return o;}
});

/**
* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
* 
* @param  f  onMouseOver function || An object with configuration options
* @param  g  onMouseOut function  || Nothing (use configuration options object)
* @author    Brian Cherne <brian@cherne.net>
*/
(function($){$.fn.hoverIntent=function(f,g){var cfg={sensitivity:7,interval:100,timeout:0};cfg=$.extend(cfg,g?{over:f,out:g}:f);var cX,cY,pX,pY;var track=function(ev){cX=ev.pageX;cY=ev.pageY;};var compare=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);if((Math.abs(pX-cX)+Math.abs(pY-cY))<cfg.sensitivity){$(ob).unbind("mousemove",track);ob.hoverIntent_s=1;return cfg.over.apply(ob,[ev]);}else{pX=cX;pY=cY;ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}};var delay=function(ev,ob){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);ob.hoverIntent_s=0;return cfg.out.apply(ob,[ev]);};var handleHover=function(e){var p=(e.type=="mouseover"?e.fromElement:e.toElement)||e.relatedTarget;while(p&&p!=this){try{p=p.parentNode;}catch(e){p=this;}}if(p==this){return false;}var ev=jQuery.extend({},e);var ob=this;if(ob.hoverIntent_t){ob.hoverIntent_t=clearTimeout(ob.hoverIntent_t);}if(e.type=="mouseover"){pX=ev.pageX;pY=ev.pageY;$(ob).bind("mousemove",track);if(ob.hoverIntent_s!=1){ob.hoverIntent_t=setTimeout(function(){compare(ev,ob);},cfg.interval);}}else{$(ob).unbind("mousemove",track);if(ob.hoverIntent_s==1){ob.hoverIntent_t=setTimeout(function(){delay(ev,ob);},cfg.timeout);}}};return this.mouseover(handleHover).mouseout(handleHover);};})(jQuery);
