/*
 * Fire functions asasp
 */
$(document).ready(function() {

	if(window.console && console.timeEnd)
		console.time('DOMReadyTook');

	$('body').addClass('hasJS');	// useful for css overrides

    // Apply all enhancements
	sectionEdits.init({animSpeed: 300});
    applyEnhancements();
    reOrderables.init();
    checkRow.init();
    excludedEmployers.init();
	buttonsToLinks.init();


	// add extra markup handles to buttons (primarily for IE styling)
	$('input[type=submit], input[type=button]').addClass('button');
	
	
	// Stop accidental submit of the wizard form
	var preventAccidentalSubmit = function(){
		var lastKeyPressWasEnter = false;
		
		$('form.wizardSteps').bind('keypress', function(e){
			var target = e.target;
			if( e.keyCode == 13 && target.nodeName.toLowerCase() != 'textarea' && target.type.toLowerCase() != 'submit'){
				lastKeyPressWasEnter = true;
				mlog('Accidental form submit.');
				e.preventDefault();
			}else{
				lastKeyPressWasEnter = false;
			}
		});

	}();


	// Pull title from input's that have one and use it as example inputs.
	$('input[title], textarea[title]').example(function() {
		return $(this).attr('title');
	});
		
	// On the homepage, add big targets to homepage call to actions
	if( $('body.home').size() ){
		$('#content:not(.incompletecv)').find(".feature .calltoaction h3 a").bigTarget({
			hoverClass: 'over', // CSS class applied to the click zone onHover
			clickZone : 'div:eq(1)' // jQuery parent selector
		});
		$('#content:not(.incompletecv)').find('.feature .calltoaction form').hide();
	}

	// prevent a user from moving away from a page which has un-saved edit states
	$('p.nextStep input').click(function(event){
		var stopDefaultAction = false;
		var firstUnsavedForm;
		var sectorDiv;

		$('input[value=Save]').each(function(){
			var $this = $(this);
			var sectorDiv = $this.parents('.section').eq(0);
			if( (sectorDiv[0] && ((sectorDiv.attr('id') != "tempAjaxDump"))) && ($this.is(':visible')) ){
				firstUnsavedForm = $(sectorDiv).eq(0);
				stopDefaultAction = true;
				return false;
			}
		});
		
		if(stopDefaultAction){
			alert('There are unsaved forms on the page.\r\nPlease save them to continue.');
			$.scrollTo(firstUnsavedForm, 400, {easing: 'easeInOutExpo', offset: {left:0, top: -50}, onAfter: function(){
					firstUnsavedForm
						.animate( { backgroundColor: '#fdfcb9' }, 150)
						.animate( { backgroundColor: '#fff' }, 500);
			} });
			

			event.preventDefault();
			return false;
		}
	});
	

	if(window.console && console.timeEnd)
		console.timeEnd('DOMReadyTook');

});



	// wrapper function for console.log without causing problems in IE
	(function(){
		window.mlog = function(){
		  if(!jQuery.browser.mozilla)
		  	return false;
			
		  // store logs to an array for reference
		  mlog.history = mlog.history || [];
		  mlog.history.push(arguments);
		 
		  window.console && console.log[console.firebug ? 
		    'apply' : 'call'](console,Array.prototype.slice.call(arguments));
		};
		 
		// logargs(this); == console.log(this,arguments);
		window.logargs = function(context){
		  // grab the calling functions arguments
		  mlog(context,arguments.callee.caller.arguments); 
		};
		
	})();	


	// Simple Yellow Fade jQuery method
	(function($) {
	   $.fn.yellowFade = function() {
	    return this.css({backgroundColor: "#ffffcc"})
	    .animate({backgroundColor: "#ffffff"}, 1500, "linear");
	   };
	 })(jQuery);


	jQuery.extend({
		encHTML: function(html) { 
			return html.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/'/g, '&#39;').replace(/"/g, '&quot;'); 
		},
		decHTML: function(html) { 
			return html.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&quot;/g, '"').replace(/&#39;/g, "'"); 
		}
	}); 
	 

    // jQuery function for getting a structdata input
    jQuery.extend({
        structId: function($submit) {
            return $submit.attr('name').split(':')[1];
        },
        structInput: function(structId, name) {
            return jQuery("input[name='StructData:" + structId + ":" + name + "']").eq(0);
        },        
        structValue: function(structId, name) {
            return jQuery.structInput(structId, name).val();
        }
    });





   /*
	* Custom fade in/out to cope with IE's inability to apply cleartype when a filter is also present.
	* Source: http://blog.bmn.name/2008/03/jquery-fadeinfadeout-ie-cleartype-glitch/
	*/
	(function($) {
		$.fn.customFadeIn = function(speed, callback) {
			$(this).fadeIn(speed, function() {
				if(jQuery.browser.msie)
					$(this).get(0).style.removeAttribute('filter');
				if(callback != undefined)
					callback();				
			});
		};
		$.fn.customFadeOut = function(speed, callback) {
			$(this).fadeOut(speed, function() {
				if(jQuery.browser.msie)
					$(this).get(0).style.removeAttribute('filter');
				if(callback != undefined)
					callback();
			});
		};
	})(jQuery);


	$.fn.slideFadeToggle = function(speed, easing, callback) {
		return this.animate({opacity: 'toggle', height: 'toggle'}, speed, easing, callback);
	};


	function nl2br(text){
		text = escape(text);
		if(text.indexOf('%0D%0A') > -1){
			re_nlchar = /%0D%0A/g ;
		}else if(text.indexOf('%0A') > -1){
			re_nlchar = /%0A/g ;
		}else if(text.indexOf('%0D') > -1){
			re_nlchar = /%0D/g ;
		}
		return unescape( text.replace(re_nlchar,'<br />') );
	};

	  
	var buttonsToLinks = function(){
		return {
			init: function(root){
				var $root = root ? $(root) : $(document);
				// Checkout page - replace remove buttons with links for aesthetic reasons.
				$root.find('.buttonToLink, .replacebuttons input[type=submit]').each(function(){
					var $this = $(this);
					var $link = $('<a href="#"></a>').text( $this.val() );
				
					$this.after($link)
						.css({'position': 'absolute', 'left': '-1000em', 'width': '4em'});
						
					// when the link is pressed click the button instead.
					$link.click(function(e){
						$(this).prev().click();	// click the button we previously hid.
						e.preventDefault();		// we don't want to carry on with the link click thankyou.
						return false;
					});
					
				});
			}
		}
	}();

	var checkRow = function(){
		return {
			init: function(root){
				var $root = root ? $(root) : $(document);

				// show the selections under the check-scroller on page load & on check.
				// create selections container
				$root.find('.check-scroller').each(function(){
					var $this = $(this);
					$this.disableTextSelect();
					$this.parent().append('<div class="checkbox-selection"></div>').end()
						.next().css({width: $this.width() });
					
				});
				
										
				// Read current selections & show labels
				$root.find('.check-scroller').each(function(){
					var $checkedBoxes = $(this).find('input:checked');
					if( $checkedBoxes[0] ){
						$checkedBoxes.each(function(){
							checkRow.add(this, false);
						});
					}
				});
				
				// add selection feedback when options are toggled
				$root.find('.check-scroller input[type=checkbox]').click(function(){
					if( this.checked ){
						checkRow.add(this, true);
						
						// check parent, if exists.
						var $parentCheck = $(this).parents('li').eq(1).find('input[type="checkbox"]:first:not(:checked)');
						if($parentCheck[0]){
							$parentCheck.attr('checked', 'checked');
							checkRow.add($parentCheck.get(), true);
						}
							
					}else{
						checkRow.remove(this);
						
						// also uncheck child inputs
						$(this).parent().find('ul input[type="checkbox"]').each(function(){
							$(this).removeAttr('checked');
							checkRow.remove(this);
						})
							
					}
				});
				
			},
			add: function(elem, yellowFade){
				$elem = $(elem);
				$elem.parent().addClass('checked-row');
				var $resultsDiv = $elem.parents('.check-scroller').next();
				var label = $elem.next().text().replace(/ /g, '&nbsp;');
				var generatedId = 'option-'+$elem.attr('id').replace(/\./g,'-');
				
				if( !$resultsDiv.find('#'+generatedId)[0] ) {
					$resultsDiv.append('<div id="'+generatedId+'" class="selection-item">' + label + '</div>');
					if(yellowFade){
						$('#'+generatedId).yellowFade();
					}
				}

			},
			remove: function(elem){
				var $elem = $(elem);
				$elem.parent().removeClass('checked-row');
				$('#option-'+$elem.attr('id').replace(/\./g,'-')).remove();
			}
		}
	}();
	


	var twisters = function(){
		
		var options = {
			targetClass: 'div.twistReveal',
			twisterHandle: 'ul.twister > li > a.twister',
			twisterContainerElem: 'li'
		};
		
		return {
			init: function(node){
				
				var $root = node ? $(node) : $(document);
				
				$root.find(options.twisterHandle).each(function(){
					// find the target that's to hide/reveal & stick an inner div for extra styling hooks (padding)
					var $handle = $(this);
					var $targetNode = $handle.parent(options.twisterContainerElem)
										.find(options.targetClass)
										.wrapInner('<div class="inner"></div>');
					
					$targetNode.addClass('twister-collapsed');
					$handle.addClass('twister-collapsed');

					$handle.click(function(){
						var $this = $(this);
						var targetNode = $this.parent(options.twisterContainerElem).find(options.targetClass);
						if( targetNode.hasClass('twister-collapsed') ){
							targetNode.slideDown('slow', 'easeOutExpo');
							targetNode.removeClass('twister-collapsed');
							$this.removeClass('twister-collapsed');
						}else{
							targetNode.slideUp('slow', 'easeOutExpo');
							targetNode.addClass('twister-collapsed');
							$this.addClass('twister-collapsed');
						}
						
						// don't do anything with the click
						return false;
					});
	
	
				});
				
			}
		}	
		
	}();


	function textareaLimit(node){
		// HTML layout expected to be based on this:
		//
		// 		<div class="inputField">
		// 			<p><textarea name="Summary" class="width0 limit" rows="10"></textarea></p>
		// 			<p class="charCount">0 characters (max. <span class="limit">2500</span>)</p>
		// 		</div>
		//
		var $root = node ? $(node) : $(document);

		$root.find('textarea.limit').each(function(){
			var $this = $(this);
			var limit = $this.parents('div').eq(0).find('p.charCount span.limit').text();
			if(limit){
				$this.maxLength(limit, $(this).parents('div').find('.charCount').eq(0) );
			}
		});
	};



   /*
	* Immitate a maxlength property for textareas
	*/
	jQuery.fn.maxLength = function(max, statusTarget){
		this.each(function(){
			var type = this.tagName.toLowerCase();
			var origBgColor = statusTarget.css('backgroundColor');
			var inputType = this.type? this.type.toLowerCase() : null;
			if(type == "input" && inputType == "text" || inputType == "password"){
				this.maxLength = max;
			}
			else if(type == "textarea"){
				this.onkeypress = function(e){
					var ob = e || event;
					var keyCode = ob.keyCode;
					var hasSelection = document.selection ? document.selection.createRange().text.length > 0 : this.selectionStart != this.selectionEnd;
					var countMsg = this.value.length + ' characters (max. '+max+')';
					
					if( keyCode == 46 ){
						// delete key.
						statusUpdate(countMsg);
					}else{
						if( (this.value.length >= max && (keyCode > 50 || keyCode == 118 || keyCode == 46 || keyCode == 32 || keyCode == 0 || keyCode == 13) && !ob.ctrlKey && !ob.altKey && !hasSelection) ){
							statusUpdate('Character limit reached!', true);
							return false;
						}else{
							statusUpdate(countMsg);
						}
					}
				};
				this.onkeyup = function(){
					if(this.value.length > max){
						this.value = this.value.substring(0,max);
						statusUpdate('Too much information! Content was trimmed at ' + max + ' characters.', true);
					}else{
						statusUpdate(this.value.length+' characters (max. '+max+')');
					}
				};
			}
		});
		function statusUpdate(msg, showHighlight){
			if(!statusTarget.is(':animated')){
				if( showHighlight ){
					statusTarget.html(msg);
					statusTarget
						.animate( { backgroundColor: '#A50E0E', color: '#fff' }, 250)
						.animate( { backgroundColor: '#A50E0E', color: '#fff' }, 500) // pause
						.animate( { backgroundColor: '#fff', color: '#434343' }, 500);
				}else{
					statusTarget.html(msg);
				}
			}
		}
	};




   /*
	* Animated re-ordering of repeating sections (emp. history, edu. history etc.)
	*/
	var reOrderables = function(){
		
		var options = {
			animSpeed: 1200,
			easingMethod: 'easeInOutExpo',
			containerSelector: '.ajax-reArrangeSection',
			eachOrderableSelector: '.ajax-reOrderable',
			reportToURL: false
		}
		var lastChange = {};	
		
		
		function getOrder($container, direction){
			var sections = $container.find(options.eachOrderableSelector);
			var structId = $.structId( $container.find('input.' + direction).eq(0) );			
			
			var sectionId = $.structValue(structId, 'Section');			
			var cid = $('input[name=Candidate.CandidateId]').eq(0).attr('value');
			var results = {
				Section: sectionId,
				"Candidate.CandidateId": cid
			};
			
			jQuery.each(sections, function(i){
			    var $ceid = $(this).find('input[name$=CandidateElementId]');
			    results["CandidateElementIds." + i] = ($ceid && $ceid.length > 0)
				                                        ? $ceid.eq(0).val()
				                                        : null;
				
                // Update OrderInCollection inputs
				$(this).find('input[name$=OrderInCollection]').each(function(){ 
                    $(this).val(i);
				});
				
				$(this).find('input[name^="Candidate"], select[name^="Candidate"], textarea[name^="Candidate"]').each(function(){ 
					var origName = $(this).attr('name');
					if(origName.indexOf('.') == "-1"){
						// doesn't have the delimiter, doesn't need renumbering.
						return;
					}
					var nameparts = origName.split('.');
					nameparts[2] = i;

					var newName = nameparts.join('.');

					// Set id and name with new id
					$(this).attr('name', newName);
				});
				
				mlog(this, i);

			});
			return results;
		}
		
		function saveSectionOrder(order){
			if(options.reportToURL){
				$.ajax({
					type: 'POST',
					timeout: 5000,
					url: options.reportToURL,
					data: order,
					success: function(msg){
						mlog('Successfully saved section oredering.');
					},
					error: function(XMLHttpRequest, textStatus, errorThrown) {
						alert( 'Error: There was a problem saving the re-ordering, please refresh your browser and try again.\r\n\r\n(Message from the server: ' + XMLHttpRequest.statusText +')');
						//revert the change
						var reqDir = lastChange.direction == "up" ? "down" : "up";
						move(lastChange.node, reqDir, true);
					}
				});
			}
		}
		
		function move($sourceDiv, reqDir, noSave){
			lastChange = {node: $sourceDiv, direction: reqDir};
			
			
			// disabled all inputs
			var $wrapperDiv = $sourceDiv.parents(options.containerSelector).eq(0);
			$wrapperDiv.find(options.eachOrderableSelector + ' .actions input').attr('disabled', 'disabled');
			
			// find the node we want to switch with
			var $underDiv = false;
			if(reqDir == 'up'){
				$sourceDiv.prevAll().each(function(){
					if( $(this).hasClass(options.eachOrderableSelector) ){
						$underDiv = $(this);
						return false; // break from the each()
					}
				});
				// var $underDiv = ($sourceDiv.prev().hasClass(options.eachOrderableSelector)) ? $sourceDiv.prev() : false ;
			}else if(reqDir == 'down'){
				$sourceDiv.nextAll().each(function(){
					if( $(this).hasClass(options.eachOrderableSelector) ){
						$underDiv = $(this);
						return false; // break from the each()
					}
				});
				// var $underDiv = ($sourceDiv.next().hasClass(options.eachOrderableSelector)) ? $sourceDiv.next() : false ;
			}else{
				return false;
			}
			
			if($underDiv == false){
				// didn't get the node to swap with.
				return false;
			}

			// var $underDiv	= (reqDir == 'up') ? $prevNode.eq(0) : $nextNode.eq(0);

			// create the dummy divs that'll be animated
			var $dummyTopDiv = $sourceDiv
									.clone()
									.css({
										position: 'absolute',
										top: $sourceDiv.position().top,
										left: $sourceDiv.position().left,
										width: $sourceDiv.width(),
										zIndex: 1000
									})
									.appendTo($wrapperDiv);
			
			var $dummyUnderDiv = $underDiv
									.clone()
									.css({
										position: 'absolute',
										top: $underDiv.position().top,
										left: $underDiv.position().left,
										width: $underDiv.width(),
										zIndex: 100
									})
									.appendTo($wrapperDiv);

			// hide the dom nodes we want to keep
			$sourceDiv.css({visibility: 'hidden'});
			$underDiv.css({visibility: 'hidden'});
			$dummyTopDiv.addClass('moveOver');
			$dummyUnderDiv.addClass('moveUnder');

			// find out the destinations of where the dummy nodes are to animate to
			if(reqDir == 'up'){
				var topDivNewPosition		= $dummyUnderDiv.position().top;
				var underDivNewTopPosition	= ($dummyTopDiv.position().top) + ($dummyTopDiv.height() - $dummyUnderDiv.height());
				$underDiv.before($sourceDiv);
			}else{
				var topDivNewPosition		= ($dummyUnderDiv.position().top) + ($dummyUnderDiv.height() - $dummyTopDiv.height());
				var underDivNewTopPosition	= $dummyTopDiv.position().top;
				$underDiv.after($sourceDiv);
			}

			// do animation. once complete, reveal the switched 'real' nodes
			var almostFinishedFlag  = false;
			$dummyUnderDiv
				.animate({'left': 10}, {
					queue: true,
					duration: options.animSpeed/2
				})
				.animate({'left': 0}, {
					queue: true,
					duration: options.animSpeed/2
				})
				.animate({'top': underDivNewTopPosition }, {
					queue: false,
					duration: options.animSpeed,
					easing: options.easingMethod
				})
				
			$dummyTopDiv
				.animate({'left': -10}, {
					queue: true,
					duration: options.animSpeed/2
				})
				.animate({'left': 0}, {
					queue: true,
					duration: options.animSpeed/2
				})
				.animate({'top': topDivNewPosition }, {
					queue: false,
					duration: options.animSpeed,
					easing: options.easingMethod,
					step: function(now, obj){
						// check to see if we're 2/3s of the way there, if so, remove the mouseOver CSS class
						// this is used 
						var point = parseInt(obj.start + ((obj.end - obj.start) * 2/3));
						now = parseInt(now);
						
						if(reqDir == "up"){
							if( (now <= point) && !almostFinishedFlag ){
								$(this).removeClass('moveOver');
								almostFinishedFlag  = true;
							}
						}else{
							if( now >= obj.end/3 ){
								
							}
							if( (now >= point) && !almostFinishedFlag  ){
								$(this).removeClass('moveOver');
								almostFinishedFlag  = true;
							}
						}
					},
					complete: function(){
						$dummyTopDiv.add( $dummyUnderDiv ).remove();
						$sourceDiv.removeAttr('style');
						$underDiv.removeAttr('style');

						if(!noSave){
							saveSectionOrder( getOrder($sourceDiv.parent().eq(0), lastChange.direction) );
						}

						updateButtons( $underDiv.parent() );
					}
				});


			
		}

		function updateButtons(node){
			var $root = node ? $(node) : $(options.containerSelector);
			
			$root.each(function(){
				var allInputs = $(this).find(options.eachOrderableSelector + ' input');
				allInputs
					.removeAttr('disabled', 'disabled')
					.removeClass('reOrderablePos-first')
					.removeClass('reOrderablePos-last');

				var firstUpButton = $(this).find(options.eachOrderableSelector + ' .up:first').eq(0).attr('disabled', 'disabled');
				firstUpButton
					.addClass('reOrderablePos-first');

				var lastDownButton = $(this).find(options.eachOrderableSelector + ' .down:last').eq(0).attr('disabled', 'disabled');
				lastDownButton
					.addClass('reOrderablePos-last');

			});
		}		

		function attachClicks(node){
			var $root = node ? $(node) : $(options.containerSelector);
			
			$root.find('input.up, input.down').unbind('click').click(function(){		
				var $clickedDiv	= $(this).parents(options.eachOrderableSelector).eq(0);
				var direction	= ($(this).hasClass('up')) ? 'up' : 'down';
				move($clickedDiv, direction);
				return false;
			});
		};


		return {
			init: function(){
				// mlog('reOrderables.init()');

				if( document.body.innerHTML.indexOf(options.containerSelector) ){
					//updateButtons();
					attachClicks();
				}else{
					// mlog('No reOrderables found.');
					return false;
				}
			},
			updateSection: function(node){
				// mlog('reOrderables.updateSection: ', node);
				// expecting to recieve a newly inserted section item
				if( node.hasClass(options.eachOrderableSelector) ){
					updateButtons( node.parent() );
				}else{
					updateButtons(node);
				}
				attachClicks(node);
			},
			attachClicks: function(node){
				// mlog('reOrderables.attachClicks: ', node);
				attachClicks(node);
			},
			updateButtons: function(node){
				mlog('reOrderables.updateButtons: ', node);
				updateButtons(node);
			},
			reportToURL: function(url){
				options.reportToURL = url;
			}
		}
		
	}();






	/*
	 * Enable/disable to dates in date pairs.
	 */
	var endDateOverrides = function(){
		
		function findControls(e){
			return $(e).parents('div.inputField').find('textarea, select, input:not(.endDateOverride)');
		}

		function handler(e){
			if( $(e).is(':checked') ){
				findControls(e).each(function(){
					$(this).attr('disabled', 'disabled');
				});
			}else{
				findControls(e).each(function(){
					$(this).removeAttr('disabled');
				});
			}
	
		}
		
		return {
			enable: function(node){
				var $root = node ? $(node) : $('.section');
				var $OverRideChecks = $root.find('input.endDateOverride');
				
				$OverRideChecks.each(function(){
					handler(this);
				});
				
				$OverRideChecks.click(function(){
					handler(this);
				});
				
			}
		}
	}();
	

	var attachLocationAutoComplete = function(){
		var targetURL; // url of the webservice. provided in the html template
		
		return {
			attach: function(node){
				var $node = node ? $(node) : $(document);
				var $nextElem = $node.find("input.locationautocomplete").next();
				if($nextElem.is('input[type=submit]')){
					$nextElem.hide();
				}
				$node.find("input.locationautocomplete").each(function(){
					var $this = $(this);
					var $container = $this.parents('.locationfieldset').eq(0);
					
					var fields = {
						displayfield: $this,
						radiusfield: $container.find("select.locationradius").eq(0)
					};
					
					$this.autocomplete({
						url: targetURL,
						minChars: 3,
						delay: 50,
						scroll: false,
						scrollMax: 10,
						highlight: false,
						parse: function(obj){
							obj = $.evalJSON(obj);
							var parsedObj = [];
							jQuery.each(obj, function() {
								parsedObj.push({ result: this.Source, value: this.DisplayValue, data: this });
							});
							return parsedObj;
						},
						result: function(n, data){
							var radiusSelector = $(this).parents('tr').find('.locationradius');
							if(data.RadiusAllowed == false){
								mlog('Radius not allowed for this location: ', data.DisplayValue);
								radiusSelector.attr('disabled', 'disabled');
							}else{
								radiusSelector.removeAttr('disabled');
							}
						},
						formatItem: function(data, i, max, datavalue, term){
							return datavalue;
						}
					});

				});
			},
			init: function(){
				if(targetURL != ""){
					this.attach();
				}
			},
			serviceUrl: function(url){
				targetURL = url;
			}
		}
		
	}();


	var excludedEmployers = function(){
		var serviceUrl = '';	// url to hit with block/unblock updates
		var $searchTextBox;		// input holding search string
		var $searchResults;		// search results container
		var $blockedResults;	// blocked recruiters container
		var $form;
		var CandidateId;
		var UserId;
		
		var AJAXSpinner = {
			on: function(){ $searchTextBox.addClass('loading') },
			off: function(){ $searchTextBox.removeClass('loading') }
		}

		function updateSearchResults(html){
			$searchResults.html(html);							
			buttonsToLinks.init($searchResults);
			$searchResults.find('input[type=submit]').click(function(){
				$(this).parents('div').eq(0).hide();
				setRecruiterState( $(this).parent(), this );
				return false;
			});
		}
		
		function updateBlockedResults(html){
			if(html){
				$blockedResults.html(html);
				buttonsToLinks.init($blockedResults);
			}
			
			$blockedResults.find('input[type=submit]').click(function(){
				setRecruiterState( $(this).parent(), this );
				return false;
			});
		}
		
		function search(str, formData){
			if(str.length < 2){
				alert('Employers search needs at least 2 characters.');
				return false;
			}
			
			AJAXSpinner.on();
			
			$.ajax({
				type: "POST",
				timeout: 5000,
				url: serviceUrl,
				data: formData,
				success: function(data){
					AJAXSpinner.off();
					updateSearchResults(data);
				},
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					AJAXSpinner.off();
					alert( 'Error: There was a problem trying to perform that search.\r\nPlease refresh your browser and try the search again.');
				}
			});

		}
		
		function setRecruiterState($recruiterFormData, node){

			if( $recruiterFormData.is('td') ){
				var $elWrapper = $recruiterFormData.parents('tr').eq(0);
			}else if( $recruiterFormData.is('span') ){
				var $elWrapper = $recruiterFormData.parents('li').eq(0);
			}

			var formData = {};
			formData['Candidate.CandidateId'] = CandidateId;
			formData['UserId'] = UserId;

			AJAXSpinner.on();

			// gather data
			$recruiterFormData.find(':text, :radio:checked, :checked, select, textarea, input:hidden, input:submit')
				.each(function() {
					formData[this.name] = this.value;
				});

			$.ajax({
				type: "POST",
				timeout: 5000,
				url: serviceUrl,
				data: formData,
				success: function(data){
					AJAXSpinner.off();
					$elWrapper.slideUp('fast', function(){
						$(this).remove();
					});

					updateBlockedResults(data);
				},
				error: function(XMLHttpRequest, textStatus, errorThrown) {
					AJAXSpinner.off();
					alert( 'Error: There was a problem trying to do that search, please refresh your browser and try again.\r\n\r\n(Message from the server: ' + XMLHttpRequest.statusText +')');
				}
			});

		}
		
		return {
			init: function(){
				if(serviceUrl == ''){
					mlog('excludedEmployers: No service URL set for company lookup');
					return false;
				}else{
					if( $('.companyLookup')[0] ){
						
						$blockedResults = $('#blockedCompanies div');	// blocked recruiters container
						$form = $('.companyLookup').parents('form').eq(0);
						$searchTextBox = $form.find('input[type=text]').eq(0);
						$searchResults = $form.find('.searchResults').eq(0);

						CandidateId = $form.find('input[name=Candidate.CandidateId]').eq(0).val();
						UserId = $form.find('input[name=UserId]').eq(0).val();
						
						updateBlockedResults(false);
						
						$('.companyLookup input[type=submit]').click(function(){
							
							var formData = {};
							formData['Candidate.CandidateId'] = CandidateId;
							formData['UserId'] = UserId;
							
							// gather data
							$form.find('.companyLookup').find(':text, :radio:checked, :checked, select, textarea, input:hidden, input:submit')
								.not('.searchResults *')
								.each(function() {
									formData[this.name] = this.value;
								});
							
							var searchStr = $.trim( $form.find('input[type=text]').val() );								
							search( searchStr, formData );
							
							// stop click going anywhere.
							return false;
							
						});
					}
				}
			},
			serviceUrl: function(url){
				if(url && typeof(url) == 'string'){
					serviceUrl = url;
				}
			}
		}
		
	}();



	
	// Enables any input that is disabled and has the class ajax-enable
	var ajaxEnabler = function(){
		return {
			init: function(){
				this.attach();
			},
			attach: function(node){
				var $node = node ? $(node) : $(document);
				$node.find('input.ajax-enable:disabled')
					.not('.reOrderablePos-first, .reOrderablePos-last')
					.removeAttr('disabled', 'disabled');
			}
		}
	}();
	

	function applyEnhancements(root){
		var $root = root ? $(root) : $(document);

		if(root){
			reOrderables.updateSection( $root );
			//reOrderables.attachClicks( $root );

			// Pull title from input's that have one and use it as example inputs.
			$root.find('input[title], textarea[title]').example(function() {
				return $(this).attr('title');
			});
		}
		
		//sectionEdits.attachActionClicks( $root );
		// revealableSections.init( $root );
		// addRemoveRows.init( $root );		
		//reOrderables.init( $root );
		
		attachLocationAutoComplete.attach( $root );
		endDateOverrides.enable( $root );
		textareaLimit( $root );
		ajaxEnabler.init( $root );
		twisters.init( $root );
		

	}


	// Hide CV History error message when an education/employment record is successfully saved
	var hideCVHistoryError = function() {
	    $('#cvhistoryerror').each(function() {
	        var $this = $(this);

	        if ($this.attr('display') != 'none')
	            $this.fadeTo(200, 0);
	    });
	};