String.prototype.trim = function () {
	return this.replace(/^\s+|\s+$/g, "");
};

$(document).ready(function() {
  // add event handlers to font controls
	$("#font-small").click(function(){fontSize('small'); this.blur(); return false;});
	$("#font-medium").click(function(){fontSize('medium'); this.blur(); return false;});
	$("#font-large").click(function(){fontSize('large'); this.blur(); return false;});
  setStyles();
  
  // init add to recipe box widget (wrapped in a function so we can call again when paging through search results)
  initAddRecipeBox = function() {
    $("a.add-recipe, p.add-recipe a").bind("click", function() {
      var me = $(this);
        try {
          $.ajax({
            url: me.href(),
            type: "GET",
            success: function(req) {
              if(req.trim() == 'true') {
                me.before("In your recipe box");
                $(me).parent().toggleClass("add-recipe-on");
                me.remove();
                monitorAjax("recipebox - add");
              } else {
                ajaxError();
              }
            },
            error: ajaxError
          });
        } catch(err) {ajaxError(err);}
        this.blur();
        return false;
    });
  };
  
  initAddRecipeBox();
  
  // init remove from recipe box widget
  $("a.remove-recipe").bind("click", function() {
    this.blur();
    $(this).ancestors("tr").children("td").background("#ff9");
    if(window.confirm("Are you sure you want to remove this recipe?")) {
      var me = $(this);
      try {
        $.ajax({
          url: me.href(),
          type: "GET",
          success: function(req) {
            if(req.trim() == 'true') {
              $(me).ancestors("tr").hide().remove();
              // if we removed the last recipe, show the empty box message
              if(!$("#table-recipe-box tbody tr").size()) {
                $("#recipe-box-empty").show();
                $("#table-recipe-box").hide();
              }
              monitorAjax("recipebox - remove");
            } else {
              ajaxError();
            }
          },
          error: ajaxError
        });
      } catch(err) {ajaxError(err);}
    } else {
      $(this).ancestors("tr").children("td").background("#fff");
    }
    return false;
  });
  
  initSearchResults = function() {
    // style search results tables
    $("#search-results tbody tr").hover(function() {
      $(this).addClass("over");
    }, function() {
      $(this).removeClass("over");
    }).bind("click", function() {
      location.href = $(this).find("a")[0].href;
    });
    
    // add AJAX calls for next/previous
    $("p.search-nav a").each(function() {
      $(this).bind("click", function() {
        var loading = $("#loading");
        loading.show();
        var href = $(this).href();
        var container = $(this).ancestors("div.results");
        container.fadeTo(250, 0.01, function() {container.load(href, function() {
            container.fadeTo(250, 1.0, function() {
              loading.hide();
            });
            dhtmlHistory.add(escape(href));
            currentLocation = href;
            initSearchResults();
            initAddRecipeBox();
          });
        });
        window.scrollTo(0, $("table.search-results").offset().top);
        return false;
      });
    });
  };
  
  // search results page?
  if($("body#recipe_search").size()) {
    // initialize the DHTML History
    // framework
    dhtmlHistory.initialize();
    
    historyChange = function(newLocation, historyData) {
	    //try{console.log("cur: " + currentLocation); console.log("new: " + newLocation);} catch(err){}
	    //alert("cur: " + currentLocation + "\n" + "new: " + newLocation);
	    if(newLocation && newLocation != currentLocation) {
	      $("#recipe-results").load(unescape(newLocation), function() {
	        initSearchResults();
	        initAddRecipeBox();
	      });
	    }
	  };
    
    // subscribe to DHTML history change
    // events
    dhtmlHistory.addListener(historyChange);
    
    //added for browser back functionality
	    // add a history entry if we're on page one of the results
	    var href = $(".search-nav a");
	    if (location.href.indexOf("#http") == -1 && href.size()) {
	    	href = href[0].href;
		    href = href.replace(/page=2/, "page=1");
			dhtmlHistory.add(escape(href));
			var currentLocation = href;
			//try{console.log("cur: " + currentLocation); console.log("new: " + newLocation);} catch(err){};
		} else {
			var currentLocation = "";
		}
    
    // init search results
    initSearchResults();
  }
  
  // init form validation
  if($("form.validate").size()) {
    initFormValidation();
  }
});

ajaxError = function(req) {
  monitorAjax("Error");
  alert("Sorry, the server was unable to process your request. Please try again.");
  //alert(req);
};

imageMissing = function(img) {
  if($("#recipe_detail").size()) {
    img.src = "/images/recipe.gif";
  } else {
    img.src = "/images/image_not_available.gif";
  }
};

setActiveStyleSheet = function(title) {
  if(document.getElementById && document.createTextNode) {
		var i, a;
	  for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
	    if(a.getAttribute("rel").indexOf("style") != -1 && a.getAttribute("title")) {
	     	a.disabled = true;
	      if(a.getAttribute("title") == title) {
          a.disabled = false;
        }
	    }
	  }
  	$("#font-small img").src("images/font_small.gif");
  	$("#font-medium img").src("images/font_medium.gif");
  	$("#font-large img").src("images/font_large.gif");
  	$("#font-" + title + " img").src("images/font_" + title + "_on.gif");
    // equalize the column heights on the homepage
    $("#indexFeatureText1, #indexFeatureText2, #indexFeatureText3").equalizeCols();
	}
};

