/**
 * rpm JQuery Plugin
 *	overview: This plugin provides utility functions to be used throughout RealPageMaker products.
 *
 *	usage: used on all website pages as a utility class
 *	dependencies: jQuery, jquery.form.js, jquery.backOffice.js (if logged into backoffice), 
 */
(function($) {

	/** This function binds the given function to the close event of the Thickbox
	  *
	  * @param onClose function to be called once the thickbox closes
	  */
	$.bindThickboxClose = function(onClose) {
		var self = this;
		self.onClose = onClose;
		$(".blockOverlay").click(function(){return self.onClose();});
		document.onkeyup = function(e){
			if (e == null) { // ie
				keycode = event.keyCode;
			} else { // mozilla
				keycode = e.which;
			}
			if(keycode == 27){ // close
				self.onClose();
			}
		};
		document.onkeydown = function(e){
			if (e == null) { // ie
				keycode = event.keyCode;
			} else { // mozilla
				keycode = e.which;
			}
			if(keycode == 27){ // close
				self.onClose();
			}
		};
	}
	
	/*
	 * Compares whether two colors are equal. Accepts colors in both hex and RGB format
	 *
	 * @param color1 First color
	 * @param color2 Second color
	 */
	$.compareColors = function(color1, color2){
		// utility functions
		var toHex = function(N) {
 			if (N==null) 
 				return "00";
 			N=parseInt(N);
 			if (N==0 || isNaN(N)) 
 				return "00";
 			N=Math.max(0,N);
 			N=Math.min(N,255);
 			N=Math.round(N);
 			return "0123456789ABCDEF".charAt((N-N%16)/16) + "0123456789ABCDEF".charAt(N%16);
		}

		var RGBtoHex = function(rgbColor) {
			//rgb(123, 116, 165);
			var r = $.trim(rgbColor.substring(rgbColor.indexOf('(')+1, rgbColor.indexOf(',')));
			var g = $.trim(rgbColor.substring(rgbColor.indexOf(',')+1, rgbColor.lastIndexOf(',')));
			var b = $.trim(rgbColor.substring(rgbColor.lastIndexOf(',')+1, rgbColor.lastIndexOf(')')));
			return toHex(r)+toHex(g)+toHex(b)
		};
		
		
		// convert colors to hex if necessary
		if(color1.toLowerCase().indexOf('rgb') >= 0)
			color1 = RGBtoHex(color1);
		if(color2.toLowerCase().indexOf('rgb') >= 0)
			color2 = RGBtoHex(color2);

		// check if they match
		return(color1.replace('#', '') == color2.replace('#', ''));
	}

	/*
	 * shows a png image appropriately
	 *
	 * @param imageId id of image 
	 * @param imageId id of image 
	 */
	$.rpmImageSafe = function(imageId, imagePath, imageWidth, imageHeight, imageClass, imageAlt, browser){
		if((browser.isIE55||browser.isIE6up) && browser.isWin32){
			return('<div style="height:'+imageHeight+'px; width:'+imageWidth+'px; filter:progid:DXImageTransform.Microsoft.AlphaImageLoader (src=\''+imagePath+'\', sizingMethod=\'scale\')" id="'+imageId+'" class="'+imageClass+'"></div>');
		}else{
			return('<img src="'+imagePath+'" width="'+imageWidth+'" height="'+imageHeight+'" name="'+imageId+'" border="0" class="'+imageClass+'" alt="'+imageAlt+'" />');
		}
	}

	/**
	 * Binds the jQuery datepicker module to any and all input_datetime fields
	 *
	 */
	$.bindDatePickers = function(){
		$('.date').datepicker({
			showOn: 'both',
			firstDay: 1,
			buttonImageOnly: true, 
			buttonImage: '/t/resources/rpm3.0/images/icons/calendar.png',
			dateFormat: 'yy-mm-d'
		});
	}

	/**
	 * Returns a string containing a price formatted with commas
	 *
	 * @param nStr an input value to be formatted
	 * @return string return value containing formatted price string
	 */
	$.formatPrice = function (nStr){
		nStr += '';
		x = nStr.split('.');
		x1 = x[0];
		x2 = x.length > 1 ? '.' + x[1] : '';
		var rgx = /(\d+)(\d{3})/;
		while (rgx.test(x1)) {
			x1 = x1.replace(rgx, '$1' + ',' + '$2');
		}
		return x1 + x2;
	}

	/**
	 * preloads an array of images
	 *
	 * @param imgArray array of images to preload
	 */
	$.preloadImages = function(imgArray){
		for(var i = 0; i < imgArray.length; i++){
			jQuery("<img>").attr("src", imgArray[i]);
		}
	}

	/**
	 * preloads an array of images
	 *
	 * @param imgArray array of images to preload
	 */
	$.fn.clearApplet = function(){
		return this.each( function(){
			if(document.getElementById(this.id)){
				document.getElementById(this.id).innerHTML = "";
			}
		});
	}

	/**
	 * binds a hide/show optional fields button
	 *
	 * @param speed speed at which the hide/show operation takes place (Options: slow, fast)
	 */
	$.fn.bindHideShow = function(speed){
		return this.each(function(){
			var self = $(this);
			self.before('<div style="text-align:center;margin-top:5px;">+ <a href="" id="'+self.attr('id')+'MoreOptions'+'">More Options</a></div>');
			self.prepend('<div style="text-align:center;margin-top:5px;">- <a href="" id="'+self.attr('id')+'LessOptions'+'">Hide Options</a></div>');
			
			$('#'+self.attr('id')+'MoreOptions').click(function(){
				$(this).parent().slideUp(speed, function(){
					self.slideDown(speed);
				});
				return false;
			});
			
			$('#'+self.attr('id')+'LessOptions').click(function(){
				self.slideUp(speed, function(){
					$('#'+self.attr('id')+'MoreOptions').parent().slideDown(speed);
				});
				return false;
			});
			return false;
		});
		
	}
	

	/**
	 * Utility to encode the HTML for the URL
	 *
	 * @param html string with the HTML that needs to be converted
	 * @return HTML encoded into URL format
	 */
	$.encodeEditorHTML = function(content){
		content = content.replace(/\r\n/g,"\n");
		var utftext = "";
		
		for (var n = 0; n < content.length; n++) {
			var c = content.charCodeAt(n);
			if (c < 128) {
				utftext += String.fromCharCode(c);
			}else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}
		}
	        //return(escape(utftext)); // doesn't encode plus signs (+)
			return(encodeURIComponent(utftext));
	}

	/**
	 * Returns the HTML that composes the whole element.
	 * http://blog.brandonaaron.net/2007/06/17/jquery-snippets-outerhtml/
	 */
	$.fn.outerHTML = function() {
		return $('<div>').append( this.eq(0).clone() ).html();
	};
	
	/**
	*move option items between two multi- selectable combo boxs
	*@param  sourceId combo Box Id, originally hold  some options
	*@param  destId combo Box Id, can add options from the source
	*@param addButtonId  button for moving options from the source to dest combo box
	*@param removeButtonId button for moving options from the dest back to the source combo box
	*/
	//$.initComboBoxTransfer = function(sourceId,destId,addButtonId,removeButtonId){
	$.initComboBoxTransfer = function(componentId){
		var sourceId = componentId + "_left"; // _src
		var destId = componentId + "_right"; // _dest
		var addButtonId = componentId + "_add";
		var removeButtonId = componentId + "_remove";
		var sourceCount = 0;
		var destCount = 0;
		
		//initialize counters
		sourceCount = count(sourceId);
		destCount = count(destId);

		//display counters		
		$("#" + sourceId+"Counter").html(sourceCount+" item(s)");
		$("#" + destId+"Counter").html(destCount+" item(s)");
		
	
		$("#" + addButtonId).click(function(){
			moveitem($("#" + sourceId),$("#" + destId));
			sourceCount = count(sourceId);
			destCount = count(destId);
			$("#" + sourceId+"Counter").html(sourceCount+" item(s)");
			$("#" + destId+"Counter").html(destCount+" item(s)");
			
		});
		 
		$("#" + removeButtonId).click(function(){
			moveitem($("#" + destId),$("#" + sourceId));
			sourceCount = count(sourceId);
			destCount = count(destId);
			$("#" + sourceId+"Counter").html(sourceCount+" item(s)");
			$("#" + destId+"Counter").html(destCount+" item(s)");
		}); 
		 
		function moveitem(source,dest){
			source.children("option").filter(":selected").each(function(x){			
				dest.append($(this).outerHTML());
				$(this).remove();
			});
		}

		function count(id){
			var count = 0;
			$("#"+id).children("option").each(function(x){
				count += 1;
			});
			return count;
		}
	}
	
	/**
	* get the id list from the dest (right) combo box for transferring
	*/
	$.getComboBoxTransferIds = function(componentId){
		var destIds="";
		$("#"+componentId+"_right").children("option").each(function(x){
			destIds += "&" + componentId +"=" + $(this).attr("value");
		  });
		  
		return destIds;
		 
	}

	/**
	* Combines the parameters given into an e-mail address as a way to make it more
	* difficult for e-mail harvesters to find the address.
	*/
	$.obfuscateEmail = function(name, domain){
		location.href = "mailto:" + name + "@" + domain;
	}


	/**
	* This function loads the data at the configured
	* url when this input is changed.
	*
	* @param opt object containing the configuration properties (
	*	updateDivId name of the div to update (usually encloses the "form")
	*	loadingDivId name of the div surrounding the form on which the loading modal is placed
	*	message message to include in the loading modal
	*	success an optional function to be called when the function completes that takes the response text
	*  confirm optional string or function to set a confirm message before submitting
	* @author Tin Hoang
	*/
	$.fn.triggerAJAXOnChangeEvent = function(opt){
		// configure the function
		var config = {
			updateDivId: opt.updateDivId || null,   //divId we use to update with response from the server
			loadingDivId: opt.loadingDivId || null, //divId we use to block waiting
			defaultURL: opt.defaultURL || null,     // url to load if special condition is not satistifed
			specialConditions: opt.specialConditions || null, //json object used to store rpmzone/URL.
			selectConditions: opt.selectConditions || null,   //json object used to form the query string we send to the server
			message: opt.message || "loading...",
			confirm: opt.confirm || null
		};

		if(this == null || config.updateDivId == null || config.loadingDivId == null || config.message == null || config.specialConditions == null || config.selectConditions == null){
			alert("$().triggerAJAXOnChangeEvent missing required parameters");
			return false;
		} else {
			
			
			//add change event to input
			this.change(function(){
				
				if($.isFunction(config.confirm)){
					var confirmFunc = config.confirm;  //Config function must return a boolean...
					if(confirmFunc() == false){
						return false;
					}
				}else if(typeof confirm == "string"){
					if(confirm(config.confirm) == false){
						return false;
					}
				}

				var optionValue = $(this).val();

				//figure out url we will call based on selected option
				var myURL = config.defaultURL;
				for(var i = 0; i < config.specialConditions.length; i++){
					//if the special condition is met set the url to special url
					//and break out of loop
					if(config.specialConditions[i].optionValue == optionValue){
						myURL = config.specialConditions[i].url;
						break;
					}
				}

				//form query string
				var myData = "";
				for(var i = 0; i < config.selectConditions.length; i++){
					myData = myData + $("#"+config.selectConditions[i].divID+" "+config.selectConditions[i].selectCondition).fieldSerialize();	
					if(i != config.selectConditions.length - 1){
						myData = myData + "&";
					} 

				}
				
				//make AJAX call
				$.ajax({
					type: "GET",
					url: myURL,  
					data: myData,
					beforeSend: function (XMLHttpRequest ){
							// show the form submission loading window
							$('#'+config.loadingDivId).block({ message: config.message+' <img src="/t/resources/rpm3.0/images/activity/indicator_medium.gif" />' });
							
						},
					success: function(msg){
						$('#'+config.updateDivId).html(msg);
						// hide loading window
						setTimeout(function(){$('#'+config.loadingDivId).unblock();}, 1000);
					}, // end success
					error: function(data, error){
						alert("$().triggerAJAXOnChangeEvent Error: could not register: " + error);
						setTimeout(function(){$('#'+config.loadingDivId).unblock();}, 1000);
					}
				});
				return false;
			});
		}
	}

})(jQuery);