setStyles = function() {
  var cookie = readCookie("ssStyles");
  var title = cookie ? cookie : "small";
	createCookie("ssStyles", title, 365);
 	setActiveStyleSheet(title);
};

fontSize = function(fs) {
	createCookie("ssStyles", fs, 365);
	setActiveStyleSheet(fs);
};

createCookie = function(name,value,days) {
  if (days) {
    var date = new Date();
    date.setTime(date.getTime()+(days*24*60*60*1000));
    var expires = "; expires="+date.toGMTString();
  } else {
    expires = "";
  }
  document.cookie = name+"="+value+expires+"; path=/";
};

readCookie = function(name) {
  var nameEQ = name + "=";
  var ca = document.cookie.split(';');
  for(var i=0;i < ca.length;i++) {
    var c = ca[i];
    while (c.charAt(0)==' ') {
      c = c.substring(1,c.length);
    }
    if (c.indexOf(nameEQ) === 0) {
      return c.substring(nameEQ.length,c.length);
    }
  }
  return null;
};

preload = function(arr) {
  var images = [];
  $.each(arr, function(i, n) {
    images[i] = new Image;
    images[i].src = n;
  });
}

showSeason = function(s) {
  $("#seasonNav img").each(function() {
    this.src = this.src.replace(/01\.gif/, "00.gif");
  });
  var i = $("#season_" + s)[0];
  i.src = i.src.replace(/00\.gif/, "01.gif");
  $("#seasonHeader img").src("images/season_" + s + ".jpg");
  $("#recipeListContain").load(s + ".html");
};

showFruity = function(s, url) {
  $("#seasonNav img").each(function() {
    this.src = this.src.replace(/on\.gif/, "off.gif");
  });
  var i = $("#fnf" + s)[0];
  i.src = i.src.replace(/off\.gif/, "on.gif");
  $("#seasonHeader img").src("images/fnf" + s + ".jpg");
  $("#seasonHeader a").attr("href", url);
};

showWCC = function(s, url) {
  $("#seasonNav img").each(function() {
    this.src = this.src.replace(/on\.gif/, "off.gif");
  });
  var i = $("#wcc" + s)[0];
  i.src = i.src.replace(/off\.gif/, "on.gif");
  $("#seasonHeader img").src("images/wcc" + s + ".jpg");
  $("#seasonHeader a").attr("href", url);
};

parseQuery = function() {
	var returnVals = [];
	qString = new String(window.location);
	var queryStart = qString.indexOf('?');
	if (queryStart==-1) {
		return returnVals;
	}
	var query = qString.substring(queryStart + 1, qString.length);
	var parts = query.split("&");
	for (var i=0; i<parts.length; i++) {
		bits = parts[i].split("=");
		if(bits[1]) {
			subbits = bits[1].split("#"); // added by T.document. to handle fragment identifier in URL
			returnVals[bits[0].toLowerCase()] = bits[1]; // query[] indexes are now lowercase!
		}
	}
	return returnVals;
};

var query = parseQuery();

// recipe submit functions
////////////////////////////////////////

initIngredients = function() {
  var l = $("#ingredients")[0];
  
	if(l.value) {
		var arrIngredients = l.value.split("^");
		for(var i=0; i<arrIngredients.length; i++) {
      var arrParts = arrIngredients[i].split("|");
			insertIngredientRow(arrParts[0], getUnitText(arrParts[1], arrParts[0]), arrParts[2]);
		}
	}
};

addIngredient = function() {
  var q = $("#quantity")[0];
  var u = $("#unit")[0];
  var i = $("#ingredient")[0];
  
  // validate
  if(!i.value) {
    alert("Enter an ingredient.");
		i.focus();
		return false;
	}
  
  var qc = q.value.cleanIngredient();
  var ic = i.value.cleanIngredient();
	
	// insert row	
	insertIngredientRow(qc, getUnitText(u.value, qc), ic);
	
	// update hidden value
  var l = $("#ingredients")[0];	
	l.value = l.value ? l.value + "^" + qc + "|" + u.value + "|" + ic : qc + "|" + u.value + "|" + ic;
	
	//alert(l.value);
	
	// reset form values
	q.value = "";
	u.selectedIndex = 0;
  i.value = "";
	q.focus();
  return false;
};

insertIngredientRow = function(q, u, i) {
	var table = $("#tbl_ingredients")[0];
	if(table) {
		var tr = table.insertRow(table.rows.length);
		// insert the cells
		var td = [];
	  td[0] = tr.insertCell(0);
    td[1] = tr.insertCell(1);
    
    td[0].className = "ingredient";
    td[1].className = "delete";
   
		td[0].innerHTML = '<p>' + q + ' ' + u + ' ' + i + '</p>';
		td[1].innerHTML = '<p><a href="#" onclick="deleteIngredientRow(this.parentNode.parentNode.parentNode);this.blur();return false;"><img src="/images/trash.gif" border="0"></a></p>';
	}
  table.parentNode.className = "section";
  alternateRows(table);
};

deleteIngredientRow = function(tr) {
  var table = $("#tbl_ingredients")[0];
  tr.className = "highlight";
	if(window.confirm("Delete this ingredient?")) {
	
		// update hidden values
		var i = tr.rowIndex;
		var l = $("#ingredients")[0];
		var arrIngredients = l.value.split("^");
		
		arrIngredients.splice(i, 1);
		
		l.value = arrIngredients.join("^");
		
		//alert(l.value);
		
		// delete row
		table.deleteRow(i);
    if(!table.rows.length) {
      table.parentNode.className = "";
    }
	}
  alternateRows(table);
  $("#quantity")[0].focus();
};

getUnitText = function(v, q) {
  var u = $("#unit")[0];
  q = q.trim();
  if(u) {
    for(var i=0; i<u.length; i++) {
      if(u.options[i].value == v) {
        if(q.isSingular()) {
          return u.options[i].className;
        } else {
          return u.options[i].text;
        }
      }
    }
  }
  return "";
};

alternateRows = function(table) {
	for(var i=0; (tr=table.getElementsByTagName("tr")[i]); i++) {
		if(i % 2) {
      tr.className = "even";
    } else {
      tr.className = "odd";
    }
	}
};

String.prototype.cleanIngredient = function () {
	return this.replace(/[\^\|]/g, "");
};

String.prototype.isSingular = function() {
  return ((this === null) || (this.length === 0) || (/^(\d+\/\d+|0?\.\d+|1)$/.test(this)));
};

createTextCounter = function(id, maxlimit) {
	// add event handlers to textarea
	var el = document.getElementById(id);
	el.onkeydown = function(){textCounter(this.id, maxlimit);};
	el.onkeyup = function(){textCounter(this.id, maxlimit);};
	// add SPAN element to the field hint div
	var c = document.getElementById(id + "-H");
	c.innerHTML = c.innerHTML + "<span></span>";
	// update the text counter
	textCounter(id, maxlimit);
};

textCounter = function(id, maxlimit) {
	var el = document.getElementById(id);
	if (el.value.length > maxlimit) {
    el.value = el.value.substring(0, maxlimit);
    alert('Your text may not exceed ' + maxlimit + ' characters in length.' );
		textCounter(id, maxlimit);
  } else {
    var c = document.getElementById(id + "-H");
		var s = c.getElementsByTagName("SPAN")[0];
		if(s) {
			s.innerHTML = "(" + (maxlimit - el.value.length) + " characters remaining)";
		}
  }
};

exitMessage = function() {
	return confirm('You are now leaving ReadyCrust.com.\n\nAre you sure you want to continue?');
};

popUpWindow = function(URL,windowName,width,height) {
	var w = screen.availWidth;
	var h = screen.availHeight;
	var leftPos = Math.round((w-width)/2);
	var topPos = Math.round((h-height)/2);
	var defaults = "scrollbars, resizable";
	var centerOnScreen = "top="+topPos+", left="+leftPos+", width="+width+", height="+height;
	var options = centerOnScreen + " ," + defaults;
	var msgWindow = window.open(URL,windowName,options);
	if(!msgWindow) {
		return false;
	} else {
		msgWindow.creator=self;
		msgWindow.focus();
	}
  return true;
};


// validation prototypes
////////////////////////////////////////

String.prototype.trim = function () {
	return this.replace(/^\s+|\s+$/g, "");
};

String.prototype.isEmpty = function() {
  return ((this === null) || (this.length === 0) || (/^\s+$/.test(this)));
};