(function($) {

	// this variable is used for session management in RPM3. It is not used in ClickSold but need to be here to keep RPM3 working.
	var csSessionManagerState = null;
	
	/**
	 * The main utilities plugin. Functions are passed in via the "method" parameter
	 *
	 * @param method string of the function to run
	 * 
	 */
	$.fn.clickSoldUtils = $.clickSoldUtils = function(method) {
	
		// public methods
		var methods = {

			/**
			 * Prints a message to the console
			 *
			 * @param message to the printed to the console
			 * @param optional debug boolean variable
			 */
			csConsole : function(message, debug){
				if(debug == null)
					debug = true;
				if (console && console.log && debug){
					console.log( message );
				}
			},
			/**
			 * Fairly self-explanatory. Shows or hides optional fields in a form depending on the existing show/hide state
			 *
			 * @param before callback to be made before the toggle is done
			 * @param before callback to be made after the toggle is done
			 *
			 */
			csToggleOptionalFields : function(before, after){
				var self = this;
				return this.each(function(){
					// run 'before' callback
					if(before != null && typeof before == "function") before();


					if($(this).find('.cs-required-fields-only').html() == "Only show required fields"){
						// rename flag
						$(this).find('.cs-required-fields-only').html("Show optional fields");

						// hide the fields
						methods.csHideOptionalFields.call(self);

						// alter the hidden field indicating that we're suppressing optional fields
						$(this).find("input[name='cs-toggle-optional-fields']").attr("value", "hidden");

					}else{
						// rename flag
						$(this).find('.cs-required-fields-only').html("Only show required fields");

						// show the fields
						methods.csShowOptionalFields.call(self);
						
						// alter the hidden field indicating that we're suppressing optional fields
						$(this).find("input[name='cs-toggle-optional-fields']").attr("value", "shown");
					}

					// run 'after' callback
					if(after != null && typeof after == "function") after();

				});
			},
			/**
			 * Hides all of the non-required elements of a form. Will also hide any sections that have all of their inputs hidden
			 *
			 * @param callback to be made once fields are hidden
			 *
			 */
			csHideOptionalFields : function(callback){
				return this.each(function() {
					var self = this;
					// hide all fields and labels that aren't required
					$("label", this).filter(function(){
						return $(this).has(".cs-required-field").length == 0;
					}).each(function(){
						$("#"+$(this).attr("for"), self).closest(".cs-input-container, .cs-input-small").each(function(){
							// hide prefixes or suffixes
							$(this).siblings(".cs-input-suffix, cs-input-prefix").hide();
						}).hide();
						$(this).closest(".cs-label-container").hide();
					});
					
					// hide all cs-form-section that do not contain displayed elements
					$(".cs-form-section", this).filter(function(){
						return $(this).has("label:visible").length == 0;
					}).hide();
				});
			},
			
			/**
			 * Shows the optional elements of a form previously hidden by csHideOptionalFields.
			 *
			 * @param callback to be made once fields are shown
			 */
			csShowOptionalFields : function(callback){
				return this.each(function(){	
					var self = this;
					
					// hide all fields and labels that aren't required
					$("label", this).filter(function(){
						return $(this).has(".cs-required-field").length == 0;
					}).each(function(){
						$("#"+$(this).attr("for"), self).closest(".cs-input-container, .cs-input-small").each(function(){
							// hide prefixes or suffixes
							$(this).siblings(".cs-input-suffix, cs-input-prefix").show();
						}).show();
						$(this).closest(".cs-label-container").show();
					});
					
					// show all cs-form-section divs
					$(".cs-form-section", this).show();
				});
			},
			/**
			 * Bind to a form binds to a specified form.
			 *
			 * @param opt object containing the configuration properties (
			 *	updateDivId name of the div to update (usually encloses the "form")
			 *	loadingDivId name of the div surrounding the form on which the loading modal is placed
			 *	message message to include in the loading modal
			 *	onRPMSuccessCallback an optional function to be called if response from the submit is "success"
			 *	success an optional function to be called when the function completes that takes the response text
			 *	backOffice optional boolean flag indicating whether this is being called from the back office or not)
			 *	context the element that we are relative to... necessary if multiple of the same item are bound on in the same dom.
			 *  	plugin optional booelan flag indicating whether or not this is being called from a plugin - overrides backOffice param
			 *  	confirm optional string or function to set a confirm message before submitting
			 */
			csBindToForm : function(opt){
				//alert("CS Bind To Form updateDivId("+opt.updateDivId+")("+$('[id='+opt.updateDivId+']').size()+")");
				
				return this.each(function() {
					// configure the function
					var config = {
						beforeSubmit: opt.beforeSubmit || null,
						updateDivId: opt.updateDivId || null,
						loadingDivId: opt.loadingDivId || null,
						message: opt.message || "Submitting, please wait...",
						onRPMSuccessCallback: opt.onRPMSuccessCallback || function(responseText){return false;},
						success: opt.success || function(responseText){return false;},
						complete: opt.complete || function(XMLHttpRequest, textStatus){return false;},
						backOffice:  opt.backOffice || false,
						plugin: opt.plugin || false,
						confirm: opt.confirm || null,
						context: opt.context || null, // All our work here will be relative to this element.
						loadingIndicatorImg : opt.loadingIndicatorImg || '/t/resources/rpm3.0/images/activity/indicator_medium.gif'
					};
					
					// Massage the config a little bit.
					if(typeof opt.loadingIndicatorImg == "undefined" && config.plugin) { // If it's a plugin and no loading image was specified we clear it (aka forget the defualt as it (the default) is wrong for plugins).
						config.loadingIndicatorImg = '';
					}
					
					// store the name of this form
					var formId = $(this).attr("id");
					
					// do error checking to make sure all required options are specified
					if(this == null || config.updateDivId == null || config.loadingDivId == null || config.message == null){
						alert("clickSoldUitls.csBindToForm: missing required parameters");
						return false;
					}else{
						// create options for the form plugin
						var options = {
							//method: 'POST', //activate this when using firebug or error below will occur - will be fixed in Firefox 3.0.3
							/*
							Firebug needs to POST to the server to get this information for url:
							http://localhost:8080/listings?firstName=Anthony&lastName=Abenojar&email_addr=anthony%40realpagemaker.com&home_phone=780-428-3006&fax=&addr=&city=&prov=&coun=&postal_c=&subject=Test&message=TESTING+TESTING!&pathway=21&send=true
			
							This second POST can interfere with some sites. If you want to send the POST again, open a new tab in Firefox, use URL 'about:config', set boolean value 'extensions.firebug.allowDoublePost' to true
							This value is reset every time you restart Firefox This problem will disappear when https://bugzilla.mozilla.org/show_bug.cgi?id=430155 is shipped.
							*/
							target: $('#'+config.updateDivId, config.context), //target element(s) to be updated with server response
							beforeSubmit: function (formData, jqForm, options){
								if(config.beforeSubmit != null && typeof config.beforeSubmit == "function") config.beforeSubmit();
							
								// show the form submission loading window
								if(config.loadingIndicatorImg == '') { // Skip the spinny indicator image if it has not been specified (and the default has been cleared likely because it's a plugin).
									$('#'+config.loadingDivId, config.context).block({ message: config.message });
								} else {
									$('#'+config.loadingDivId, config.context).block({ message: config.message+' <img src="'+config.loadingIndicatorImg+'" />' });
								}
							},
							success: function(responseText, statusText){
								
								// Make sure that the target div given to ajaxSubmit is still the same one and that nobody has changed it on us
								// (happens when colorboxes are stacked to save the state when they are brought up again). If it has changed we 
								// set the response html here as likely the old target does not exist anymore. This is done like this because we
								// have to pass the original target as a jQuery object and not just a selector cause we need the context.
								if($('#'+config.updateDivId, config.context).is(options.target)) {
									// Nothing we'll let the default ajaxSubmit work it's magic.
								} else {
									$('#'+config.updateDivId, config.context).html(responseText);
								}
								
								// hide loading window
								setTimeout(function(){$('#'+config.loadingDivId, config.context).unblock();}, 1000);
								//If the RPM framework declares success (RPM_AGENT_AJAX_SUCCESS output) and the onRPMSuccessCallback function != null call it
								if(config.onRPMSuccessCallback != null && $.trim(responseText).toLowerCase() == "success") {
									config.onRPMSuccessCallback(responseText);
								} else {
									$('#'+formId, config.context).clickSoldUtils("csBindToForm", config);
								}
								
								// run success function
								config.success(responseText);
							},
							complete: function(XMLHttpRequest, textStatus){
								config.complete(XMLHttpRequest, textStatus);
							},
							error: function(data, error){
								alert("Error: could not register: " + error);
								setTimeout(function(){$('#'+config.loadingDivId, config.context).unblock();}, 1000);
							}
						};
				
						// run necessary backOffice tasks
						if(config.backOffice && !config.plugin){
							// run the session manager
							helpers.runRPMSessionManager();
						}
						
						// bind to the form's submit event 
						/*$('#'+formId).submitfunction() */
						$('#'+formId, config.context).unbind("submit." + formId).bind("submit." + formId, function(){
							if($.isFunction(config.confirm)){
								var confirmFunc = config.confirm;  //Config function must return a boolean...
								if(confirmFunc() == false){
									return false;
								}
							}else if(typeof confirm == "string"){
								if(confirm(config.confirm) == false){
									return false;
								}
							}
							$(this).ajaxSubmit(options);
							return false;
						});
						
						//DEBUG START
						/*
						var self = $(this);
						
						$('#'+formId + ' input, textarea, select', config.context).unbind("focus." + formId).bind("focus." + formId, function(){
							alert("form " + self.attr("id") + " focused!");
						});
						*/
						//DEBUG END
						
						return false;
					}
				});		
			},

			/** Initializes session manager
			  *
			  * options See defaults definition below.
			  *
			  * @return nothing
			  */
			csInitRPMSessionManager : function(options) {
				
				// initialize
				csSessionManagerState = jQuery.extend({
					inlineId: null,		// the id of the division that contains the message we want to display to the user in case the session is about to timeout. This division usally has a display: none; attribute.
					inlineButtonId: null,	// the id of the button that the user must click to keep the session alive
					keepAliveUrl: null,	// the url we must call to refresh the session.
					data: null,		// the data we pass to the url as to refresh the session
					autoLogoutUrl: null,	// the url we redirect to if the user has not clicked on the, this.config.inlineButtonId button to keep the session alive.
					timeout: null,		// time in MINUTES configured on the tomcat webapp as the session timeout.
					grace: null,		// time in MINUTES before the timeout to display the notification to the user.
					csAutoLogoutTimeout: null,	// Will hold the grace timeout object.
					csSessionManagerTimeout: null	// Will hold the overall timeout object.
				}, options || {});	
								
				$('#'+csSessionManagerState.inlineButtonId).click(function(){
					$.ajax({
						type: "GET",
						url: csSessionManagerState.keepAliveUrl,
						data: csSessionManagerState.data,
						dataType: "html",
						error: function(data, error){
							// do nothing
						},
						success: function(data){
							// do nothing
						},
						complete: function (XMLHttpRequest, textStatus) {
							// remove any active thickboxes
							tb_remove();
							
							// run the session manager
							helpers.runRPMSessionManager();
						}
					});
				});
				// run the session manager
				helpers.runRPMSessionManager();
			},

			/** This function integrates the session manager with all other AJAX calls in the RPM3 back office. This function
			  * extends the AJAX call in jQuery whilst adding a session management layer.
			  *
			  * Not used by clicksold products, but is integral with RPM3 and modules used by clicksold still reference it.
			  *
			  * @param options same options as for a normal jQuery AJAX call
			  * @return returns AJAX request
			  */
			csAdminAjax : function(options) {
				if(options.plugin == null || options.plugin == false){
					helpers.runRPMSessionManager();
				}
				
				// run the AJAX function
				return $.ajax(options);
			},

			/**
			 * Displays the privacy policy
			 *
			 * @return false;
			 */
			showPrivacyPolicy: function(ajaxTarget) {
				var pHref = "listings?pathway=302";
				if(typeof ajaxTarget !== "undefined" && ajaxTarget != "null") pHref = ajaxTarget + "?pathway=302";
			
				$.clickSoldUtils('infoBoxCreate', {
					href : pHref,
					cb_his_keep_prev_html : true,
					cb_his_control : "skip"
				});
				return false;
			},

			/**
			 * Instantiate a drop-down range field
			 *
			 * NOTE: setting the selected options were done via the loops and if/else instead of the one line clean jquery way because
			 * IE6 is crap.
			 * @param - modeletId DOM id of the containing modlet
			 * @param minVal - the minimum value of both drop down lists
			 * @param maxVal - the maximum value of both drop down lists
			 * @param leftStart - the value the left field starts on
			 * @param rightStart - the value the right field starts on
			 * @param increment - incremental value used to create list of values going from minVal to maxVal
			 * @param zeroStartOpt - flag to have first value at zero without minVal having to be set to zero (left slider only)
			 * @param overMaxOpt - flag to add extra value to represent option greater than the maximum value (right slider only)
			 * @param format - the type of formatting for the option labels
			 */
			createDDRange: function(modletId, minVal, maxVal, leftStart, rightStart, increment, zeroStartOpt, overMaxOpt, format){
				return this.each(function() {

					try{
						var x = this;
						var minV = parseInt(minVal, 10), maxV = parseInt(maxVal, 10), inc = parseInt(increment, 10), min;
						var parentModlet = $(this).closest("div[id^='modletWrapper_']");
						
						// Round up invalid start values
						if(minV == 0){
							min = inc;
						}else{
							min = minV;
						}
						
						if(leftStart.indexOf("+") == -1){
							if(leftStart % min != 0 && Math.floor(leftStart/min) != inc){
								leftStart = (Math.floor(leftStart / (minVal + increment)) + 1) * (minVal + increment);
							}
						}
						
						if(rightStart.indexOf("+") == -1){
							if(rightStart % min != 0 && Math.floor(rightStart/min) != inc){
								rightStart = (Math.floor(rightStart / (minVal + increment)) + 1) * (minVal + increment);
							}
						}
						
						//var options = "";
						var minOption = "", maxOption = "", maxVLabel = "";
						var optionsLeft = "", optionsRight = "";
						
						// Calculate and create option tags
						if(format === "priceHalfInc") { 
							var label = "";
							for(var i=minV;i<=maxV;i+=inc) {
								if(i < 1000000){
									label = (i/1000) + "k";
									if(i.toString() == leftStart){
										optionsLeft = optionsLeft + "<option value=\"" + i + "\" selected=\"selected\">" + label + "</option>";
									}else{
										optionsLeft = optionsLeft + "<option value=\"" + i + "\">" + label + "</option>";
									}
									
									if(i.toString() == rightStart){
										optionsRight = optionsRight + "<option value=\"" + i + "\" selected=\"selected\">" + label + "</option>";
									}else{
										optionsRight = optionsRight + "<option value=\"" + i + "\">" + label + "</option>";
									}
								}else{
									label = (i/1000000) + "mil";
									if(i.toString() == leftStart){
										optionsLeft = optionsLeft + "<option value=\"" + i + "\" selected=\"selected\">" + label + "</option>";
									}else{
										optionsLeft = optionsLeft + "<option value=\"" + i + "\">" + label + "</option>";
									}
									
									if(i.toString() == rightStart){
										optionsRight = optionsRight + "<option value=\"" + i + "\" selected=\"selected\">" + label + "</option>";
									}else{
										optionsRight = optionsRight + "<option value=\"" + i + "\">" + label + "</option>";
									}
								}
								if(i.toString() == maxV && overMaxOpt){
									maxVLabel = label;
								}
							}
						}else{
							for(var i=minV;i<=maxV;i+=inc){
								if(i.toString() == leftStart){
									optionsLeft = optionsLeft + "<option value=\"" + i + "\" selected=\"selected\">" + i + "</option>";
								}else{
									optionsLeft = optionsLeft + "<option value=\"" + i + "\">" + i + "</option>";
								}
								
								if(i.toString() == rightStart){
									optionsRight = optionsRight + "<option value=\"" + i + "\" selected=\"selected\">" + i + "</option>";
								}else{
									optionsRight = optionsRight + "<option value=\"" + i + "\">" + i + "</option>";
								}
							}
							maxVLabel = maxV;
						}
						
						// Add tags to drop down
						if(zeroStartOpt){
							if(leftStart == "0"){
								optionsLeft = "<option value=\"0\" selected=\"selected\">0</option>" + optionsLeft;
							}else{
								optionsLeft = "<option value=\"0\">0</option>" + optionsLeft;
							}
						}
						
						if(overMaxOpt){
							if(rightStart == maxV.toString() + "+"){
								optionsRight = optionsRight + "<option value=\"" + maxV + "+\" selected=\"selected\">" + maxVLabel + "+</option>";
							}else{
								optionsRight = optionsRight + "<option value=\"" + maxV + "+\">" + maxVLabel + "+</option>";
							}
						}
						
						
						
						$(".cs-dd-range-left", this).html(optionsLeft);
						$(".cs-dd-range-right", this).html(optionsRight);
									
						// Create events for drop down
						$(".cs-dd-range-left", this).unbind("change").bind("change", function(){
							//Compensate if overMaxOpt is true
							var curMax = $(".cs-dd-range-right", x).val();
							if(curMax.indexOf("+") != -1){
								curMax = parseInt(maxV, 10) + 1;
							}
							
							if(parseInt($(this).val(), 10) > parseInt(curMax, 10)){
								$(".cs-dd-range-right", x).val($(this).val());
							}
							$("#"+modletId).csIDXSearchEngine("updateResults", 0, true);
						});
						
						$(".cs-dd-range-right", this).unbind("change").bind("change", function(){
							//Compensate if overMaxOpt is true
							var curMax = $(this).val();
							var overMax = false;
							if(curMax.indexOf("+") != -1){
								curMax = (maxV + 1);
								overMax = true;
							}
							
							if(parseInt($(".cs-dd-range-left", x).val(), 10) > parseInt(curMax, 10)){
								if(overMax){
									$(".cs-dd-range-left", x).val(maxV);
								}else{
									$(".cs-dd-range-left", x).val($(this).val());
								}
							}
							$("#"+modletId).csIDXSearchEngine("updateResults", 0, true);
						});
					}catch(ex){
						alert(ex.name + ": \n" + ex.message); 
					}
				});
			},
			/**
			 * Instantiate a slider element
			 *
			 * @param - modeletId DOM id of the containing modlet
			 * @minVal - minimum value (leftmost value)
			 * @maxVal - maximum value (rightmost value)
			 * @increment - value slider will jump to on drag
			 * @leftStart - starting position of left slider (value)
			 * @rightStart - starting position of right slider (value)
			 * @format - format of value (used to check if "year")
			 */
			createSlider: function(modletId, minVal, maxVal, increment, leftStart, rightStart, format){
				
				return this.each(function() {
		
					//for format = year, need to calculate new division depending on current year
					var maxV = parseInt(maxVal);
					increment = parseInt(increment);
					if($.trim(format) == "year"){
						var date = new Date(); 
						maxV = parseInt(date.getFullYear());
					}
					
					// declare constants for quick reference
					var $track = $(".cs-slider-track", this);
					var $leftOutput = $(".cs-slider-inner-label-value-left", this);
					var $rightOutput = $(".cs-slider-inner-label-value-right", this);
					
					// create a function that shows the values					
					var showValues = function(ui) {
						var leftSliderValue, rightSliderValue;

						// we can only initialize without the UI element - if we try to
						// not use it we get wrong tracking.
						if( ui != null ){
							leftSliderValue = ui.values[0];
							rightSliderValue = ui.values[1];
						} else {
							leftSliderValue = $track.slider( "values", 0 );
							rightSliderValue = $track.slider( "values", 1 );
						}
			
						if(leftSliderValue == 0){
							$leftOutput.html("0");
						}else if(parseInt(leftSliderValue) == (maxV + increment)){
							$leftOutput.html((parseInt(leftSliderValue) - increment) + "+");
						}else{
							$leftOutput.html(leftSliderValue);
						}
					
						if(rightSliderValue == 0){
							$rightOutput.html("0");
						}else if(parseInt(rightSliderValue) == (maxV + increment)){
							$rightOutput.html((parseInt(rightSliderValue) - increment) + "+");
						}else{
							$rightOutput.html(rightSliderValue);
						}
					}
			
					// initiate the UI slider
					$track.slider({
						range: true,
						min: minVal,
						max: (maxV + increment),
						values: [leftStart, rightStart],
						step:increment,
						change : function(e, ui) {
							$("#"+modletId).csIDXSearchEngine("updateResults", 0, true);
						},
						slide: function( event, ui ) {
							showValues(ui);
						}
					});

					// show the initial values			
					showValues();
				});
			}, 
						
			/** This function displays the daysonmarket graph in the given div with the given data
			  *
			  * @param MLSStatsDOMs object containing all listing DOM data
			  * @param displayDiv jQuery object to display the graph in
			  */
			renderDOMGraph : function(MLSStatsDOMs){
				var x = this;
				return this.each(function() {
					$.plot($(x), MLSStatsDOMs, {
						series: {
							pie: {
								show: true, 
								radius: 9/12,
								stroke: {
									color: '#ccc',
									width: 0
								},
								label: {
									show: true,			//use ".pieLabel div" to format looks of labels
									radius: 9/12, 		// part of radius (default 5/6)
									formatter: function(label, series) {
										return Math.round(series.percent)+'%';
									},
									background: {
										opacity: 0.55
									}
								}
							}
						},
						legend: {
							show:true,
							backgroundOpacity:0.4,
							position: "ne",
							margin: 10,
							noColumns: 2,
							labelFormatter: function(label) {
								return '<span class="graphLegendLabel">' + label + '</span>';
							}
						}
					});
			
					return false;
				});
			},
			
			/** This function displays the price graph in the given div with the given data
			  *
			  * @param MLSStatsPrices object containing all listing pricing data
			  */
			renderPricesGraph : function(MLSStatsPrices){
				var x = this;
				return this.each(function() {

					var median = new Array();
					var average = new Array();
					var graphTicks = new Array();
			
					var j = 0;
					for(var i = 0; i < MLSStatsPrices.length; i++){
						median[i] = [j, parseFloat(MLSStatsPrices[i].medianPrice)];
						average[i] = [j, parseFloat(MLSStatsPrices[i].averagePrice)];
						graphTicks[i] = [j, '<span class="graphLegendLabel">'+MLSStatsPrices[i].dayRanges+'</span>'];
						j = j + 2;
					}
			
					$.plot($(x), [
						{ //active listing price
							data: median,
							label: "Median Price",
							lines: { show: true, fill: true }
						},
						{ //active listing price
							data: average,
							label: "Average Price",
							lines: { show: true, fill: true }
						}], 
						{
							xaxis: {
								ticks: graphTicks,
								tickFormatter: function(val, axis){
									return '<span class="graphLegendLabel">'+val+'</span>';
								}
								},
							yaxis: {
								tickFormatter: function(val, axis){
									return '<div style="width:50px"><span class="graphLegendLabel">'+$('body').clickSoldUtils("formatAsCurrency", val, false)+'</span></div>';
								}
							},
							legend: {
								backgroundOpacity:0.4,
								position: "ne",
								margin: 10,
								labelFormatter: function(label) {
									return '<span class="graphLegendLabel">' + label + '</span>';
								}
							}
						}
					);
					
					return false;
				});
			},
			
			/** This function displays the listedVersusSold graph in the given div with the given data
			  *
			  * @param MLSStatsListedVersusSold object containing all listed/sold data
			  */
			renderListedVersusSoldGraph : function(MLSStatsListedVersusSold){
				var x = this;
				return this.each(function() {
					var listed = new Array();
					var sold = new Array();
					var graphTicks = new Array();
					var j = 0;
					for(var i = 0; i < 10; i++){
						// initialize data values at 0
						listed[i] = [j, 0];
						sold[i] = [j+1, 0];
						if(i == 9)
							graphTicks[i] = [j+1, '<span class="graphLegendLabel">this wk</span>'];
						else
							graphTicks[i] = [j+1, '<span class="graphLegendLabel">'+(9-i)+' wk ago</span>'];
						j = j + 3;
					}
					
					for(var i = 0; i < MLSStatsListedVersusSold.length; i++){
						var index = MLSStatsListedVersusSold[i].dayRanges.substring(0,1);
						if(MLSStatsListedVersusSold[i].dayRanges.substring(1,2) == 'l'){
							listed[index] = [index*3, MLSStatsListedVersusSold[i].listingCount];
						}else{
							sold[index] = [index*3+1, MLSStatsListedVersusSold[i].listingCount];
						}
					}
			
					var graphData = [
						{ data: listed, label: "# Listed"},
						{ data: sold, label: "# Sold"}];
					
					$.plot($(x), graphData, 
						{
							bars: { show: true, fillOpacity: 0.8, barWidth:1},
							shadowSize: 2,
							xaxis: {
								ticks: graphTicks
								},
							yaxis: {
								min: 0,
								tickFormatter: function(val, axis){
									return '<div style="width:50px"><span class="graphLegendLabel">'+val+'</span></div>';
								}
							},
							legend: {
								backgroundOpacity:0.4,
								position: "ne",
								margin: 10,
								labelFormatter: function(label) {
									return '<span class="graphLegendLabel">' + label + '</span>';
								}
							}
						}
					);
			
					return false;
				});
			}
		};
		
		
		// public static methods - called without an element being specified
		var staticMethods = {

			/**
			 * Binds a beforeunload warning function to the page and any active infobox.
			 *
			 * @param object to be printed out
			 */
			bindBeforeUnloadWarning : function(){
				// Add 'beforeunload' functions to the window and the infobox. The infobox function will execute the
				// infobox closing function, and unbind the window 'beforeunload' function so that it doesn't continue
				// to prompt the user whenever he/she changes pages.
				$(window).bind('beforeunload', function(){ 
					return "If you leave this page without saving, all changes will be lost.";
				});
				$.clickSoldUtils('infoBoxBindBeforeUnload', function(closeFunction){
					if(confirm("If you close this window without saving, all changes will be lost. Continue?")){
						$.clickSoldUtils('unbindBeforeUnloadWarning');
						closeFunction();
					}
				});
			},
			/**
			 * Un-binds a beforeunload warning function to the page and any active infobox.
			 *
			 * @param object to be printed out
			 */
			unbindBeforeUnloadWarning : function(){
				$.clickSoldUtils('infoBoxUnbindBeforeUnload');
				$(window).unbind('beforeunload');
			},

			/**
			 * Utility function used to print out the source code of a javascript object. Used as a debugging tool.
			 *
			 * @param object to be printed out
			 */
			toSourceCode : function(Object){
				var theSourceCode = '{\n';
				for ( var thePropName in this ){
					if ( !this.hasOwnProperty || this.hasOwnProperty( thePropName ) ){
						theSourceCode += '  ' + thePropName + ' : ' + this[ thePropName ] + ',\n';
					}
				}
				return theSourceCode.replace( /,\n$/, '\n}' );
			},
			/**
			 * Gets the next available module wrapper id
			 *
			 */
			getNextAvailableModuleId: function(){
				var currentId = -1;
				$("div[id^='modletWrapper_']").each(function(){
					if(parseInt(this.id.substring(this.id.indexOf("_")+1)) > currentId)
						currentId = parseInt(this.id.substring(this.id.indexOf("_")+1));
				});
				return currentId + 1;
			},

			/**
			 * Creates a pop-up box. Currently using colorbox.
			 *
			 * @param options - array containing the parameters for the pop-up box
			 *   cs options are:
			 *     - cb_his_control: skip - don't log this info box in the history, it's as if it never existed.
			 *                       log (default) - log this info box.
			 *                       bust - bust the history, when closed it will ignore the history and close all info boxes.
			 *     - cb_his_keep_html: true/false(default) - This is the HTML of the CURRENTly being opened infoBox. (setting this to true will mean that this info box will NOT be reloaded when the next one is closed).
			 *                                               if true will save and reload the html of this info box if it comes back in the queue.
			 *                                               If false will use the same method as was originally used when creating this info box in the first place.
			 *     - cb_his_keep_prev_html: true/false(default) - This is the HTML of the PREVIOUSly opened infoBox (if any). (setting this to true will mean that when this info box is closed it's predecessor will NOT be reloaded).
			 *                                                    if true will save and reload the html of the previous info box when this one is closed.
			 *                                                    If false will use the same method as was originally used when creating the previous info box in the first place.
			 *
			 *  NOTE: the diff between cb_his_keep_html and cb_his_keep_prev_html is that:
			 *        - cb_his_keep_html - makes sure that the info box we're opening will NOT (never) be reloaded if another is opened (and then closed).
			 *        - cb_his_keep_prev_html - makes sure that the info box we're openening will not force the previous one to reload.
			 *  
			 *          Careful as cb_his_keep_prev_html trumps the value of cb_his_keep_html.
			 */ 
			infoBoxCreate: function(options){
				var self = this;
				var opts = options;
				var oldOnLoad = options.onLoad;
				var oldOnComplete = options.onComplete;

				//alert("cb_his_control("+options.cb_his_control+"), cb_his_keep_html("+options.cb_his_keep_html+"), cb_his_keep_prev_html("+options.cb_his_keep_prev_html+"), options("+options+")");
				
				/**
				 * Careful here, onLoad is called right before the content is loaded into the info box when the info box opens but it is also
				 * called when an infobox is closed and a previous one is opened.
				 */
				options.onLoad = function(){
					//alert("On Load");
					
					// Run any specified onOpen function first.
					if(oldOnLoad) oldOnLoad();

					// If we are not the first info box to be opened then save the state of the dom of the PREVIOUS info box, right before the new content is loaded.
					var cb_his = $("#cboxContent").data("cb_his");
					if(typeof cb_his != "undefined" && cb_his.length > 0 && opts != cb_his[cb_his.length - 1]) { // Last clause makes sure that this does not happen if we're re-opening a previous info box.
						// Here we save the state of the previous dom, when this infobox is replaced by another, if we get back to it the saved
						// dom will be switched in (therefore keeping state). However the info box will be reloaded first (from the server) as ie
						// does not place nicely if we just give the info box the old html.
						cb_his[cb_his.length - 1].cb_his_our_html_dom = $("#cboxLoadedContent").clone(true, true);
					} else if(typeof cb_his == "undefined") { // If we are the first one being opened we clear the old content.
						//alert("Clearing old content.");
						$("#cboxLoadedContent").html("");
					}
				};

				options.onComplete = function(){

					// Create the close button
					if(typeof $('#cboxCloseAdded', '#colorbox').get() == "undefined" || $('#cboxCloseAdded', '#colorbox').html() == null){
						$('#colorbox').prepend('<div id="cboxCloseAdded">close</div>');
						$('#cboxCloseAdded').click(function(){
							$.clickSoldUtils('infoBoxClose');
						});
					}

					//alert("Opened...cb_his_control = ("+opts.cb_his_control+") ("+opts.cb_his_keep_html+")("+opts.cb_his_force_keep_html+")");

					// Note: all color boxes are logged in the history, if they are set to skip then they will be skipped during the re-opening step.
					var cb_his = $("#cboxContent").data("cb_his");
					if(typeof cb_his == "undefined") { cb_his = new Array(); }
						
					// We're being re-opened (after having been replaced by one or more other infoboxes).
					if(opts == cb_his[cb_his.length - 1]) {
						
						// Bit of logic here.
						var restore_state = false;
						
						if(typeof opts.cb_his_force_keep_html != "undefined") { // The window that just closed has specifically told us to restore or not restore the state.
							restore_state = opts.cb_his_force_keep_html;
							opts.cb_his_force_keep_html = undefined;
						} else if(typeof opts.cb_his_keep_html != "undefined") { // Else if we have a preference (our own setting) we follow that.
							restore_state = opts.cb_his_keep_html;
						} // else - nothing just stick with the default.
						
						// Restore our saved state (if any).
						if(typeof opts.cb_his_our_html_dom != "undefined" && restore_state) {
							//alert("Replacing content.");
							
							// Clear the original content.
							$("#cboxLoadedContent").empty();

							// Replace the insides of the cboxLoadedContent element with the old sutff, NOTE: we have to not replace the cboxLoadedContent element itself as then cb get's confused.
							$("#cboxLoadedContent").append(opts.cb_his_our_html_dom.children());
							
						} else {
							// We only run the old on complete function if we've decided not to replace the dom.
							if(oldOnComplete) { oldOnComplete(); }
						}
						
						// Always clear the value as the onComplete has a habit of being called multiple times.
						opts.cb_his_our_html_dom = undefined;
					}

					if(cb_his.length == 0 || opts != cb_his[cb_his.length - 1]){ // Don't save to the log if we're the ones being opened (happens when we re open after having been replaced by another infobox)

						// When we're just being opened we need to run any old onComplete function.
						if(oldOnComplete) { oldOnComplete(); }

						//alert("Adding to queue");
						cb_his.push(opts);
						
						//alert("Size of Queue: " + cb_his.length);
						$("#cboxContent").data("cb_his", cb_his);
					}
				};
				
				// Override the close
				// First, check if this was already instantiated as we don't want to call this fucker multiple times
				var cb_default_close = $("#cboxContent").data("cb_default_close");
				if(typeof cb_default_close == "undefined"){
					var cb_default_close = $.colorbox.close;
					$.colorbox.close = function(){
						var cb_his = $("#cboxContent").data("cb_his");
						var runOnRPMClosed = !($("#cboxContent").data("cb_clear_rpm_closed")||false);
						$("#cboxContent").data("cb_clear_rpm_closed", false);
						
						if(typeof cb_his != "undefined"){
							
							// First off remove all history for any *consecutive* info boxes *BEFORE* us that are to be skipped.
							while(cb_his.length >= 2 && cb_his[cb_his.length - 2].cb_his_control == "skip") {
								//alert("skip history go!");
								var hisTop = cb_his.pop();
								var hisPop = cb_his.pop();
								//if(typeof hisPop != "undefined" && hisPop.onRPMClosed != null && typeof hisPop.onRPMClosed == "function") hisPop.onClosed();
 								if(runOnRPMClosed && typeof hisPop != "undefined" && hisPop.onRPMClosed != null && typeof hisPop.onRPMClosed == "function") hisPop.onRPMClosed();
 
								// Restore the top entry, that will close by itself shortly.
								cb_his.push(hisTop);
							}
							
							// Check the current colorbox settings.
							if(cb_his.length >= 1 && cb_his[cb_his.length - 1].cb_his_control == "bust") { // We've been told to bust the history.
								//alert("bust history go!");
								
								// Kill the entire history.
								while(cb_his.length > 0) { 
									var hisPop = cb_his.pop();
									//if(typeof hisPop != "undefined" && hisPop.onRPMClosed != null && typeof hisPop.onRPMClosed == "function") hisPop.onClosed();
									if(runOnRPMClosed && typeof hisPop != "undefined" && hisPop.onRPMClosed != null && typeof hisPop.onRPMClosed == "function") hisPop.onRPMClosed();
								}
							} else if(cb_his.length >= 2 && typeof cb_his[cb_his.length - 1].cb_his_keep_prev_html != "undefined") { // This color box is instructed to force the previous one to keep or discard it's stored html.
								//alert("cb_his_keep_prev_html (true) -- restoring previous window's html.");
								
								//NOTE: in IE replacing the html does not work correctly (the styles get messed) so we reload the html from the server but will replace the actual dom anyways.
								// This html loading step is needed to get the colorbox to open with the correct size.
								
								// let's make sure that the infobox before us in the queue does not reload.
								//cb_his[cb_his.length - 2].html = cb_his[cb_his.length - 2].cb_his_our_html_dom.html();
								//cb_his[cb_his.length - 2].data = false;
								//cb_his[cb_his.length - 2].href = false;
								
								// Specifically tell the previous infobox to either keep or discard it's html.
								cb_his[cb_his.length - 2].cb_his_force_keep_html = cb_his[cb_his.length - 1].cb_his_keep_prev_html;
								
								var hisPop = cb_his.pop();
								//if(typeof hisPop != "undefined" && hisPop.onRPMClosed != null && typeof hisPop.onRPMClosed == "function") hisPop.onClosed();
								if(runOnRPMClosed && typeof hisPop != "undefined" && hisPop.onRPMClosed != null && typeof hisPop.onRPMClosed == "function") hisPop.onRPMClosed();
							} else { // Just go back one level in the history.
								var hisPop = cb_his.pop();
								//if(typeof hisPop != "undefined" && hisPop.onRPMClosed != null && typeof hisPop.onRPMClosed == "function") hisPop.onClosed();
								if(runOnRPMClosed && typeof hisPop != "undefined" && hisPop.onRPMClosed != null && typeof hisPop.onRPMClosed == "function") hisPop.onRPMClosed();
							}
							
							//alert("New size of queue: " + cb_his.length);
							if(cb_his.length >= 1){ // We have a queue, must open up an old one.
								$("#cboxContent").data("cb_his", cb_his);
								//$.colorbox(cb_his[cb_his.length - 1]);
								staticMethods["infoBoxCreate"].call(self, cb_his[cb_his.length - 1]);
								return;
							} else {
								// Set the close function back to what it was before or it'll save past iterations of this function being instantiated
								$.colorbox.close = $("#cboxContent").data("cb_default_close");
								$("#cboxContent").removeData("cb_his");
								$("#cboxContent").removeData("cb_default_close");
								cb_default_close();
							}
							
						} else { // This still happens if a cb is closed before it gets a chance to fully open.
							if(runOnRPMClosed && opts.onRPMClosed != null && typeof opts.onRPMClosed == "function"){opts.onRPMClosed();};
							cb_default_close();
						}
					};
					
					//Save the original to reset the close function when it truly closes
					$("#cboxContent").data("cb_default_close", cb_default_close);
				}
								
				// create the preset opacity
				if(options.opacity == null)
					options.opacity = 0.5;
				$.colorbox(options);
				return $.colorbox.element();
			},
			/**
			 * Adds a "beforeUnload" function that returns a boolean, true or false, with a warning message for the user
			 * attempting to close the infobox
			 *
			 * @param func function that is run prior to the infobox closing. func is passed the 'closeFunction' which it must run to complete the close.
			 */
			infoBoxBindBeforeUnload: function(func){
				$('#cboxWrapper').data('beforeunload', func);
			},
			/**
			 * Deletes any "beforeUnload" function 
			 *
			 */
			infoBoxUnbindBeforeUnload: function(func){
				$('#cboxWrapper').data('beforeunload', "");
			},
			/**
			 * Closes an active pop-up box. Currently using colorbox. Runs the optional "onClosed" function once the box is completely closed.
			 *
			 * @param onClosed function that is run immediately after the infobox closes.
			 */
			infoBoxClose: function(onClosed){
				// create the 'close' function
				var closeFunction = function(){
					$(document).bind('cbox_closed', function() {
						if(onClosed != null && typeof(onClosed) == "function")
							onClosed();
						$(document).unbind('cbox_closed');
					});
					$.colorbox.close();
				};

				// first we check if there is a 'beforeunload' function that needs to be executed prior to closing.
				var beforeUnload = $('#cboxWrapper').data('beforeunload');
				if(beforeUnload != null && typeof(beforeUnload) == "function"){
					beforeUnload(closeFunction);
				}else{
					closeFunction();
				}
			},
			infoBoxClearHistory : function(){
				$("#cboxContent").removeData("cb_his");
			},
			
			infoBoxClearOnRPMClosed: function(){
				$("#cboxContent").data("cb_clear_rpm_closed", true);
			},
			infoBoxSuccessfulClose: function(){
				$.clickSoldUtils('infoBoxClearHistory'); 
				$.clickSoldUtils('infoBoxClearOnRPMClosed');
				$.clickSoldUtils('infoBoxClose');
			},
			/**
			 * Runs the specified function AFTER the infobox has loaded. Needed because some infobox
			 * implementations don't load the DOM immediately.
			 *
			 * @param onComplete function to be run when the infobox has completed loading
			 */
			infoBoxRunAfterCompleted: function(onComplete){
				$(document).bind('cbox_complete', function(){
					if(onComplete != null && typeof(onComplete) == "function")
						onComplete();
					$(document).unbind('cbox_complete');
				});
			},

			/**
			 * Resizes an infobox after load, but before display. Currently using colorbox.
			 *
			 * @param optional configuration object with the following options:
			 *		width, innerWidth, height, innerHeight
			 *
			 */
			infoBoxResize: function(options){
				$.colorbox.resize(options);
			},
			/**
			 * Force-hides the loading icon to show on an infobox
			 *
			 */
			infoBoxShowLoading: function(){
				$.clickSoldUtils('$infoBox', '#cboxLoadingGraphic').show();
			},
			/**
			 * Force-shows the loading icon to show on an infobox
			 *
			 */
			infoBoxHideLoading: function(){
				$.clickSoldUtils('$infoBox', '#cboxLoadingGraphic').hide();
			},
			/**
			 * Sometimes, for whatever reason, there is a need to hide scrollbars (overflow). This function does that.
			 * Should be used sparingly. Currently used in some places because colorbox doesn't seem to be able to properly
			 * resize elements using "inline-block".
			 *
			 */
			infoBoxForceHideOverflow: function(){
				$('#cboxLoadedContent').css("overflow", "hidden");
			},
			/**
			 * Searches ONLY IN THE INFOBOX for the specified DOM element. Returns the jQuery object for function chaining.
			 *
			 * @return jQuery object of the requested element
			 */
			 $infoBox: function(jQuerySelector){
			 	return $(jQuerySelector, '#cboxWrapper');
			 },
			
			/**
			 * formats a number as a dollar currency. does not put the '$' before the final value
			 *
			 * @param num number to be formatted
			 * @param showCents boolean indicating whether to show the cents or not
			 * return formatted number in currency format
			 */
			formatAsCurrency : function(num, showCents){
				num = num.toString().replace(/\$|\,/g,'');
				if(isNaN(num))
					num = "0";
				sign = (num == (num = Math.abs(num)));
				num = Math.floor(num*100+0.50000000001);
				cents = num%100;
				num = Math.floor(num/100).toString();
				if(cents<10)
					cents = "0" + cents;
				for (var i = 0; i < Math.floor((num.length-(1+i))/3); i++)
					num = num.substring(0,num.length-(4*i+3))+','+num.substring(num.length-(4*i+3));
				if(showCents)
					return (((sign)?'':'-') +  num + '.' + cents);
				else
					return (((sign)?'':'-') +  num);
			},
			
		   	/**
			* This function displays a map for a specific listing.
			*
			* @param options {} of ...
			* - listingCounter - the number of the listing (0-n) in this modlet's list of listing summaries.
			* - latt - lattitude for the map
			* - longt - longitude for the map
			*/
			displayListingMap : function( options ) {
				var myOptions = {'latt' : '', 'longt' : '', 'plugin' : 'false'}; // Define some defaults.
				
				if ( options ) {
					$.extend( myOptions, options );
					
					var map = null;
					var point = null;
					var mapLoaded = false;
					
					var urlOptions = {};
					urlOptions.href = myOptions.ajaxTarget;
					urlOptions.data = "pathway=1&map=true";
					
					var infoBoxCreateOptions = {
						opacity : 0.25,
						onComplete : function(){
							$(document).ready(function(){
								var map = new google.maps.Map(document.getElementById('cs-map-canvas'), {
									center : new google.maps.LatLng(myOptions.latt, myOptions.longt),
									mapTypeId : google.maps.MapTypeId.ROADMAP,
									zoom : 15,
									mapTypeControl : true,
									zoomControl : true
								});
								
								var point = new google.maps.Marker({
									clickable : false,
									map : map,
									position : new google.maps.LatLng(myOptions.latt, myOptions.longt)
								});
							});
						}
					};
					
					$.extend(infoBoxCreateOptions, urlOptions);
					
					staticMethods['infoBoxCreate'].call(this, infoBoxCreateOptions);
				}
			},
			
			/**
			 * This function displays the virtual tours for a specified listing
			 *
			 * @param options {} of ...
			 * - listingCounter - the number of the listing (0-n) in this modlet's list of listing summaries.
			 * - listNum - listing to grab vr tours for
			 */
			displayListingTours : function( options ){
				var tourIds = new Array();
				var x = this;
				// Handle any options.
				var myOptions = {'listNum' : '', 'ajaxTarget' : ''}; // Define some defaults.
				if ( options ) $.extend( myOptions, options );
				
				var infoBoxCreateOptions = {
					href : myOptions.ajaxTarget,
					opacity : 0.25,
					data: "pathway=6&type=results&listingNumber=" + myOptions.listNum + "&vrTours=true",
					scrolling : false,
					maxWidth : '100%',
					maxHeight : '100%',
					innerWidth : '646px',
					innerHeight : '531px',
					overlayClose : false,
					returnFocus : false,
					onComplete : function(){
						for(var i=0;i<new Number($("#tour_count").attr("value"));i++) tourIds[i] = $("#tour_" + i).attr("value");
						$("#tourPagination").pagination(new Number($("#tour_count").attr("value")), {
							items_per_page : 1,
							callback : function(index, container){
								helpers['displayListingTour'].call(x, myOptions.ajaxTarget, tourIds[index]);
								return false;
							}
						});
						$.colorbox.resize();
					}
				};
				staticMethods['infoBoxCreate'].call(this, infoBoxCreateOptions);
			},
			/**
			 * This function displays the media for a specified listing
			 *
			 * @param options {}: (listNum, ajaxTarget)
			 */
			displayListingMedia : function( options ){
				var tourIds = new Array();
				var x = this;
				// Handle any options.
				var myOptions = {'listNum' : '', 'ajaxTarget' : ''}; // Define some defaults.
				if ( options ) $.extend( myOptions, options );
				
				var infoBoxCreateOptions = {
					href : myOptions.ajaxTarget,
					opacity : 0.25,
					data: "pathway=6&summary=true&listingNumber=" + myOptions.listNum + "&media=true",
					overlayClose : false,
					onComplete : function(){
						$.clickSoldUtils("infoBoxResize");
					}
				};
				staticMethods['infoBoxCreate'].call(this, infoBoxCreateOptions);
			},
			/**
			 * This function displays contact form for a given listing
			 *
			 * @param options {} of ...
			 * - listingCounter - the number of the listing (0-n) in this modlet's list of listing summaries.
			 * - listNum - listing to send the contact form about
			 */
			displayListingEmailForm : function( options ){		
				// Handle any options.
				var myOptions = {'listingCounter' : '', 'listNum' : '', 'plugin' : false}; // Define some defaults.
				if ( options ) $.extend( myOptions, options );
				
				var infoBoxCreateOptions = {
					href : options.ajaxTarget,
					data : "pathway=6&listingNumber=" + myOptions.listNum + "&loadListingEmail=true",
					opacity : 0.25,
					overlayClose : false,
					onComplete : function(){
						$.clickSoldUtils("infoBoxResize");
						$('#listingEmailForm').clickSoldUtils("csBindToForm", {
							updateDivId: "listingEmailModule",
							loadingDivId: "listingEmailLoadingOverlay",
							message: "Please wait...",
							plugin: options.plugin
						});
					}
				};

				staticMethods['infoBoxCreate'].call(this, infoBoxCreateOptions);
			}
		};

		// quickly check there we don't have duplicate names for functions between 'methods' and 'staticMethods'.
		// this only for debugging, but shouldn't be removed.
		for (var methodName in methods) {
			for (var staticMethodName in staticMethods)
				if (methodName == staticMethodName)
					alert("duplicate method names for: methods."+staticMethodName+" and staticMethods."+staticMethodName);
		}

		// private methods
		// these methods can be called only from within the plugin
		//
		// private methods can be called as
		// helpers.methodName(arg1, arg2, ... argn)
		// where "methodName" is the name of a function available in the "helpers" object below; arg1 ... argn are
		// arguments to be passed to the method
		var helpers = {
			displayListingTour : function(ajaxTarget, tourNum){
				$('.tourFrame').attr('src', ajaxTarget + '?pathway=6&type=results&tourNum=' + tourNum + '&vrTour=true');
			},
			
			 /** 
			  * options: See definition of csSessionManagerState in csInitRPMSessionManager
			  * 
			  * NOTE: This assumes a java servlet session timeout is equal to the configured timeout.
			  *
			  */
			runRPMSessionManager : function() {
				
				// clear any timeouts that may exist
				if(csSessionManagerState.csAutoLogoutTimeout != null){
					clearTimeout(csSessionManagerState.csAutoLogoutTimeout);
				}
				if(csSessionManagerState.csSessionManagerTimeout != null) {
					clearTimeout(csSessionManagerState.csSessionManagerTimeout);
				}

				csSessionManagerState.csSessionManagerTimeout = setTimeout(function(){
					
					// remove any existing thickboxes
					tb_remove();
		
					// show window to client
					tb_show(null, "#TB_inline?inlineId="+csSessionManagerState.inlineId+"&modal=true&height=71&width=320", false);
		
					// set autologout timer
					csSessionManagerState.autoLogoutTimeout = setTimeout(function(){
						window.location.href = csSessionManagerState.autoLogoutUrl;
					},(csSessionManagerState.grace * 60 * 1000)); // .grace minutes.
				}, (csSessionManagerState.timeout * 60 * 1000 - csSessionManagerState.grace * 60 * 1000)); // (.timeout - .grace) minutes.
			}
		}
		
		// if a method as the given argument exists
		if (methods[method]) {
		
			// call the respective method
			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));

		}else if(staticMethods[method]){
			return staticMethods[method].apply(this, Array.prototype.slice.call(arguments, 1));
		
		// if an object is given as method OR nothing is given as argument
		} else if (typeof method === 'object' || !method) {
		
			// call the initialization method
			return methods.init.apply(this, arguments);
		
		// otherwise
		} else {
		
			// trigger an error
			$.error( 'Method "' +  method + '" does not exist in clickSoldUtils plugin!');
		
		}
	}
})(jQuery);