String.prototype.isProfane = function() {
	var badwords = ["69", "666", "ahole", "anal", "anarchist", "anarchists", "anarchy", "anus", "aryan", "aryans", "ash0le", "ash0les", "asholes", "ass", "ass hoe", "ass lick", "Ass Monkey", "asses", "Assface", "assh0le", "assh0lez", "asshole", "assholes", "assholz", "asskisser", "asswipe", "azzhole", "babe", "babes", "baby batter", "balls", "banging", "bangle", "bassterds", "bastard", "Bastard", "bastards", "bastardz", "basterds", "basterdz", "beaner", "beastial", "beastiality", "beastility", "beaver", "beer", "belly whacker", "bestial", "bestiality", "Biatch", "bible fucker", "bitch", "bitcher", "bitchers", "bitches", "bitchin", "bitching", "Blow Job", "blow job", "blowjob", "blowjobs", "blumpkin", "boffing", "bonehead", "boner", "boob", "box", "brain banger", "breasted", "breasts", "brown eye", "browneye", "browntown", "bucket cunt", "buff", "bugger", "bukakke", "bull dyke", "bull shit", "bullshit", "bum", "bung hole", "butch", "butt", "butt breath", "butt fucker", "butt hair", "butt head", "buttface", "buttfuck", "buttfucker", "butthead", "butthole", "buttpicker", "butts", "buttwipe", "byotch", "c0ck", "c0cks", "c0k", "camel hump", "camel jockey", "camel toe", "cannabis", "carpet muncher", "Carpet Muncher", "cawk", "cawks", "chicken head", "chink", "christ", "cigs", "circle jerk", "circumcise", "clam", "cleveland steamer", "Clit", "clit", "clit clitoris", "cnts", "cntz", "cobia", "cock", "cock sucker", "cockhead", "cock-head", "cockpenis", "cocks", "cocksuck", "cocksucked", "CockSucker", "cocksucker", "cock-sucker", "cocksucking", "cocksucks", "coon", "cootch", "cooter", "cornhole", "crack", "crap", "cum", "cummer", "cumming", "cums", "cumshot", "cunilingus", "cunillingus", "cunnilingus", "cunt", "cuntlick", "cuntlicker", "cuntlicking", "cunts", "cuntvagina", "cuntz", "cyberfuc", "cyberfuck", "cyberfucked", "cyberfucker", "cyberfuckers", "cyberfucking", "damn", "damnation", "darkie", "dead", "dick", "dickhead", "dickpenis", "died", "dik", "dike", "dild0", "dild0s", "dildo", "dildos", "dilld0", "dilld0s", "dingleberry", "dink", "dinks", "dipshit", "dirty", "Dirty Sanchez", "dominatricks", "dominatrics", "dominatrix", "dong", "donkey punch", "douche", "douche bag", "dumbass", "dune coon", "dutch oven", "dyke", "ejaculate", "ejaculated", "ejaculates", "ejaculating", "ejaculatings", "ejaculation", "enema", "enemas", "f u c k", "f u c k e r", "fag", "fag1t", "faget", "fagg1t", "fagget", "fagging", "faggit", "faggot", "faggs", "fagit", "fagot", "fagots", "fags", "fagz", "faig", "faigs", "fantasies", "fart", "farted", "farting", "fartings", "farts", "farty", "fatass", "fatso", "felatio", "fellatio", "fetish", "fingerfuck", "fingerfucked", "fingerfucker", "fingerfuckers", "fingerfucking", "fingerfucks", "fist", "fist fuck", "fistfuck", "fistfucked", "fistfucker", "fistfuckers", "fistfucking", "fistfuckings", "fistfucks", "flagellate", "flesh", "flipping the bird", "frigid", "f u c k", "f.u.c.k.", "fuck", "fuck stick", "fucked", "fucker", "fuckers", "fuckin", "fucking", "fuckings", "fuckme", "fucks", "fudge packer", "Fudge Packer", "fuk", "Fukah", "Fuken", "fuker", "Fukin", "Fukk", "Fukkah", "Fukken", "Fukker", "Fukkin", "fuks", "furburger", "g00k", "gangbang", "gangbanged", "gangbangs", "gash", "gay", "gayboy", "gaygirl", "gays", "gaysex", "gayz", "gazongers", "geisha", "giz", "goatse", "goddamn", "God-damned", "gonads", "gook", "gringo", "guinne", "gun", "guns", "h00r", "h0ar", "h0re", "hard on", "hardcoresex", "hash", "hells", "hiv", "hoar", "homo", "honky", "hooker", "hoor", "hoore", "horniest", "horny", "hot carl", "hotsex", "hussy", "hustler", "hymen", "jack off", "jackass", "jacking off", "jackoff", "jack-off", "jap", "japs", "jerk", "jerkoff", "jerk-off", "jesus", "jesus christ", "jew", "jews", "jiggaboo", "jisim", "jism", "jiss", "jiz", "jizm", "jizz", "jugs", "jungle bunny", "kike", "kill", "killer", "killing", "kinky", "kkk", "klan", "klux", "knives", "knob", "knobs", "knobz", "kock", "kondum", "kondums", "kraut", "kum", "kummer", "kumming", "kums", "kunilingus", "kunt", "kunts", "kuntz", "latex", "Lesbian", "lesbian", "lesbians", "lesbo", "Lezzian", "lingerie", "Lipshits", "Lipshitz", "loser", "love mayo", "lsd", "lust", "lustful", "lusting", "lusty", "mace", "madame", "mafia", "marijuana", "masochist", "masokist", "massterbait", "masstrbait", "masstrbate", "masterbaiter", "masterbate", "masterbates", "masturbate", "masturbating", "Masturbation", "masturbation", "merde", "mistress", "money shot", "moon cricket", "moose knuckle", "Motha Fucker", "Motha Fuker", "Motha Fukkah", "Motha Fukker", "mothafuck", "mothafucka", "mothafuckas", "mothafuckaz", "mothafucked", "mothafucker", "mothafuckers", "mothafuckin", "mothafucking", "mothafuckings", "mothafucks", "mother fucker", "Mother Fucker", "Mother Fukah", "Mother Fuker", "Mother Fukkah", "Mother Fukker", "motherfuck", "motherfucked", "motherfucker", "mother-fucker", "motherfuckers", "motherfuckin", "motherfucking", "motherfuckings", "motherfucks", "mound", "muff", "muff diving", "munitions", "murder", "murderer", "Mutha Fucker", "Mutha Fukah", "Mutha Fuker", "Mutha Fukkah", "Mutha Fukker", "mutilation", "n1gr", "naked", "nakedness", "nastt", "nasty", "naughty", "nazi", "nazis", "negligee", "nerd", "nigga", "nigger", "nigger", "niggers", "nigur", "niiger", "niigr", "nipple", "nob", "nobhead", "nude", "nudity", "nymph", "orafis", "orgasim", "orgasim", "orgasims", "orgasm", "orgasms", "orgasum", "oriface", "orifice", "orifiss", "packi", "packie", "packy", "paki", "pakie", "paky", "panties", "panty", "papist", "pcp", "pecker", "peeenus", "peeenusss", "peenus", "peinus", "pen1s", "penas", "penetration", "penis", "penis-breath", "penises", "penus", "penuus", "perforate the colon", "pervert", "perverted", "phonesex", "Phuc", "Phuck", "Phuk", "phuk", "phuked", "Phuker", "phuking", "phukked", "Phukker", "phukking", "phuks", "phuq", "pimp", "pink sock", "piss", "pissed", "pisser", "pissers", "pisses", "pissin", "pissing", "pissoff", "playmate", "playmates", "polac", "polack", "polak", "Poonani", "Poonanni", "poontang", "porch monkey", "porn", "porno", "pornography", "pornos", "pr1c", "pr1ck", "pr1k", "prairie nigger", "prick", "prickpenis", "pricks", "prostitute", "punk", "puss", "pusse", "pussee", "pussies", "pussy", "pussys", "puuke", "puuker", "queef", "queer", "queers", "queerz", "quickie", "qweers", "qweerz", "qweir", "raghead", "rape", "recktum", "rectum", "rectus", "retard", "rifle", "rifles", "rim job", "rogue", "rum", "rusty trombone", "sadist", "sand nigger", "satan", "satanism", "scank", "schlong", "screw", "screwing", "semen", "sensual", "sensuous", "sex", "sexual", "sexuality", "sexually", "sexy", "Sh!t", "sh1t", "sh1ter", "sh1ts", "sh1tter", "sh1tz", "sheister", "shit", "shit face", "shited", "shitfeces", "shitfull", "shiting", "shitings", "shits", "shitted", "shitter", "shitters", "shitting", "shittings", "Shitty", "shitty", "Shity", "shitz", "shocker", "Shyt", "Shyte", "Shytty", "Shyty", "skanck", "skank", "skankee", "skankey", "skanks", "Skanky", "skeet", "skin flute", "slag", "slant", "slave", "slaves", "slay", "slayer", "sleaze", "slut", "slutprostitute", "sluts", "Slutty", "slutz", "smack", "smut", "snatch", "snowball", "snuff", "son-of-a-bitch", "spank", "spank the monkey", "spanked", "spanking", "Spaz", "sperm", "spic", "spik", "spunk", "strip", "strippers", "striptease", "studs", "suck", "sucker", "sucking", "suicide", "supremacy", "swinger", "swingers", "taint", "taste the waste", "teabagging", "testicle", "testicles", "thc", "tip drill", "tit", "titbreast", "tits", "toss the salad", "towel head", "turd", "twat", "twot", "underwear", "va1jina", "vag1na", "vagiina", "vagina", "vaginas", "vaj1na", "vajina", "vart", "virginity", "virgins", "vullva", "vulva", "w0p", "wanker", "wench", "wenches", "wetback", "Wetback", "wh00r", "wh0re", "whipped", "whiskey", "White Cracker", "Whitey", "whore", "wine", "witchcraft", "wog", "wop", "xrated", "xxx"];
	var lowerCaseCheck = this.toLowerCase(), bl = badwords.length, me = this, clean = "####################";
  RegExp.multiline = true;
	for(var i=0;i<bl;i++) {
		if(lowerCaseCheck.indexOf(badwords[i]) != -1) {
      me = me.replace(new RegExp("" + badwords[i] + "", "gi"), clean.substring(0, badwords[i].length));
    }
	}
	return me;
};

String.prototype.isEmail = function() {
	return (/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(this));
};


// validation functions
////////////////////////////////////////

initFormValidation = function() {
  var start = new Date();
  
  var foc; // where to put focus on form load
  
  // hide empty error spans
  $("span.error").each(function(){
    if(this.innerHTML === "") {
      $(this).hide();
    } else {
      // error condition, so place focus on first error field
      if(!foc) {
        foc = $(this).siblings("input")[0];
        if(!foc) {
          foc = $(this).siblings("select")[0];
        }
      }
    }
  });
  
  // add validation handlers to forms
  $("form.validate").submit(function(e) { 
    return validateForm(this);
  });
  
  // set tab index, disable autocomplete
  var tab = 1;
  $(["form.validate input", "form.validate select", "form.validate textarea"]).each(function() {
    $(this).addClass("tabme");
  });
  $("form.validate .tabme").each(function() {
    if(this.type != "hidden") {
			this.setAttribute("autocomplete", "off");
			this.tabIndex = tab;
      if(tab == 1 && !foc) {
        foc = this;
      }
      tab++;
		}
  });
  
  // show form
  $("form.validate").css("visibility", "visible");
  
  // focus first input or first error input
  if(foc) {
    foc.focus();
  }
  
 //alert(new Date() - start);
};

validateForm = function(f) {
  // validate required fields
  var result = $.map($(f).find(".required").not("span"), function(el) {
    return testField(el, true) ? null : false;
  });
  
  // validate optional fields
  var result2 = $.map($(f).find(".optional"), function(el) {
    return testField(el, false) ? null : false;
  });
  
  if(result[0] === false || result2[0] === false) {
    // focus on first error field
    var errfield = $(".errorfield")[0];
    if(errfield) {
      errfield.focus();
    }
    return false;
  }
  
  return true;
};

testField = function(el, required) {
  var id = el.id;
  
  switch(id) {
    
    case "youremail":
    case "theiremail":
      var v = $(el).val();
      var eid = $("#" + id + "_error");
      if(required) {
        if(v.isEmpty()) {
          showErr(el, eid, "Please type an e-mail address.");
          return false;
        }
      }
      if(!v.isEmpty()) {
        if(!v.isEmail()) {
          showErr(el, eid, "Please type an e-mail address in the following format: yourname@example.com");
          return false;
        }
      }
      hideErr(el, eid);
      break;
      
    case "yourname":
    case "theirname":
      var v = $(el).val();
      var eid = $("#" + id + "_error");
      if(required) {
        if(v.isEmpty()) {
          showErr(el, eid, "Please enter first name.");
          return false;
        } 
      }
      if(!v.isEmpty()) {
         var vp = v.isProfane();
         if(v != vp) {
          $(el).val(vp);
          showErr(el, eid, "Some information may be incorrect or inappropriate. Please try again.");
          return false;
        }
      }
      hideErr(el, eid);
      break;
      
    case "theirmessage":
      var v = $(el).val();
      var eid = $("#" + id + "_error");
      if(required) {
        if(v.isEmpty()) {
          showErr(el, eid, "Please enter a message.");
          return false;
        } 
      }
      if(!v.isEmpty()) {
         var vp = v.isProfane();
         if(v != vp) {
          $(el).val(vp);
          showErr(el, eid, "Some information may be incorrect or inappropriate. Please try again.");
          return false;
        }
      }
      hideErr(el, eid);
      break;
      
    case "txtKeyword":
      var v = $(el).val();
      var eid = $("#" + id + "_error");
      if(required) {
        // part of a validation group? (product and promo advanced search)
        if($(el).parents("fieldset.validate-group").size()) {
          if(v.isEmpty()) { // validate the additional fields in the group
            var result = false;
            $(el).parents("fieldset.validate-group").find(".group").each(function() {
              if(testField(this, false)) result = true;
            });
            if(!result) {
              showErr(el, eid, "Please select or enter at least one search criterion.");
              return false;
            }
          }
        } else if(v.isEmpty()) {
          showErr(el, eid, "Keyword is required.");
          return false;
        }
      }
      hideErr(el, eid);
      break;
      
    case "ddlCategories":
    case "ddlBrands":
    case "ddlTypes":
      var v = $(el).val();
      if(v.isEmpty()) {
        return false;
      }
      break;
      
    case "zip":
      var v = $(el).val();
      var eid = $("#" + id + "_error");
      if(required) {
        if(v.isEmpty()) {
          showErr(el, eid, "Please type a ZIP code.");
          return false;
        }
      }
      if(!v.isEmpty()) {
         if(!v.isZIP()) {
          showErr(el, eid, "Please type a valid 5-digit U.S. ZIP code.");
          return false;
        }
      }
      hideErr(el, eid);
      break;
  }
  return true;
};

hideErr = function(input, el) {
  $(input).removeClass("errorfield");
  el.hide();
  el.html("");
};

showErr = function(input, el, msg) {
  $(input).addClass("errorfield");
  el.hide();
  el.html(msg);
  el.fadeIn(1000);
};

/*
  equalizes the heights of all elements in a jQuery collection
  returns the original jQuery collection
  
  example: $("#col1, #col2, #col3").equalizeCols();
  
  requires: dimensions plugin
  (http://dev.jquery.com/browser/trunk/plugins/dimensions/)
*/
jQuery.fn.equalizeCols = function(){
  var height = 0;
  return this.css("height", "auto").each(function(){
    height = Math.max(height, jQuery(this).outerHeight());
  }).css("height", height + "px");
};


/*
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * $LastChangedDate$
 * $Rev$
 */

jQuery.fn._height = jQuery.fn.height;
jQuery.fn._width  = jQuery.fn.width;

/**
 * If used on document, returns the document's height (innerHeight)
 * If used on window, returns the viewport's (window) height
 * See core docs on height() to see what happens when used on an element.
 *
 * @example $("#testdiv").height()
 * @result 200
 *
 * @example $(document).height()
 * @result 800
 *
 * @example $(window).height()
 * @result 400
 *
 * @name height
 * @type Object
 * @cat Plugins/Dimensions
 */
jQuery.fn.height = function() {
	if ( this[0] == window )
		return self.innerHeight ||
			jQuery.boxModel && document.documentElement.clientHeight ||
			document.body.clientHeight;

	if ( this[0] == document )
		return Math.max( document.body.scrollHeight, document.body.offsetHeight );

	return this._height(arguments[0]);
};

/**
 * If used on document, returns the document's width (innerWidth)
 * If used on window, returns the viewport's (window) width
 * See core docs on height() to see what happens when used on an element.
 *
 * @example $("#testdiv").width()
 * @result 200
 *
 * @example $(document).width()
 * @result 800
 *
 * @example $(window).width()
 * @result 400
 *
 * @name width
 * @type Object
 * @cat Plugins/Dimensions
 */
jQuery.fn.width = function() {
	if ( this[0] == window )
		return self.innerWidth ||
			jQuery.boxModel && document.documentElement.clientWidth ||
			document.body.clientWidth;

	if ( this[0] == document )
		return Math.max( document.body.scrollWidth, document.body.offsetWidth );

	return this._width(arguments[0]);
};

/**
 * Returns the inner height value (without border) for the first matched element.
 * If used on document, returns the document's height (innerHeight)
 * If used on window, returns the viewport's (window) height
 *
 * @example $("#testdiv").innerHeight()
 * @result 800
 *
 * @name innerHeight
 * @type Number
 * @cat Plugins/Dimensions
 */
jQuery.fn.innerHeight = function() {
	return this[0] == window || this[0] == document ?
		this.height() :
		this.css('display') != 'none' ?
		 	this[0].offsetHeight - (parseInt(this.css("borderTopWidth")) || 0) - (parseInt(this.css("borderBottomWidth")) || 0) :
			this.height() + (parseInt(this.css("paddingTop")) || 0) + (parseInt(this.css("paddingBottom")) || 0);
};

/**
 * Returns the inner width value (without border) for the first matched element.
 * If used on document, returns the document's Width (innerWidth)
 * If used on window, returns the viewport's (window) width
 *
 * @example $("#testdiv").innerWidth()
 * @result 1000
 *
 * @name innerWidth
 * @type Number
 * @cat Plugins/Dimensions
 */
jQuery.fn.innerWidth = function() {
	return this[0] == window || this[0] == document ?
		this.width() :
		this.css('display') != 'none' ?
			this[0].offsetWidth - (parseInt(this.css("borderLeftWidth")) || 0) - (parseInt(this.css("borderRightWidth")) || 0) :
			this.height() + (parseInt(this.css("paddingLeft")) || 0) + (parseInt(this.css("paddingRight")) || 0);
};

/**
 * Returns the outer height value (including border) for the first matched element.
 * Cannot be used on document or window.
 *
 * @example $("#testdiv").outerHeight()
 * @result 1000
 *
 * @name outerHeight
 * @type Number
 * @cat Plugins/Dimensions
 */
jQuery.fn.outerHeight = function() {
	return this[0] == window || this[0] == document ?
		this.height() :
		this.css('display') != 'none' ?
			this[0].offsetHeight :
			this.height() + (parseInt(this.css("borderTopWidth")) || 0) + (parseInt(this.css("borderBottomWidth")) || 0)
				+ (parseInt(this.css("paddingTop")) || 0) + (parseInt(this.css("paddingBottom")) || 0);
};

/**
 * Returns the outer width value (including border) for the first matched element.
 * Cannot be used on document or window.
 *
 * @example $("#testdiv").outerWidth()
 * @result 1000
 *
 * @name outerWidth
 * @type Number
 * @cat Plugins/Dimensions
 */
jQuery.fn.outerWidth = function() {
	return this[0] == window || this[0] == document ?
		this.width() :
		this.css('display') != 'none' ?
			this[0].offsetWidth :
			this.height() + (parseInt(this.css("borderLeftWidth")) || 0) + (parseInt(this.css("borderRightWidth")) || 0)
				+ (parseInt(this.css("paddingLeft")) || 0) + (parseInt(this.css("paddingRight")) || 0);
};

/**
 * Returns how many pixels the user has scrolled to the right (scrollLeft).
 * Works on containers with overflow: auto and window/document.
 *
 * @example $("#testdiv").scrollLeft()
 * @result 100
 *
 * @name scrollLeft
 * @type Number
 * @cat Plugins/Dimensions
 */
jQuery.fn.scrollLeft = function() {
	if ( this[0] == window || this[0] == document )
		return self.pageXOffset ||
			jQuery.boxModel && document.documentElement.scrollLeft ||
			document.body.scrollLeft;

	return this[0].scrollLeft;
};

/**
 * Returns how many pixels the user has scrolled to the bottom (scrollTop).
 * Works on containers with overflow: auto and window/document.
 *
 * @example $("#testdiv").scrollTop()
 * @result 100
 *
 * @name scrollTop
 * @type Number
 * @cat Plugins/Dimensions
 */
jQuery.fn.scrollTop = function() {
	if ( this[0] == window || this[0] == document )
		return self.pageYOffset ||
			jQuery.boxModel && document.documentElement.scrollTop ||
			document.body.scrollTop;

	return this[0].scrollTop;
};

/**
 * Returns the location of the element in pixels from the top left corner of the viewport.
 *
 * For accurate readings make sure to use pixel values for margins, borders and padding.
 *
 * @example $("#testdiv").offset()
 * @result { top: 100, left: 100, scrollTop: 10, scrollLeft: 10 }
 *
 * @example $("#testdiv").offset({ scroll: false })
 * @result { top: 90, left: 90 }
 *
 * @example var offset = {}
 * $("#testdiv").offset({ scroll: false }, offset)
 * @result offset = { top: 90, left: 90 }
 *
 * @name offset
 * @param Object options A hash of options describing what should be included in the final calculations of the offset.
 *                       The options include:
 *                           margin: Should the margin of the element be included in the calculations? True by default.
 *                                   If set to false the margin of the element is subtracted from the total offset.
 *                           border: Should the border of the element be included in the calculations? True by default.
 *                                   If set to false the border of the element is subtracted from the total offset.
 *                           padding: Should the padding of the element be included in the calculations? False by default.
 *                                    If set to true the padding of the element is added to the total offset.
 *                           scroll: Should the scroll offsets of the parent elements be included in the calculations?
 *                                   True by default. When true, it adds the total scroll offsets of all parents to the
 *                                   total offset and also adds two properties to the returned object, scrollTop and
 *                                   scrollLeft. If set to false the scroll offsets of parent elements are ignored.
 *                                   If scroll offsets are not needed, set to false to get a performance boost.
 * @param Object returnObject An object to store the return value in, so as not to break the chain. If passed in the
 *                            chain will not be broken and the result will be assigned to this object.
 * @type Object
 * @cat Plugins/Dimensions
 * @author Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
 */
jQuery.fn.offset = function(options, returnObject) {
	var x = 0, y = 0, elem = this[0], parent = this[0], op, sl = 0, st = 0, options = jQuery.extend({ margin: true, border: true, padding: false, scroll: true }, options || {});
	do {
		x += parent.offsetLeft || 0;
		y += parent.offsetTop  || 0;

		// Mozilla and IE do not add the border
		if (jQuery.browser.mozilla || jQuery.browser.msie) {
			// get borders
			var bt = parseInt(jQuery.css(parent, 'borderTopWidth')) || 0;
			var bl = parseInt(jQuery.css(parent, 'borderLeftWidth')) || 0;

			// add borders to offset
			x += bl;
			y += bt;

			// Mozilla removes the border if the parent has overflow property other than visible
			if (jQuery.browser.mozilla && parent != elem && jQuery.css(parent, 'overflow') != 'visible') {
				x += bl;
				y += bt;
			}
		}

		if (options.scroll) {
			// Need to get scroll offsets in-between offsetParents
			op = parent.offsetParent;
			do {
				sl += parent.scrollLeft || 0;
				st += parent.scrollTop  || 0;

				parent = parent.parentNode;

				// Mozilla removes the border if the parent has overflow property other than visible
				if (jQuery.browser.mozilla && parent != elem && parent != op && jQuery.css(parent, 'overflow') != 'visible') {
					y += parseInt(jQuery.css(parent, 'borderTopWidth')) || 0;
					x += parseInt(jQuery.css(parent, 'borderLeftWidth')) || 0;
				}
			} while (parent != op);
		} else
			parent = parent.offsetParent;

		if (parent && (parent.tagName.toLowerCase() == 'body' || parent.tagName.toLowerCase() == 'html')) {
			// Safari doesn't add the body margin for elments positioned with static or relative
			if (jQuery.browser.safari && jQuery.css(parent, 'position') != 'absolute') {
				x += parseInt(jQuery.css(op, 'marginLeft')) || 0;
				y += parseInt(jQuery.css(op, 'marginTop'))  || 0;
			}
			break; // Exit the loop
		}
	} while (parent);

	if ( !options.margin) {
		x -= parseInt(jQuery.css(elem, 'marginLeft')) || 0;
		y -= parseInt(jQuery.css(elem, 'marginTop'))  || 0;
	}

	// Safari and Opera do not add the border for the element
	if ( options.border && (jQuery.browser.safari || jQuery.browser.opera) ) {
		x += parseInt(jQuery.css(elem, 'borderLeftWidth')) || 0;
		y += parseInt(jQuery.css(elem, 'borderTopWidth'))  || 0;
	} else if ( !options.border && !(jQuery.browser.safari || jQuery.browser.opera) ) {
		x -= parseInt(jQuery.css(elem, 'borderLeftWidth')) || 0;
		y -= parseInt(jQuery.css(elem, 'borderTopWidth'))  || 0;
	}

	if ( options.padding ) {
		x += parseInt(jQuery.css(elem, 'paddingLeft')) || 0;
		y += parseInt(jQuery.css(elem, 'paddingTop'))  || 0;
	}

	// Opera thinks offset is scroll offset for display: inline elements
	if (options.scroll && jQuery.browser.opera && jQuery.css(elem, 'display') == 'inline') {
		sl -= elem.scrollLeft || 0;
		st -= elem.scrollTop  || 0;
	}

	var returnValue = options.scroll ? { top: y - st, left: x - sl, scrollTop:  st, scrollLeft: sl }
	                                 : { top: y, left: x };

	if (returnObject) { jQuery.extend(returnObject, returnValue); return this; }
	else              { return returnValue; }
};