	
	



/*
Copyright (c) 2005 JSON.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*
    The global object JSON contains two methods.

    JSON.stringify(value) takes a JavaScript value and produces a JSON text.
    The value must not be cyclical.

    JSON.parse(text) takes a JSON text and produces a JavaScript value. It will
    return false if there is an error.
*/
var JSON = function () {
    var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        s = {
            'boolean': function (x) {
                return String(x);
            },
            number: function (x) {
                return isFinite(x) ? String(x) : 'null';
            },
            string: function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = b.charCodeAt();
                        return '\\u00' +
                            Math.floor(c / 16).toString(16) +
                            (c % 16).toString(16);
                    });
                }
                return '"' + x + '"';
            },
            object: function (x) {
                if (x) {
                    var a = [], b, f, i, l, v;
                    if (x instanceof Array) {
                        a[0] = '[';
                        l = x.length;
                        for (i = 0; i < l; i += 1) {
                            v = x[i];
                            f = s[typeof v];
                            if (f) {
                                v = f(v);
                                if (typeof v == 'string') {
                                    if (b) {
                                        a[a.length] = ',';
                                    }
                                    a[a.length] = v;
                                    b = true;
                                }
                            }
                        }
                        a[a.length] = ']';
                    } else if (x instanceof Object) {
                        a[0] = '{';
                        for (i in x) {
                            v = x[i];
                            f = s[typeof v];
                            if (f) {
                                v = f(v);
                                if (typeof v == 'string') {
                                    if (b) {
                                        a[a.length] = ',';
                                    }
                                    a.push(s.string(i), ':', v);
                                    b = true;
                                }
                            }
                        }
                        a[a.length] = '}';
                    } else {
                        return;
                    }
                    return a.join('');
                }
                return 'null';
            }
        };
    return {
        copyright: '(c)2005 JSON.org',
        license: 'http://www.crockford.com/JSON/license.html',
/*
    Stringify a JavaScript value, producing a JSON text.
*/
        stringify: function (v) {
            var f = s[typeof v];
            if (f) {
                v = f(v);
                if (typeof v == 'string') {
                    return v;
                }
            }
            return null;
        },
/*
    Parse a JSON text, producing a JavaScript value.
    It returns false if there is a syntax error.
*/
        parse: function (text) {
            try {
                return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
                        text.replace(/"(\\.|[^"\\])*"/g, ''))) &&
                    eval('(' + text + ')');
            } catch (e) {
                return false;
            }
        }
    };
}();

	
	


/*-----------------------------------------------------------------------------
  BookingBuddy
  The root BookingBuddy lib module. Contains other sub-modules and utility
  methods.
  See the Wiki for instructions by searching "JavaScript Compression"
  ---------------------------------------------------------------------------*/
var BookingBuddy =  {
	
	/**
	 *
	 */
	abTests: {},

	/**
	 * Domain using this the bookingbuddy lib. E.g., "foo.com"
	 * @type String
	 */
	domain: null,

	/**
	 * Name of the current BookingBuddy affiliate. E.g. "bookingbuddy_co_uk"
	 * @type String
	 */
	affiliateName: null,

	/**
	 * Hash of server side query string parameters
	 * @type Object
	 */
	queryString: {},

	/**
	 * Name of the bb_search_mode currently in use.
	 * @type String
	 */
	searchMode: null,
	
	/**
	 * The default sliding factor.
	 * @type int
	 */
	slidingFactor: 7,
	
	/**
	 * The unique id generator counter.
	 * @type int
	 */
	uniqueIDCounter: 1,

	/**
	 * Initializes the bookingbuddy lib.
	 * @param string mode
	 * @param string domain
	 * @param string affiliateName
	 * @param allow_framed boolean If true the site can be displayed in a frameset
	 */
	// init: function(mode, domain, affiliateName, allow_framed, redirectUrl) {
	init: function(settings) {
		for (var option in settings) {
			BookingBuddy[option] = settings[option];	
		}

		// Frame-busting can be overridden via the querystring
		if (!BookingBuddy.queryString['allow_framed']) {
			this.bustFrames();
		}
	},

	/**
	 * Prevents the site from being framed unless there is an override set
	 * on the page.
	 * @return boolean
	 */
	bustFrames: function() {
		if (!BookingBuddy.allowFramed && (top != self)) {
			top.location.replace(self.location.href);
		}

		return true;
	},

	/**
	 * Returns the content of the cookie named by @name
	 * @param string name
	 * @return null|string
	 */
	getCookie: function(name) {
		var dc = document.cookie;
		var prefix = name + '=';
		var begin = dc.indexOf('; ' + prefix);
		if (begin == -1) {
			begin = dc.indexOf(prefix);
			if (begin !== 0) return null;
		} else {
			begin += 2;
		}
		var end = document.cookie.indexOf(';', begin);
		if (end == -1) {
			end = dc.length;
		}
		try {
			return decodeURIComponent(dc.substring(begin + prefix.length, end));
		}
		catch(e) {
			return null;
		}
	},

	createCookie: function(name,value,minutes) {

		var expires;
		if (minutes) {
			// NOTE: Cookies are client side so dont pass server time here
			var date = new Date();
			date.setTime(date.getTime()+(minutes*60*1000));
			expires = "; expires="+date.toGMTString();
		} else {
			expires = "";
		}
	
		var domain = '';
		if (!BookingBuddy.domain) {
			var match = document.domain.match(/.+(bookingbuddy.+|smartertravel.+)/); 
			if (match !== null) {
				domain = match[1];
			}
			
		} else {
			domain = BookingBuddy.domain;
		}			

		document.cookie = name+"="+value+expires+"; path=/; domain=" + domain;
		return true;
	},

	/**
	 * Returns the current EST hour in 24 hour format
	 */
	getESTHour: function() {
		var now = new Date(BookingBuddy.Strings.ServerTime);
		var localTime = now.getTime();
		var offset = -now.getTimezoneOffset()/60;
		var ESTTime = new Date(localTime - offset);

		var hours = ESTTime.getHours();
		if (hours === 0) hours = 24;

		return hours;
	},

	/**
	 * Disables the enter key to prevent form submission.
	 * @param object e  Event object
	 * @return bool
	 */
	handleEnterKey: function(e) {
		// IE doesn't pass event as arg so use the global value
		e = (!e) ? window.event : e;

		// IE uses different event property to store which key was pressed
		var code = (!e.keyCode) ? e.which : e.keyCode;

		// return false unless key != 13 (enter key)
		if (code == 13) {
			Event.stop(e);
			return false;
		} else {
			return true;
		}
	},

	/**
	 * Handles displaying and hiding elements that are underneath a DHTML overlay.
	 *
	 * IE6 renders applets, iframes, and selects on top of everything else regardless of z-index.
	 * This hides any of those elements that would cover the visible element el.
	 *
	 * @param object el  Element that needs selects underneath it hidden/shown
	 * @param string visib_state  New visibility state for elements underneath el
	 */
	toggleCovered: function(el, visib_state) {

		// Only do this for IE6
		if (window.XMLHttpRequest) { return; }

		function getVisib(obj){
			var value = obj.style.visibility;
			if (!value) {
				// Gecko, W3C
				if (document.defaultView && typeof (document.defaultView.getComputedStyle) == 'function') {
					value = document.defaultView.getComputedStyle(obj, '').getPropertyValue('visibility');
				// IE
				} else if (obj.currentStyle) {
					value = obj.currentStyle.visibility;
				} else {
					value = '';
				}
			}
			return value;
		}

		function getElementCoords(el) {
			var p = BookingBuddy.getAbsolutePos(el);
			return [p.x, el.offsetWidth + p.x, p.y, el.offsetHeight + p.y];
		}

		var tags = ['applet', 'iframe', 'select'];
		var coords = getElementCoords(el);
		var EX1 = coords[0];
		var EX2 = coords[1];
		var EY1 = coords[2];
		var EY2 = coords[3];

		for (var k = tags.length; k > 0;) {
			--k; // FYI -  Putting this inside the for loop breaks IE6
			var ar = document.getElementsByTagName(tags[k]);
			var cc = null;

			for (var i = ar.length; i > 0;) {
				--i; // FYI -  Putting this inside the for loop breaks IE6
				cc = ar[i];

				coords = getElementCoords(cc);
				var CX1 = coords[0];
				var CX2 = coords[1];
				var CY1 = coords[2];
				var CY2 = coords[3];

				if ((CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
					if (!cc.__sl_save_visibility) {
						cc.__sl_save_visibility = getVisib(cc);
					}
					cc.style.visibility = cc.__sl_save_visibility;
				} else {
					if (!cc.__sl_save_visibility) {
						cc.__sl_save_visibility = getVisib(cc);
					}
					cc.style.visibility = visib_state;
				}
			}
		}
	},

	/**
	 * Returns the absolute x and y position of element el.
	 * @param object el  Any element object that can be positioned on the screen
	 * @return object
	 */
	getAbsolutePos: function(el) {
		var SL = 0, ST = 0;
		var is_div = /^div$/i.test(el.tagName);
		if (is_div && el.scrollLeft)
			SL = el.scrollLeft;
		if (is_div && el.scrollTop)
			ST = el.scrollTop;
		var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
		if (el.offsetParent) {
			var tmp = BookingBuddy.getAbsolutePos(el.offsetParent);
			r.x += tmp.x;
			r.y += tmp.y;
		}
		return r;
	},

	/**
	 * Returns the value of the query string parameter named by @name.
	 * @param string name
	 * @return null|string
	 */
	getQSParam: function(name) {
		return $j.isUndefined(BookingBuddy.queryString[name]) ? null : BookingBuddy.queryString[name];
	},

	/**
	 * Determine whether or not the given string is a numerical number.
	 *
	 * @param string	data	The data to test.
	 * @return boolean
	 */
	isNumeric: function(data) { return (parseFloat(data) == data); },

	/**
	 * Generate a unique ID for the given DOM element if it doesn't already
	 * have an ID. This will not only update that element's ID to the given
	 * ID but it will return the ID of that element at the end. If the element
	 * does not exist, then an empty string is returned.
	 *
	 * @param string|object	 element	 The element to have its ID  generated.
	 * @return string
	 */
	generateUniqueID: function(element) {
		element = $j('#' + element);

		if (element[0]) {
			// If it already have and ID, just return it
			if (element.attr('id') !== '') { return element.attr('id'); }

			// Generate a unqiue ID
			BookingBuddy.uniqueIDCounter++;
			var id = 'generatedID_'+ BookingBuddy.uniqueIDCounter;
			while ($j('#' + id)[0]) {
				BookingBuddy.uniqueIDCounter++;
				id = 'generatedID_'+BookingBuddy.uniqueIDCounter;
			}

			// Set the ID and return it
			element.attr('id', id);
			return id;
		} else { 
			return ''; 
		}
	},

	/**
	 * Simple wrapper around window.open to allow popup windows
	 * to be centered on the screen
	 *
	 * @param string url
	 * @param int height
	 * @param int width
	 * @param bool center
	 * @return mixed reference to the window on success, false on failure
	 */
	popWindow: function(url, height, width, center) {
		var window_options = 'height=' + height + ',width=' + width + ',scrollbars=yes';
		var window_name = 'BBSurvey' + Math.floor(Math.random() * 100);
		var the_popup = window.open(url, window_name, window_options);
		
		if (the_popup) {
			if (center) {
				try {
					var popup_top = (screen.height  - height) / 2;
					var popup_left = (screen.width - width) / 2;
					the_popup.moveTo(popup_left, popup_top);
				} catch (e) {
					// not allowed, nothing to do here
				}
			}
			return the_popup;
		}

		return false;
	}
};

/*-----------------------------------------------------------------------------
  BookingBuddy.LE
  This module handles location validation error services.
  ---------------------------------------------------------------------------*/
BookingBuddy.LE = {
	/**
	  * List of default locations for display in the point_1 select.
	  * @var array
	  */
	defaultLocations: [],

	/**
	 * ID for the search adapter to validate against.
	 * @var int
	 */
	searchAdapterID: null,

	/**
	 * Timestamp for the trip's start.
	 * @var int
	 */
	startTimestamp: null,

	/**
	  * Timestamp for the trip's end.
	  * @var int
	  */
	endTimestamp: null,

	/**
	  * ID of the input corresponding to the first point. E.g., 'departure_city'.
	  * @var string
	  */
	point1InputID: null,

	/**
	  * ID of the input corresponding to the second point. E.g., 'arrival_city'.
	  * @var string
	  */
	point2InputID: null,

	/**
	 * Flag indicating if this module has been initialized.
	 * @var bool
	 */
	inited: false,

	/**
	  * Initializes this module.
	  */
	init: function() {
		$j(document).ready(function() {
			BookingBuddy.LE.updateLocations(1);
			BookingBuddy.LE.updateLocations(2);
		});

		BookingBuddy.LE.inited = true;
	},

	/**
	 * Populates the selects with the default locations.
	 * @param string target  ID of the select to update
	 */
	loadDefaults: function(target) {
		if (!BookingBuddy.LE.inited) {
			return;
		}

		BookingBuddy.LE.updateSelect(target, BookingBuddy.LE.defaultLocations);
	},

	/**
	 * Updates the target select based on the selected item in the source select.
	 * @param int completions_for  Number of the route point to update. Either '1' or '2'.
	 */
	updateLocations: function(completions_for) {
		if (!BookingBuddy.LE.inited) { 
			return;
		}
		var sa_id = BookingBuddy.LE.searchAdapterID;
		var start_timestamp = BookingBuddy.LE.startTimestamp;

		var source = 'point_' + ((completions_for % 2) + 1);
		var target = 'point_' + completions_for;

		if (!$j('#' + source).val()) {
			return;
		}

		var source_value = $j('#' + source).val();
		var selection_data = source_value.split('|');
		var selection_id = selection_data[0];

		var params = {
			'selection_id': selection_id,
			'id': sa_id,
			'cf': completions_for,
			'start_date': start_timestamp
		};

		$j.bbget('LocationError.locations', params, function(data) {
			BookingBuddy.LE.__callback(data, selection_id, source, target);
		});
	},

	/**
	 * Callback for handling responses from the LE ajax service.
	 * @param int selection  content_id of the selected option
	 * @param string source  ID of the source element (the element containing the selected option)
	 * @param string target  ID of the target element (the element that needs to be updated)
	 */
	__callback: function(locs, selection, source, target) {
		if (!locs) {
			return;
		}

		BookingBuddy.LE.updateSelect(target, locs);
		BookingBuddy.LE.setPointInput(BookingBuddy.LE.point1InputID, 1);
		BookingBuddy.LE.setPointInput(BookingBuddy.LE.point2InputID, 2);
	},

	/**
	 * Updates the target select with the locations in locs.
	 * @param string target  ID of the element to update
	 * @param array locs  Array of locations (as returned by the LE ajax service)
	 */
	updateSelect: function(target, locs) {
		if (!BookingBuddy.LE.inited) {
			return;
		}

		var targetMenu = $j('#' +target);
		if (!targetMenu[0]) {
			return;
		}

		var prevTargetSelection = targetMenu.val() ? targetMenu.val() : '';
		targetMenu[0].options.length = 0;

		var selected_index = 0;
		var newOptionValue, newOptionText, isSelected, newOption;
		for (var key in locs) {
			newOptionValue = key;			
			newOptionText = locs[key];
			isSelected = (newOptionValue == prevTargetSelection);
			newOption = new Option(newOptionText, newOptionValue, isSelected);
			targetMenu[0].options[targetMenu[0].options.length] = newOption;

			/* This is required for IE */
			if (isSelected) {
				targetMenu[0].selectedIndex = selected_index;
			}
			++selected_index;
		}
	},

	/**
	 * Validates location error correction and prepares the form for submission.
	 * @return bool
	 */
	checkSubmitRoute: function() {
		var point_1_input_name = BookingBuddy.LE.point1InputID;
		var point_2_input_name = BookingBuddy.LE.point2InputID;
		var point_1_select = $j('#point_1');
		var point_2_select = $j('#point_2');
		var p_1_si = point_1_select.selectedIndex;
		var p_2_si = point_2_select.selectedIndex;
		if (p_1_si < 0 || p_2_si < 0) {
			alert(BookingBuddy.Strings.LE.ChooseArrivalAndDeparture);
			return false;
		}
		var ret1 = BookingBuddy.LE.setPointInput(point_1_input_name, 1);
		var ret2 = BookingBuddy.LE.setPointInput(point_2_input_name, 2);
		if (!ret1 || !ret2) {
			alert(BookingBuddy.Strings.LE.GenericError);
			return false;
		}
		return true;
	},

	/**
	 * Validates location error correction and prepares the form for submission.
	 * @return bool
	 */
	checkSubmitSingle: function() {
		var point_1_input_name = BookingBuddy.LE.point1InputID;
		var point_1_select = $j('#point_1');
		var p_1_si = point_1_select.selectedIndex;
		if (p_1_si < 0) {
			alert(BookingBuddy.Strings.LE.ChooseLocation);
			return false;
		}
		var ret = BookingBuddy.LE.setPointInput(point_1_input_name, 1);
		if (!ret) {
			alert(BookingBuddy.Strings.LE.GenericError);
		}
		return ret;
	},

	/**
	 * Uses the surrogate "point_X" input to set the value of the real input.
	 * @param string input_id  ID of the real input
	 * @param int input_num  Number of the surrogate "point_X" input to use
	 * @return bool
	 */
	setPointInput: function(input_id, input_num) {
		var select = $j('#point_' + input_num);
		var element = $j('#' + input_id);
		if (!select[0] || !element[0] || !select.val()) {
			return false;
		}

		var data = select.val().split('|');
		if (data[1]) {
			element.val(data[1]);
		}

		return true;
	}
};

BookingBuddy.User = {

	/**
	 * Name of the cookie used to store source information
	 */
	source_cookie: 'referrer',

	/**
	 * Set up any user specific information (source, tracking_code, etc)
	 *
	 * @return void
	 */
	init: function() {},

	/**
	 * Sanitize source values and return them as a hash
	 *
	 * @param string source
	 * @param string value
	 * @param string value2
	 * @param int timestamp
	 * @return Hash
	 */
	_sanitizeSource: function(source, value, value2, timestamp) {
		return {
			source: source.replace(/[^\w\-\.]/, ''),
			value: value.replace(/[^\w\-\.]/, ''),
			value2: value2.replace(/[^\w\-\.]/, ''),
			timestamp: parseInt(timestamp,10)
		};
	},

	/**
	 * Return the value corresponding to the specified key of a
	 * user's source information or a hash if no key is specified
	 *
	 * Return null when an invalid key is specified.
	 *
	 * @param string key
	 * @return mixed string|Hash
	 */
	getSource: function(key) {
		var raw = decodeURIComponent( BookingBuddy.getCookie(BookingBuddy.User.source_cookie));

		if (!raw) {
			return null;
		}

		var parts = raw.split(':::');
		if (4 > parts.length) {
			return null;
		}

		var data = BookingBuddy.User._sanitizeSource(parts[0], parts[1], parts[3], parts[2]);
	  	if(parts.length == 5) {
		  data['sunique'] = parts[4].replace(/[^\w\-\.]/, '');
		}
		if ('undefined' === typeof key) {
			return data;
		}

		return data[key];
	},
	/**
	 * Name of source session cookie that stores source metadata
	 */
	session_cookie: 'ssource',

	/**
	 * Store parsed session cookie
	 */
	json_source: null,

	getSourceMeta: function(key) {
		if(key == undefined) {
			return null;
		}
		if(BookingBuddy.User.json_source === null) {
			BookingBuddy.User.json_source = decodeURIComponent(
					BookingBuddy.getCookie(BookingBuddy.User.session_cookie)
			);
		}
		if (!BookingBuddy.User.json_source) {
			return null;
		}
		var json_data = JSON.parse(unescape(BookingBuddy.User.json_source));
		if(json_data != null && json_data[key]) {
			return json_data[key];
		}
		return null;
	}

};

/*-----------------------------------------------------------------------------
  BookingBuddy.PopUnder
  Module to handle popunder functionality
  
 ---------------------------------------------------------------------------*/
BookingBuddy.PopUnder = {
	/**
	 *  How many searches the user has performed
	 */
	searchesPerformed: 0,
	
	/**
	 *  Array of popUnder objects
	 */
	popUnderArray: [],

	/**
	 * Determine if the popup should be centered or not
	 */
	center: null,
	/**
	 * Initializes this module
	 */
	init: function (formID) {
		BookingBuddy.PopUnder.searchesPerformed = parseInt(BookingBuddy.getCookie('BBPopUnderNumOfSearches'), 10);

		if (isNaN(BookingBuddy.PopUnder.searchesPerformed)) {
			BookingBuddy.PopUnder.searchesPerformed = 0; 
		}	

		var bbform = $j('#' + formID);
		if (bbform) {
			bbform.bind('submit', function(event) {
				BookingBuddy.PopUnder.searchesPerformed++;
				BookingBuddy.createCookie('BBPopUnderNumOfSearches', BookingBuddy.PopUnder.searchesPerformed, 30);				
				setTimeout(BookingBuddy.PopUnder.call, 500);
			});
		}
	},
	
	/**
	 * Creates popUnder to be called later
	 * @param url - URL to open 
	 * @param params - window.open parameters
	 * @param numOfSearches - number of searches the user performs before the popunder appears
	 */
	register: function (url, numOfSearches, center, params, height, width) {
		BookingBuddy.PopUnder.popUnderArray.push({ 
			url: url,
			params: params,
			numOfSearches: numOfSearches,
			center: center,
			height: height,
			width: width
		});
	},
	
	/**
	 * Called when a search is performed on BB or directly
	 * 
	 * @param _popUnder -(optional) Contains options for a specific popunder doesn't get triggered on submit
	 * @return mixed reference to opened window when called with a specific config, true otherwise
	 */
	call: function(_popUnder) {
		if (typeof _popUnder == 'object' && _popUnder) {
			return BookingBuddy.PopUnder.pop(_popUnder);	
		}

		for (var i = 0;i < BookingBuddy.PopUnder.popUnderArray.length; i++) {
			var popUnder = BookingBuddy.PopUnder.popUnderArray[i];
			BookingBuddy.PopUnder.pop(popUnder);
		}

		return true;
	},

	/**
	 * Displays pop under
	 * Will display a popup based on num of searches if needed
	 * @param popUnder - Popunder options
	 * @return mixed reference to the window opened, false on failure
	 */
	pop: function(popUnder) {
		if (typeof popUnder != 'object' && !popUnder) { return false; }

		if (popUnder.numOfSearches) {
			if (BookingBuddy.PopUnder.searchesPerformed != popUnder.numOfSearches) {
				return false;
			}
		}
			
		popUnder.params += ',height=' + popUnder.height + ',width=' + popUnder.width;
		var windowName = popUnder.name ? popUnder.name : "BBPopUnder" + Math.floor(Math.random() * 100);
		
		// Open a blank window & blur and position it first, then set the href to avoid any
		// "Access denied" errors
		var popUnderWindow = window.open('about:blank', windowName, popUnder.params);
		
		if (!popUnderWindow) {
			return false;
		} else if (popUnder.name) {
			$j(document).trigger("BookingBuddy:" + popUnder.name + "Opened");
		}
		
		popUnderWindow.blur();
		if (!popUnder.center) {
			if (popUnder.xpos && popUnder.ypos) {
				popUnderWindow.moveTo(popUnder.xpos, popUnder.ypos);
			} else {
				popUnderWindow.moveTo(0,0);
			}
		} else {
			var left = (screen.width - (popUnder.width + 32)) /2;					
			var top = (screen.height - (popUnder.height + 96)) /2;
			popUnderWindow.moveTo(left, top);
		}
		
		popUnderWindow.location.href = popUnder.url;

		return popUnderWindow;
	},


	/**
	 * Registers the BookingBuddy popunder to occur on the first click
	 * Should be called on/after page load
	 *
	 */
	firstClick: function(args) {
		// check for required arguments and return if they don't exist
		if (!args.popunderName || !args.getPopunderURL || !args.width || !args.height) {
			return;
		}

		// if there is no search mode, just stop
		if ($j('#searchModeName').length < 1) {
			return;
		}

		// get search mode and source
		var search_mode = $j('#searchModeName').text() || 'air';

		// if source is a major search engine, just stop
	  	var no_popunder_mpartners = new Array('google', 'msn', 'yahoo');

		var marketing_partner = BookingBuddy.User.getSourceMeta('marketing_partner');
		if (marketing_partner) {
		  no_popunder_mpartners.each( function(item) {
			if (marketing_partner.match(item)) {
			  return;
			}
							  });
		}

		// determine the input ID that should refresh the popunder on blur
		var inputID = null;
		if ('car' == search_mode) {
			inputID = 'pickup_city';
		} else if ('cruise' != search_mode && 'vacation_rental' != search_mode) {
			inputID = 'arrival_city';
		} else {
			return;
		}

		// check that the given input truly *exists*
		if ($j('#'+inputID).length < 1) {
			inputID = search_mode + '_' + inputID;
			if ($j('#'+inputID).length < 1) {
				return;
			}
		}
		
		// popunderName arg is used for the event that gets triggered when the
		// popunder opens successfully, and for the cookie that gets created
		var popunderName = cookie_name = args.popunderName;
		var pop_window = null;

		// on the input's blur, update the popup window (if it exists)
		$j('#'+inputID).blur(function() {
			if (pop_window && !pop_window.closed) {
				pop_window.location.href = args.getPopunderURL(inputID);
			}
		});
		
		//This is why we cant have nice things
		var isaol = navigator.userAgent.match("AOL");

		// set up the 'on exit' popup, except if this source is exempt from them
		var sunique = BookingBuddy.User.getSourceMeta('unique_name');
		if (!isaol && args.popOnExit && source && sunique.search(/^(BBS_TripMama_.*|bbs_tv_.*)/i) < 0) {
			var base = args.getPopunderURL(inputID);
			ExitPopup.init({
				url: base + (-1 == base.search(/\?/) ? '?' : '&') + 'exit=1',
				num_searches: 0,
				windowOptions: 'toolbar=1,location=1,directories=0,status=1,menubar=1,scrollbars=1,resizable=1',
				resizetoW: args.width,
				resizetoH: args.height,
				popCallback: function() { return !BookingBuddy.getCookie(cookie_name); },
				mode: 'full'
			});
		}

		var isPopped = false;

		// set up the 'on first click' popup
		$j(document).bind('BookingBuddy:FirstClick', function() {
			if (!isPopped && !BookingBuddy.getCookie(cookie_name)) {
				isPopped = true;
				setTimeout(function(){
					pop_window = BookingBuddy.PopUnder.call({
						name: popunderName,
						url: args.getPopunderURL(inputID),
						numOfSearches: null,
						center: 'yes',
						params: 'toolbar=1,location=1,directories=0,status=1,menubar=1,scrollbars=1,resizable=1',
						height: args.height,
						width: args.width
					}); }, 1000);
			}
		});

		// FirstClick occurs the first time the user clicks any element without the
		// 'noFirstClick' class. If the 'waitForLocation' arg is set to true, the
		// popunder will be triggered on the first click that occurs only after a
		// location has been entered
		$j(document).bind('click.first', function(e) {
			var target = $j(e.target);
			if ($j(e.target).hasClass('noFirstClick')) {
				return;
			}
			if (args.waitForLocation && $j('#'+inputID).val() === '') {
				return;
			}
			target.trigger('BookingBuddy:FirstClick');
			$j(this).unbind('click.first');
		});
	}
};


/*!
 * jQuery JavaScript Library v1.4.2
 * http://jquery.com/
 *
 * Copyright 2010, John Resig
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * http://jquery.org/license
 *
 * Includes Sizzle.js
 * http://sizzlejs.com/
 * Copyright 2010, The Dojo Foundation
 * Released under the MIT, BSD, and GPL Licenses.
 *
 * Date: Sat Feb 13 22:33:48 2010 -0500
 */
(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML="   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);


//Make jquery play nicely with prototype
var $j = jQuery.noConflict();

// Base ui for all sites
// NOTE: slider is dependant on core, widget & mouse
/*!
 * jQuery UI 1.8.2
 *
 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI
 */
(function(c){c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.2",plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&&b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==
"hidden")return false;b=b&&b=="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,f,g){return c.ui.isOverAxis(a,d,f)&&c.ui.isOverAxis(b,e,g)},keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,
NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},enableSelection:function(){return this.attr("unselectable","off").css("MozUserSelect","")},disableSelection:function(){return this.attr("unselectable","on").css("MozUserSelect",
"none")},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this,"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",
1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==undefined)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position");if(b=="absolute"||b=="relative"||b=="fixed"){b=parseInt(a.css("zIndex"));if(!isNaN(b)&&b!=0)return b}a=a.parent()}}return 0}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");return(/input|select|textarea|button|object/.test(b)?
!a.disabled:"a"==b||"area"==b?a.href||!isNaN(d):!isNaN(d))&&!c(a)["area"==b?"parents":"closest"](":hidden").length},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}})}})(jQuery);

(function($){$.extend($.ui,{datepicker:{version:"1.8.2"}});var PROP_NAME="datepicker";var dpuuid=new Date().getTime();function Datepicker(){this.debug=false;this._curInst=null;this._keyEvent=false;this._disabledInputs=[];this._datepickerShowing=false;this._inDialog=false;this._mainDivId="ui-datepicker-div";this._inlineClass="ui-datepicker-inline";this._appendClass="ui-datepicker-append";this._triggerClass="ui-datepicker-trigger";this._dialogClass="ui-datepicker-dialog";this._disableClass="ui-datepicker-disabled";this._unselectableClass="ui-datepicker-unselectable";this._currentClass="ui-datepicker-current-day";this._dayOverClass="ui-datepicker-days-cell-over";this.regional=[];this.regional[""]={closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"mm/dd/yy",firstDay:0,isRTL:false,showMonthAfterYear:false,yearSuffix:""};this._defaults={showOn:"focus",showAnim:"fadeIn",showOptions:{},defaultDate:null,appendText:"",buttonText:"...",buttonImage:"",buttonImageOnly:false,hideIfNoPrevNext:false,navigationAsDateFormat:false,gotoCurrent:false,changeMonth:false,changeYear:false,yearRange:"c-10:c+10",showOtherMonths:false,selectOtherMonths:false,showWeek:false,calculateWeek:this.iso8601Week,shortYearCutoff:"+10",minDate:null,maxDate:null,duration:"fast",beforeShowDay:null,beforeShow:null,onSelect:null,onChangeMonthYear:null,onClose:null,numberOfMonths:1,showCurrentAtPos:0,stepMonths:1,stepBigMonths:12,altField:"",altFormat:"",constrainInput:true,showButtonPanel:false,autoSize:false};$.extend(this._defaults,this.regional[""]);this.dpDiv=$('<div id="'+this._mainDivId+'" class="ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all ui-helper-hidden-accessible"></div>')}$.extend(Datepicker.prototype,{markerClassName:"hasDatepicker",log:function(){if(this.debug){console.log.apply("",arguments)}},_widgetDatepicker:function(){return this.dpDiv},setDefaults:function(settings){extendRemove(this._defaults,settings||{});return this},_attachDatepicker:function(target,settings){var inlineSettings=null;for(var attrName in this._defaults){var attrValue=target.getAttribute("date:"+attrName);if(attrValue){inlineSettings=inlineSettings||{};try{inlineSettings[attrName]=eval(attrValue)}catch(err){inlineSettings[attrName]=attrValue}}}var nodeName=target.nodeName.toLowerCase();var inline=(nodeName=="div"||nodeName=="span");if(!target.id){this.uuid+=1;target.id="dp"+this.uuid}var inst=this._newInst($(target),inline);inst.settings=$.extend({},settings||{},inlineSettings||{});if(nodeName=="input"){this._connectDatepicker(target,inst)}else{if(inline){this._inlineDatepicker(target,inst)}}},_newInst:function(target,inline){var id=target[0].id.replace(/([^A-Za-z0-9_])/g,"\\\\$1");return{id:id,input:target,selectedDay:0,selectedMonth:0,selectedYear:0,drawMonth:0,drawYear:0,inline:inline,dpDiv:(!inline?this.dpDiv:$('<div class="'+this._inlineClass+' ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all"></div>'))}},_connectDatepicker:function(target,inst){var input=$(target);inst.append=$([]);inst.trigger=$([]);if(input.hasClass(this.markerClassName)){return}this._attachments(input,inst);input.addClass(this.markerClassName).keydown(this._doKeyDown).keypress(this._doKeyPress).keyup(this._doKeyUp).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});this._autoSize(inst);$.data(target,PROP_NAME,inst)},_attachments:function(input,inst){var appendText=this._get(inst,"appendText");var isRTL=this._get(inst,"isRTL");if(inst.append){inst.append.remove()}if(appendText){inst.append=$('<span class="'+this._appendClass+'">'+appendText+"</span>");input[isRTL?"before":"after"](inst.append)}input.unbind("focus",this._showDatepicker);if(inst.trigger){inst.trigger.remove()}var showOn=this._get(inst,"showOn");if(showOn=="focus"||showOn=="both"){input.focus(this._showDatepicker)}if(showOn=="button"||showOn=="both"){var buttonText=this._get(inst,"buttonText");var buttonImage=this._get(inst,"buttonImage");inst.trigger=$(this._get(inst,"buttonImageOnly")?$("<img/>").addClass(this._triggerClass).attr({src:buttonImage,alt:buttonText,title:buttonText}):$('<button type="button"></button>').addClass(this._triggerClass).html(buttonImage==""?buttonText:$("<img/>").attr({src:buttonImage,alt:buttonText,title:buttonText})));input[isRTL?"before":"after"](inst.trigger);inst.trigger.click(function(){if($.datepicker._datepickerShowing&&$.datepicker._lastInput==input[0]){$.datepicker._hideDatepicker()}else{$.datepicker._showDatepicker(input[0])}return false})}},_autoSize:function(inst){if(this._get(inst,"autoSize")&&!inst.inline){var date=new Date(2009,12-1,20);var dateFormat=this._get(inst,"dateFormat");if(dateFormat.match(/[DM]/)){var findMax=function(names){var max=0;var maxI=0;for(var i=0;i<names.length;i++){if(names[i].length>max){max=names[i].length;maxI=i}}return maxI};date.setMonth(findMax(this._get(inst,(dateFormat.match(/MM/)?"monthNames":"monthNamesShort"))));date.setDate(findMax(this._get(inst,(dateFormat.match(/DD/)?"dayNames":"dayNamesShort")))+20-date.getDay())}inst.input.attr("size",this._formatDate(inst,date).length)}},_inlineDatepicker:function(target,inst){var divSpan=$(target);if(divSpan.hasClass(this.markerClassName)){return}divSpan.addClass(this.markerClassName).append(inst.dpDiv).bind("setData.datepicker",function(event,key,value){inst.settings[key]=value}).bind("getData.datepicker",function(event,key){return this._get(inst,key)});$.data(target,PROP_NAME,inst);this._setDate(inst,this._getDefaultDate(inst),true);this._updateDatepicker(inst);this._updateAlternate(inst)},_dialogDatepicker:function(input,date,onSelect,settings,pos){var inst=this._dialogInst;if(!inst){this.uuid+=1;var id="dp"+this.uuid;this._dialogInput=$('<input type="text" id="'+id+'" style="position: absolute; top: -100px; width: 0px; z-index: -10;"/>');this._dialogInput.keydown(this._doKeyDown);$("body").append(this._dialogInput);inst=this._dialogInst=this._newInst(this._dialogInput,false);inst.settings={};$.data(this._dialogInput[0],PROP_NAME,inst)}extendRemove(inst.settings,settings||{});date=(date&&date.constructor==Date?this._formatDate(inst,date):date);this._dialogInput.val(date);this._pos=(pos?(pos.length?pos:[pos.pageX,pos.pageY]):null);if(!this._pos){var browserWidth=document.documentElement.clientWidth;var browserHeight=document.documentElement.clientHeight;var scrollX=document.documentElement.scrollLeft||document.body.scrollLeft;var scrollY=document.documentElement.scrollTop||document.body.scrollTop;this._pos=[(browserWidth/2)-100+scrollX,(browserHeight/2)-150+scrollY]}this._dialogInput.css("left",(this._pos[0]+20)+"px").css("top",this._pos[1]+"px");inst.settings.onSelect=onSelect;this._inDialog=true;this.dpDiv.addClass(this._dialogClass);this._showDatepicker(this._dialogInput[0]);if($.blockUI){$.blockUI(this.dpDiv)}$.data(this._dialogInput[0],PROP_NAME,inst);return this},_destroyDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();$.removeData(target,PROP_NAME);if(nodeName=="input"){inst.append.remove();inst.trigger.remove();$target.removeClass(this.markerClassName).unbind("focus",this._showDatepicker).unbind("keydown",this._doKeyDown).unbind("keypress",this._doKeyPress).unbind("keyup",this._doKeyUp)}else{if(nodeName=="div"||nodeName=="span"){$target.removeClass(this.markerClassName).empty()}}},_enableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=false;inst.trigger.filter("button").each(function(){this.disabled=false}).end().filter("img").css({opacity:"1.0",cursor:""})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().removeClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)})},_disableDatepicker:function(target){var $target=$(target);var inst=$.data(target,PROP_NAME);if(!$target.hasClass(this.markerClassName)){return}var nodeName=target.nodeName.toLowerCase();if(nodeName=="input"){target.disabled=true;inst.trigger.filter("button").each(function(){this.disabled=true}).end().filter("img").css({opacity:"0.5",cursor:"default"})}else{if(nodeName=="div"||nodeName=="span"){var inline=$target.children("."+this._inlineClass);inline.children().addClass("ui-state-disabled")}}this._disabledInputs=$.map(this._disabledInputs,function(value){return(value==target?null:value)});this._disabledInputs[this._disabledInputs.length]=target},_isDisabledDatepicker:function(target){if(!target){return false}for(var i=0;i<this._disabledInputs.length;i++){if(this._disabledInputs[i]==target){return true}}return false},_getInst:function(target){try{return $.data(target,PROP_NAME)}catch(err){throw"Missing instance data for this datepicker"}},_optionDatepicker:function(target,name,value){var inst=this._getInst(target);if(arguments.length==2&&typeof name=="string"){return(name=="defaults"?$.extend({},$.datepicker._defaults):(inst?(name=="all"?$.extend({},inst.settings):this._get(inst,name)):null))}var settings=name||{};if(typeof name=="string"){settings={};settings[name]=value}if(inst){if(this._curInst==inst){this._hideDatepicker()}var date=this._getDateDatepicker(target,true);extendRemove(inst.settings,settings);this._attachments($(target),inst);this._autoSize(inst);this._setDateDatepicker(target,date);this._updateDatepicker(inst)}},_changeDatepicker:function(target,name,value){this._optionDatepicker(target,name,value)},_refreshDatepicker:function(target){var inst=this._getInst(target);if(inst){this._updateDatepicker(inst)}},_setDateDatepicker:function(target,date){var inst=this._getInst(target);if(inst){this._setDate(inst,date);this._updateDatepicker(inst);this._updateAlternate(inst)}},_getDateDatepicker:function(target,noDefault){var inst=this._getInst(target);if(inst&&!inst.inline){this._setDateFromField(inst,noDefault)}return(inst?this._getDate(inst):null)},_doKeyDown:function(event){var inst=$.datepicker._getInst(event.target);var handled=true;var isRTL=inst.dpDiv.is(".ui-datepicker-rtl");inst._keyEvent=true;if($.datepicker._datepickerShowing){switch(event.keyCode){case 9:$.datepicker._hideDatepicker();handled=false;break;case 13:var sel=$("td."+$.datepicker._dayOverClass,inst.dpDiv).add($("td."+$.datepicker._currentClass,inst.dpDiv));if(sel[0]){$.datepicker._selectDay(event.target,inst.selectedMonth,inst.selectedYear,sel[0])}else{$.datepicker._hideDatepicker()}return false;break;case 27:$.datepicker._hideDatepicker();break;case 33:$.datepicker._adjustDate(event.target,(event.ctrlKey?-$.datepicker._get(inst,"stepBigMonths"):-$.datepicker._get(inst,"stepMonths")),"M");break;case 34:$.datepicker._adjustDate(event.target,(event.ctrlKey?+$.datepicker._get(inst,"stepBigMonths"):+$.datepicker._get(inst,"stepMonths")),"M");break;case 35:if(event.ctrlKey||event.metaKey){$.datepicker._clearDate(event.target)}handled=event.ctrlKey||event.metaKey;break;case 36:if(event.ctrlKey||event.metaKey){$.datepicker._gotoToday(event.target)}handled=event.ctrlKey||event.metaKey;break;case 37:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,(isRTL?+1:-1),"D")}handled=event.ctrlKey||event.metaKey;if(event.originalEvent.altKey){$.datepicker._adjustDate(event.target,(event.ctrlKey?-$.datepicker._get(inst,"stepBigMonths"):-$.datepicker._get(inst,"stepMonths")),"M")}break;case 38:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,-7,"D")}handled=event.ctrlKey||event.metaKey;break;case 39:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,(isRTL?-1:+1),"D")}handled=event.ctrlKey||event.metaKey;if(event.originalEvent.altKey){$.datepicker._adjustDate(event.target,(event.ctrlKey?+$.datepicker._get(inst,"stepBigMonths"):+$.datepicker._get(inst,"stepMonths")),"M")}break;case 40:if(event.ctrlKey||event.metaKey){$.datepicker._adjustDate(event.target,+7,"D")}handled=event.ctrlKey||event.metaKey;break;default:handled=false}}else{if(event.keyCode==36&&event.ctrlKey){$.datepicker._showDatepicker(this)}else{handled=false}}if(handled){event.preventDefault();event.stopPropagation()}},_doKeyPress:function(event){var inst=$.datepicker._getInst(event.target);if($.datepicker._get(inst,"constrainInput")){var chars=$.datepicker._possibleChars($.datepicker._get(inst,"dateFormat"));var chr=String.fromCharCode(event.charCode==undefined?event.keyCode:event.charCode);return event.ctrlKey||(chr<" "||!chars||chars.indexOf(chr)>-1)}},_doKeyUp:function(event){var inst=$.datepicker._getInst(event.target);if(inst.input.val()!=inst.lastVal){try{var date=$.datepicker.parseDate($.datepicker._get(inst,"dateFormat"),(inst.input?inst.input.val():null),$.datepicker._getFormatConfig(inst));if(date){$.datepicker._setDateFromField(inst);$.datepicker._updateAlternate(inst);$.datepicker._updateDatepicker(inst)}}catch(event){$.datepicker.log(event)}}return true},_showDatepicker:function(input){input=input.target||input;if(input.nodeName.toLowerCase()!="input"){input=$("input",input.parentNode)[0]}if($.datepicker._isDisabledDatepicker(input)||$.datepicker._lastInput==input){return}var inst=$.datepicker._getInst(input);if($.datepicker._curInst&&$.datepicker._curInst!=inst){$.datepicker._curInst.dpDiv.stop(true,true)}var beforeShow=$.datepicker._get(inst,"beforeShow");extendRemove(inst.settings,(beforeShow?beforeShow.apply(input,[input,inst]):{}));inst.lastVal=null;$.datepicker._lastInput=input;$.datepicker._setDateFromField(inst);if($.datepicker._inDialog){input.value=""}if(!$.datepicker._pos){$.datepicker._pos=$.datepicker._findPos(input);$.datepicker._pos[1]+=input.offsetHeight}var isFixed=false;$(input).parents().each(function(){isFixed|=$(this).css("position")=="fixed";return !isFixed});if(isFixed&&$.browser.opera){$.datepicker._pos[0]-=document.documentElement.scrollLeft;$.datepicker._pos[1]-=document.documentElement.scrollTop}var offset={left:$.datepicker._pos[0],top:$.datepicker._pos[1]};$.datepicker._pos=null;inst.dpDiv.css({position:"absolute",display:"block",top:"-1000px"});$.datepicker._updateDatepicker(inst);offset=$.datepicker._checkOffset(inst,offset,isFixed);inst.dpDiv.css({position:($.datepicker._inDialog&&$.blockUI?"static":(isFixed?"fixed":"absolute")),display:"none",left:offset.left+"px",top:offset.top+"px"});if(!inst.inline){var showAnim=$.datepicker._get(inst,"showAnim");var duration=$.datepicker._get(inst,"duration");var postProcess=function(){$.datepicker._datepickerShowing=true;var borders=$.datepicker._getBorders(inst.dpDiv);inst.dpDiv.find("iframe.ui-datepicker-cover").css({left:-borders[0],top:-borders[1],width:inst.dpDiv.outerWidth(),height:inst.dpDiv.outerHeight()})};inst.dpDiv.zIndex($(input).zIndex()+1);if($.effects&&$.effects[showAnim]){inst.dpDiv.show(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[showAnim||"show"]((showAnim?duration:null),postProcess)}if(!showAnim||!duration){postProcess()}if(inst.input.is(":visible")&&!inst.input.is(":disabled")){inst.input.focus()}$.datepicker._curInst=inst}},_updateDatepicker:function(inst){var self=this;var borders=$.datepicker._getBorders(inst.dpDiv);inst.dpDiv.empty().append(this._generateHTML(inst)).find("iframe.ui-datepicker-cover").css({left:-borders[0],top:-borders[1],width:inst.dpDiv.outerWidth(),height:inst.dpDiv.outerHeight()}).end().find("button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a").bind("mouseout",function(){$(this).removeClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).removeClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).removeClass("ui-datepicker-next-hover")}}).bind("mouseover",function(){if(!self._isDisabledDatepicker(inst.inline?inst.dpDiv.parent()[0]:inst.input[0])){$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");$(this).addClass("ui-state-hover");if(this.className.indexOf("ui-datepicker-prev")!=-1){$(this).addClass("ui-datepicker-prev-hover")}if(this.className.indexOf("ui-datepicker-next")!=-1){$(this).addClass("ui-datepicker-next-hover")}}}).end().find("."+this._dayOverClass+" a").trigger("mouseover").end();var numMonths=this._getNumberOfMonths(inst);var cols=numMonths[1];var width=17;if(cols>1){inst.dpDiv.addClass("ui-datepicker-multi-"+cols).css("width",(width*cols)+"em")}else{inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("")}inst.dpDiv[(numMonths[0]!=1||numMonths[1]!=1?"add":"remove")+"Class"]("ui-datepicker-multi");inst.dpDiv[(this._get(inst,"isRTL")?"add":"remove")+"Class"]("ui-datepicker-rtl");if(inst==$.datepicker._curInst&&$.datepicker._datepickerShowing&&inst.input&&inst.input.is(":visible")&&!inst.input.is(":disabled")){inst.input.focus()}},_getBorders:function(elem){var convert=function(value){return{thin:1,medium:2,thick:3}[value]||value};return[parseFloat(convert(elem.css("border-left-width"))),parseFloat(convert(elem.css("border-top-width")))]},_checkOffset:function(inst,offset,isFixed){var dpWidth=inst.dpDiv.outerWidth();var dpHeight=inst.dpDiv.outerHeight();var inputWidth=inst.input?inst.input.outerWidth():0;var inputHeight=inst.input?inst.input.outerHeight():0;var viewWidth=document.documentElement.clientWidth+$(document).scrollLeft();var viewHeight=document.documentElement.clientHeight+$(document).scrollTop();offset.left-=(this._get(inst,"isRTL")?(dpWidth-inputWidth):0);offset.left-=(isFixed&&offset.left==inst.input.offset().left)?$(document).scrollLeft():0;offset.top-=(isFixed&&offset.top==(inst.input.offset().top+inputHeight))?$(document).scrollTop():0;offset.left-=Math.min(offset.left,(offset.left+dpWidth>viewWidth&&viewWidth>dpWidth)?Math.abs(offset.left+dpWidth-viewWidth):0);offset.top-=Math.min(offset.top,(offset.top+dpHeight>viewHeight&&viewHeight>dpHeight)?Math.abs(dpHeight+inputHeight):0);return offset},_findPos:function(obj){var inst=this._getInst(obj);var isRTL=this._get(inst,"isRTL");while(obj&&(obj.type=="hidden"||obj.nodeType!=1)){obj=obj[isRTL?"previousSibling":"nextSibling"]}var position=$(obj).offset();return[position.left,position.top]},_hideDatepicker:function(input){var inst=this._curInst;if(!inst||(input&&inst!=$.data(input,PROP_NAME))){return}if(this._datepickerShowing){var showAnim=this._get(inst,"showAnim");var duration=this._get(inst,"duration");var postProcess=function(){$.datepicker._tidyDialog(inst);this._curInst=null};if($.effects&&$.effects[showAnim]){inst.dpDiv.hide(showAnim,$.datepicker._get(inst,"showOptions"),duration,postProcess)}else{inst.dpDiv[(showAnim=="slideDown"?"slideUp":(showAnim=="fadeIn"?"fadeOut":"hide"))]((showAnim?duration:null),postProcess)}if(!showAnim){postProcess()}var onClose=this._get(inst,"onClose");if(onClose){onClose.apply((inst.input?inst.input[0]:null),[(inst.input?inst.input.val():""),inst])}this._datepickerShowing=false;this._lastInput=null;if(this._inDialog){this._dialogInput.css({position:"absolute",left:"0",top:"-100px"});if($.blockUI){$.unblockUI();$("body").append(this.dpDiv)}}this._inDialog=false}},_tidyDialog:function(inst){inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar")},_checkExternalClick:function(event){if(!$.datepicker._curInst){return}var $target=$(event.target);if($target[0].id!=$.datepicker._mainDivId&&$target.parents("#"+$.datepicker._mainDivId).length==0&&!$target.hasClass($.datepicker.markerClassName)&&!$target.hasClass($.datepicker._triggerClass)&&$.datepicker._datepickerShowing&&!($.datepicker._inDialog&&$.blockUI)){$.datepicker._hideDatepicker()}},_adjustDate:function(id,offset,period){var target=$(id);var inst=this._getInst(target[0]);if(this._isDisabledDatepicker(target[0])){return}this._adjustInstDate(inst,offset+(period=="M"?this._get(inst,"showCurrentAtPos"):0),period);this._updateDatepicker(inst)},_gotoToday:function(id){var target=$(id);var inst=this._getInst(target[0]);if(this._get(inst,"gotoCurrent")&&inst.currentDay){inst.selectedDay=inst.currentDay;inst.drawMonth=inst.selectedMonth=inst.currentMonth;inst.drawYear=inst.selectedYear=inst.currentYear}else{var date=new Date();inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear()}this._notifyChange(inst);this._adjustDate(target)},_selectMonthYear:function(id,select,period){var target=$(id);var inst=this._getInst(target[0]);inst._selectingMonthYear=false;inst["selected"+(period=="M"?"Month":"Year")]=inst["draw"+(period=="M"?"Month":"Year")]=parseInt(select.options[select.selectedIndex].value,10);this._notifyChange(inst);this._adjustDate(target)},_clickMonthYear:function(id){var target=$(id);var inst=this._getInst(target[0]);if(inst.input&&inst._selectingMonthYear&&!$.browser.msie){inst.input.focus()}inst._selectingMonthYear=!inst._selectingMonthYear},_selectDay:function(id,month,year,td){var target=$(id);if($(td).hasClass(this._unselectableClass)||this._isDisabledDatepicker(target[0])){return}var inst=this._getInst(target[0]);inst.selectedDay=inst.currentDay=$("a",td).html();inst.selectedMonth=inst.currentMonth=month;inst.selectedYear=inst.currentYear=year;this._selectDate(id,this._formatDate(inst,inst.currentDay,inst.currentMonth,inst.currentYear))},_clearDate:function(id){var target=$(id);var inst=this._getInst(target[0]);this._selectDate(target,"")},_selectDate:function(id,dateStr){var target=$(id);var inst=this._getInst(target[0]);dateStr=(dateStr!=null?dateStr:this._formatDate(inst));if(inst.input){inst.input.val(dateStr)}this._updateAlternate(inst);var onSelect=this._get(inst,"onSelect");if(onSelect){onSelect.apply((inst.input?inst.input[0]:null),[dateStr,inst])}else{if(inst.input){inst.input.trigger("change")}}if(inst.inline){this._updateDatepicker(inst)}else{this._hideDatepicker();this._lastInput=inst.input[0];if(typeof(inst.input[0])!="object"){inst.input.focus()}this._lastInput=null}},_updateAlternate:function(inst){var altField=this._get(inst,"altField");if(altField){var altFormat=this._get(inst,"altFormat")||this._get(inst,"dateFormat");var date=this._getDate(inst);var dateStr=this.formatDate(altFormat,date,this._getFormatConfig(inst));$(altField).each(function(){$(this).val(dateStr)})}},noWeekends:function(date){var day=date.getDay();return[(day>0&&day<6),""]},iso8601Week:function(date){var checkDate=new Date(date.getTime());checkDate.setDate(checkDate.getDate()+4-(checkDate.getDay()||7));var time=checkDate.getTime();checkDate.setMonth(0);checkDate.setDate(1);return Math.floor(Math.round((time-checkDate)/86400000)/7)+1},parseDate:function(format,value,settings){if(format==null||value==null){throw"Invalid arguments"}value=(typeof value=="object"?value.toString():value+"");if(value==""){return null}var shortYearCutoff=(settings?settings.shortYearCutoff:null)||this._defaults.shortYearCutoff;var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var year=-1;var month=-1;var day=-1;var doy=-1;var literal=false;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};var getNumber=function(match){lookAhead(match);var size=(match=="@"?14:(match=="!"?20:(match=="y"?4:(match=="o"?3:2))));var digits=new RegExp("^\\d{1,"+size+"}");var num=value.substring(iValue).match(digits);if(!num){throw"Missing number at position "+iValue}iValue+=num[0].length;return parseInt(num[0],10)};var getName=function(match,shortNames,longNames){var names=(lookAhead(match)?longNames:shortNames);for(var i=0;i<names.length;i++){if(value.substr(iValue,names[i].length)==names[i]){iValue+=names[i].length;return i+1}}throw"Unknown name at position "+iValue};var checkLiteral=function(){if(value.charAt(iValue)!=format.charAt(iFormat)){throw"Unexpected literal at position "+iValue}iValue++};var iValue=0;for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{checkLiteral()}}else{switch(format.charAt(iFormat)){case"d":day=getNumber("d");break;case"D":getName("D",dayNamesShort,dayNames);break;case"o":doy=getNumber("o");break;case"m":month=getNumber("m");break;case"M":month=getName("M",monthNamesShort,monthNames);break;case"y":year=getNumber("y");break;case"@":var date=new Date(getNumber("@"));year=date.getFullYear();month=date.getMonth()+1;day=date.getDate();break;case"!":var date=new Date((getNumber("!")-this._ticksTo1970)/10000);year=date.getFullYear();month=date.getMonth()+1;day=date.getDate();break;case"'":if(lookAhead("'")){checkLiteral()}else{literal=true}break;default:checkLiteral()}}}if(year==-1){year=new Date().getFullYear()}else{if(year<100){year+=new Date().getFullYear()-new Date().getFullYear()%100+(year<=shortYearCutoff?0:-100)}}if(doy>-1){month=1;day=doy;do{var dim=this._getDaysInMonth(year,month-1);if(day<=dim){break}month++;day-=dim}while(true)}var date=this._daylightSavingAdjust(new Date(year,month-1,day));if(date.getFullYear()!=year||date.getMonth()+1!=month||date.getDate()!=day){throw"Invalid date"}return date},ATOM:"yy-mm-dd",COOKIE:"D, dd M yy",ISO_8601:"yy-mm-dd",RFC_822:"D, d M y",RFC_850:"DD, dd-M-y",RFC_1036:"D, d M y",RFC_1123:"D, d M yy",RFC_2822:"D, d M yy",RSS:"D, d M y",TICKS:"!",TIMESTAMP:"@",W3C:"yy-mm-dd",_ticksTo1970:(((1970-1)*365+Math.floor(1970/4)-Math.floor(1970/100)+Math.floor(1970/400))*24*60*60*10000000),formatDate:function(format,date,settings){if(!date){return""}var dayNamesShort=(settings?settings.dayNamesShort:null)||this._defaults.dayNamesShort;var dayNames=(settings?settings.dayNames:null)||this._defaults.dayNames;var monthNamesShort=(settings?settings.monthNamesShort:null)||this._defaults.monthNamesShort;var monthNames=(settings?settings.monthNames:null)||this._defaults.monthNames;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};var formatNumber=function(match,value,len){var num=""+value;if(lookAhead(match)){while(num.length<len){num="0"+num}}return num};var formatName=function(match,value,shortNames,longNames){return(lookAhead(match)?longNames[value]:shortNames[value])};var output="";var literal=false;if(date){for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{output+=format.charAt(iFormat)}}else{switch(format.charAt(iFormat)){case"d":output+=formatNumber("d",date.getDate(),2);break;case"D":output+=formatName("D",date.getDay(),dayNamesShort,dayNames);break;case"o":output+=formatNumber("o",(date.getTime()-new Date(date.getFullYear(),0,0).getTime())/86400000,3);break;case"m":output+=formatNumber("m",date.getMonth()+1,2);break;case"M":output+=formatName("M",date.getMonth(),monthNamesShort,monthNames);break;case"y":output+=(lookAhead("y")?date.getFullYear():(date.getYear()%100<10?"0":"")+date.getYear()%100);break;case"@":output+=date.getTime();break;case"!":output+=date.getTime()*10000+this._ticksTo1970;break;case"'":if(lookAhead("'")){output+="'"}else{literal=true}break;default:output+=format.charAt(iFormat)}}}}return output},_possibleChars:function(format){var chars="";var literal=false;var lookAhead=function(match){var matches=(iFormat+1<format.length&&format.charAt(iFormat+1)==match);if(matches){iFormat++}return matches};for(var iFormat=0;iFormat<format.length;iFormat++){if(literal){if(format.charAt(iFormat)=="'"&&!lookAhead("'")){literal=false}else{chars+=format.charAt(iFormat)}}else{switch(format.charAt(iFormat)){case"d":case"m":case"y":case"@":chars+="0123456789";break;case"D":case"M":return null;case"'":if(lookAhead("'")){chars+="'"}else{literal=true}break;default:chars+=format.charAt(iFormat)}}}return chars},_get:function(inst,name){return inst.settings[name]!==undefined?inst.settings[name]:this._defaults[name]},_setDateFromField:function(inst,noDefault){if(inst.input.val()==inst.lastVal){return}var dateFormat=this._get(inst,"dateFormat");var dates=inst.lastVal=inst.input?inst.input.val():null;var date,defaultDate;date=defaultDate=this._getDefaultDate(inst);var settings=this._getFormatConfig(inst);try{date=this.parseDate(dateFormat,dates,settings)||defaultDate}catch(event){this.log(event);dates=(noDefault?"":dates)}inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();inst.currentDay=(dates?date.getDate():0);inst.currentMonth=(dates?date.getMonth():0);inst.currentYear=(dates?date.getFullYear():0);this._adjustInstDate(inst)},_getDefaultDate:function(inst){return this._restrictMinMax(inst,this._determineDate(inst,this._get(inst,"defaultDate"),new Date()))},_determineDate:function(inst,date,defaultDate){var offsetNumeric=function(offset){var date=new Date();date.setDate(date.getDate()+offset);return date};var offsetString=function(offset){try{return $.datepicker.parseDate($.datepicker._get(inst,"dateFormat"),offset,$.datepicker._getFormatConfig(inst))}catch(e){}var date=(offset.toLowerCase().match(/^c/)?$.datepicker._getDate(inst):null)||new Date();var year=date.getFullYear();var month=date.getMonth();var day=date.getDate();var pattern=/([+-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g;var matches=pattern.exec(offset);while(matches){switch(matches[2]||"d"){case"d":case"D":day+=parseInt(matches[1],10);break;case"w":case"W":day+=parseInt(matches[1],10)*7;break;case"m":case"M":month+=parseInt(matches[1],10);day=Math.min(day,$.datepicker._getDaysInMonth(year,month));break;case"y":case"Y":year+=parseInt(matches[1],10);day=Math.min(day,$.datepicker._getDaysInMonth(year,month));break}matches=pattern.exec(offset)}return new Date(year,month,day)};date=(date==null?defaultDate:(typeof date=="string"?offsetString(date):(typeof date=="number"?(isNaN(date)?defaultDate:offsetNumeric(date)):date)));date=(date&&date.toString()=="Invalid Date"?defaultDate:date);if(date){date.setHours(0);date.setMinutes(0);date.setSeconds(0);date.setMilliseconds(0)}return this._daylightSavingAdjust(date)},_daylightSavingAdjust:function(date){if(!date){return null}date.setHours(date.getHours()>12?date.getHours()+2:0);return date},_setDate:function(inst,date,noChange){var clear=!(date);var origMonth=inst.selectedMonth;var origYear=inst.selectedYear;date=this._restrictMinMax(inst,this._determineDate(inst,date,new Date()));inst.selectedDay=inst.currentDay=date.getDate();inst.drawMonth=inst.selectedMonth=inst.currentMonth=date.getMonth();inst.drawYear=inst.selectedYear=inst.currentYear=date.getFullYear();if((origMonth!=inst.selectedMonth||origYear!=inst.selectedYear)&&!noChange){this._notifyChange(inst)}this._adjustInstDate(inst);if(inst.input){inst.input.val(clear?"":this._formatDate(inst))}},_getDate:function(inst){var startDate=(!inst.currentYear||(inst.input&&inst.input.val()=="")?null:this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return startDate},_generateHTML:function(inst){var today=new Date();today=this._daylightSavingAdjust(new Date(today.getFullYear(),today.getMonth(),today.getDate()));var isRTL=this._get(inst,"isRTL");var showButtonPanel=this._get(inst,"showButtonPanel");var hideIfNoPrevNext=this._get(inst,"hideIfNoPrevNext");var navigationAsDateFormat=this._get(inst,"navigationAsDateFormat");var numMonths=this._getNumberOfMonths(inst);var showCurrentAtPos=this._get(inst,"showCurrentAtPos");var stepMonths=this._get(inst,"stepMonths");var isMultiMonth=(numMonths[0]!=1||numMonths[1]!=1);var currentDate=this._daylightSavingAdjust((!inst.currentDay?new Date(9999,9,9):new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));var minDate=this._getMinMaxDate(inst,"min");var maxDate=this._getMinMaxDate(inst,"max");var drawMonth=inst.drawMonth-showCurrentAtPos;var drawYear=inst.drawYear;if(drawMonth<0){drawMonth+=12;drawYear--}if(maxDate){var maxDraw=this._daylightSavingAdjust(new Date(maxDate.getFullYear(),maxDate.getMonth()-(numMonths[0]*numMonths[1])+1,maxDate.getDate()));maxDraw=(minDate&&maxDraw<minDate?minDate:maxDraw);while(this._daylightSavingAdjust(new Date(drawYear,drawMonth,1))>maxDraw){drawMonth--;if(drawMonth<0){drawMonth=11;drawYear--}}}inst.drawMonth=drawMonth;inst.drawYear=drawYear;var prevText=this._get(inst,"prevText");prevText=(!navigationAsDateFormat?prevText:this.formatDate(prevText,this._daylightSavingAdjust(new Date(drawYear,drawMonth-stepMonths,1)),this._getFormatConfig(inst)));var prev=(this._canAdjustMonth(inst,-1,drawYear,drawMonth)?'<a class="ui-datepicker-prev ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+inst.id+"', -"+stepMonths+", 'M');\" title=\""+prevText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"e":"w")+'">'+prevText+"</span></a>":(hideIfNoPrevNext?"":'<a class="ui-datepicker-prev ui-corner-all ui-state-disabled" title="'+prevText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"e":"w")+'">'+prevText+"</span></a>"));var nextText=this._get(inst,"nextText");nextText=(!navigationAsDateFormat?nextText:this.formatDate(nextText,this._daylightSavingAdjust(new Date(drawYear,drawMonth+stepMonths,1)),this._getFormatConfig(inst)));var next=(this._canAdjustMonth(inst,+1,drawYear,drawMonth)?'<a class="ui-datepicker-next ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._adjustDate('#"+inst.id+"', +"+stepMonths+", 'M');\" title=\""+nextText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"w":"e")+'">'+nextText+"</span></a>":(hideIfNoPrevNext?"":'<a class="ui-datepicker-next ui-corner-all ui-state-disabled" title="'+nextText+'"><span class="ui-icon ui-icon-circle-triangle-'+(isRTL?"w":"e")+'">'+nextText+"</span></a>"));var currentText=this._get(inst,"currentText");var gotoDate=(this._get(inst,"gotoCurrent")&&inst.currentDay?currentDate:today);currentText=(!navigationAsDateFormat?currentText:this.formatDate(currentText,gotoDate,this._getFormatConfig(inst)));var controls=(!inst.inline?'<button type="button" class="ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all" onclick="DP_jQuery_'+dpuuid+'.datepicker._hideDatepicker();">'+this._get(inst,"closeText")+"</button>":"");var buttonPanel=(showButtonPanel)?'<div class="ui-datepicker-buttonpane ui-widget-content">'+(isRTL?controls:"")+(this._isInRange(inst,gotoDate)?'<button type="button" class="ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all" onclick="DP_jQuery_'+dpuuid+".datepicker._gotoToday('#"+inst.id+"');\">"+currentText+"</button>":"")+(isRTL?"":controls)+"</div>":"";var firstDay=parseInt(this._get(inst,"firstDay"),10);firstDay=(isNaN(firstDay)?0:firstDay);var showWeek=this._get(inst,"showWeek");var dayNames=this._get(inst,"dayNames");var dayNamesShort=this._get(inst,"dayNamesShort");var dayNamesMin=this._get(inst,"dayNamesMin");var monthNames=this._get(inst,"monthNames");var monthNamesShort=this._get(inst,"monthNamesShort");var beforeShowDay=this._get(inst,"beforeShowDay");var showOtherMonths=this._get(inst,"showOtherMonths");var selectOtherMonths=this._get(inst,"selectOtherMonths");var calculateWeek=this._get(inst,"calculateWeek")||this.iso8601Week;var defaultDate=this._getDefaultDate(inst);var html="";for(var row=0;row<numMonths[0];row++){var group="";for(var col=0;col<numMonths[1];col++){var selectedDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,inst.selectedDay));var cornerClass=" ui-corner-all";var calender="";if(isMultiMonth){calender+='<div class="ui-datepicker-group';if(numMonths[1]>1){switch(col){case 0:calender+=" ui-datepicker-group-first";cornerClass=" ui-corner-"+(isRTL?"right":"left");break;case numMonths[1]-1:calender+=" ui-datepicker-group-last";cornerClass=" ui-corner-"+(isRTL?"left":"right");break;default:calender+=" ui-datepicker-group-middle";cornerClass="";break}}calender+='">'}calender+='<div class="ui-datepicker-header ui-widget-header ui-helper-clearfix'+cornerClass+'">'+(/all|left/.test(cornerClass)&&row==0?(isRTL?next:prev):"")+(/all|right/.test(cornerClass)&&row==0?(isRTL?prev:next):"")+this._generateMonthYearHeader(inst,drawMonth,drawYear,minDate,maxDate,row>0||col>0,monthNames,monthNamesShort)+'</div><table class="ui-datepicker-calendar"><thead><tr>';var thead=(showWeek?'<th class="ui-datepicker-week-col">'+this._get(inst,"weekHeader")+"</th>":"");for(var dow=0;dow<7;dow++){var day=(dow+firstDay)%7;thead+="<th"+((dow+firstDay+6)%7>=5?' class="ui-datepicker-week-end"':"")+'><span title="'+dayNames[day]+'">'+dayNamesMin[day]+"</span></th>"}calender+=thead+"</tr></thead><tbody>";var daysInMonth=this._getDaysInMonth(drawYear,drawMonth);if(drawYear==inst.selectedYear&&drawMonth==inst.selectedMonth){inst.selectedDay=Math.min(inst.selectedDay,daysInMonth)}var leadDays=(this._getFirstDayOfMonth(drawYear,drawMonth)-firstDay+7)%7;var numRows=(isMultiMonth?6:Math.ceil((leadDays+daysInMonth)/7));var printDate=this._daylightSavingAdjust(new Date(drawYear,drawMonth,1-leadDays));for(var dRow=0;dRow<numRows;dRow++){calender+="<tr>";var tbody=(!showWeek?"":'<td class="ui-datepicker-week-col">'+this._get(inst,"calculateWeek")(printDate)+"</td>");for(var dow=0;dow<7;dow++){var daySettings=(beforeShowDay?beforeShowDay.apply((inst.input?inst.input[0]:null),[printDate]):[true,""]);var otherMonth=(printDate.getMonth()!=drawMonth);var unselectable=(otherMonth&&!selectOtherMonths)||!daySettings[0]||(minDate&&printDate<minDate)||(maxDate&&printDate>maxDate);tbody+='<td class="'+((dow+firstDay+6)%7>=5?" ui-datepicker-week-end":"")+(otherMonth?" ui-datepicker-other-month":"")+((printDate.getTime()==selectedDate.getTime()&&drawMonth==inst.selectedMonth&&inst._keyEvent)||(defaultDate.getTime()==printDate.getTime()&&defaultDate.getTime()==selectedDate.getTime())?" "+this._dayOverClass:"")+(unselectable?" "+this._unselectableClass+" ui-state-disabled":"")+(otherMonth&&!showOtherMonths?"":" "+daySettings[1]+(printDate.getTime()==currentDate.getTime()?" "+this._currentClass:"")+(printDate.getTime()==today.getTime()?" ui-datepicker-today":""))+'"'+((!otherMonth||showOtherMonths)&&daySettings[2]?' title="'+daySettings[2]+'"':"")+(unselectable?"":' onclick="DP_jQuery_'+dpuuid+".datepicker._selectDay('#"+inst.id+"',"+printDate.getMonth()+","+printDate.getFullYear()+', this);return false;"')+">"+(otherMonth&&!showOtherMonths?"&#xa0;":(unselectable?'<span class="ui-state-default">'+printDate.getDate()+"</span>":'<a class="ui-state-default'+(printDate.getTime()==today.getTime()?" ui-state-highlight":"")+(printDate.getTime()==currentDate.getTime()?" ui-state-active":"")+(otherMonth?" ui-priority-secondary":"")+'" href="#">'+printDate.getDate()+"</a>"))+"</td>";printDate.setDate(printDate.getDate()+1);printDate=this._daylightSavingAdjust(printDate)}calender+=tbody+"</tr>"}drawMonth++;if(drawMonth>11){drawMonth=0;drawYear++}calender+="</tbody></table>"+(isMultiMonth?"</div>"+((numMonths[0]>0&&col==numMonths[1]-1)?'<div class="ui-datepicker-row-break"></div>':""):"");group+=calender}html+=group}html+=buttonPanel+($.browser.msie&&parseInt($.browser.version,10)<7&&!inst.inline?'<iframe src="javascript:false;" class="ui-datepicker-cover" frameborder="0"></iframe>':"");inst._keyEvent=false;return html},_generateMonthYearHeader:function(inst,drawMonth,drawYear,minDate,maxDate,secondary,monthNames,monthNamesShort){var changeMonth=this._get(inst,"changeMonth");var changeYear=this._get(inst,"changeYear");var showMonthAfterYear=this._get(inst,"showMonthAfterYear");var html='<div class="ui-datepicker-title">';var monthHtml="";if(secondary||!changeMonth){monthHtml+='<span class="ui-datepicker-month">'+monthNames[drawMonth]+"</span>"}else{var inMinYear=(minDate&&minDate.getFullYear()==drawYear);var inMaxYear=(maxDate&&maxDate.getFullYear()==drawYear);monthHtml+='<select class="ui-datepicker-month" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+inst.id+"', this, 'M');\" onclick=\"DP_jQuery_"+dpuuid+".datepicker._clickMonthYear('#"+inst.id+"');\">";for(var month=0;month<12;month++){if((!inMinYear||month>=minDate.getMonth())&&(!inMaxYear||month<=maxDate.getMonth())){monthHtml+='<option value="'+month+'"'+(month==drawMonth?' selected="selected"':"")+">"+monthNamesShort[month]+"</option>"}}monthHtml+="</select>"}if(!showMonthAfterYear){html+=monthHtml+(secondary||!(changeMonth&&changeYear)?"&#xa0;":"")}if(secondary||!changeYear){html+='<span class="ui-datepicker-year">'+drawYear+"</span>"}else{var years=this._get(inst,"yearRange").split(":");var thisYear=new Date().getFullYear();var determineYear=function(value){var year=(value.match(/c[+-].*/)?drawYear+parseInt(value.substring(1),10):(value.match(/[+-].*/)?thisYear+parseInt(value,10):parseInt(value,10)));return(isNaN(year)?thisYear:year)};var year=determineYear(years[0]);var endYear=Math.max(year,determineYear(years[1]||""));year=(minDate?Math.max(year,minDate.getFullYear()):year);endYear=(maxDate?Math.min(endYear,maxDate.getFullYear()):endYear);html+='<select class="ui-datepicker-year" onchange="DP_jQuery_'+dpuuid+".datepicker._selectMonthYear('#"+inst.id+"', this, 'Y');\" onclick=\"DP_jQuery_"+dpuuid+".datepicker._clickMonthYear('#"+inst.id+"');\">";for(;year<=endYear;year++){html+='<option value="'+year+'"'+(year==drawYear?' selected="selected"':"")+">"+year+"</option>"}html+="</select>"}html+=this._get(inst,"yearSuffix");if(showMonthAfterYear){html+=(secondary||!(changeMonth&&changeYear)?"&#xa0;":"")+monthHtml}html+="</div>";return html},_adjustInstDate:function(inst,offset,period){var year=inst.drawYear+(period=="Y"?offset:0);var month=inst.drawMonth+(period=="M"?offset:0);var day=Math.min(inst.selectedDay,this._getDaysInMonth(year,month))+(period=="D"?offset:0);var date=this._restrictMinMax(inst,this._daylightSavingAdjust(new Date(year,month,day)));inst.selectedDay=date.getDate();inst.drawMonth=inst.selectedMonth=date.getMonth();inst.drawYear=inst.selectedYear=date.getFullYear();if(period=="M"||period=="Y"){this._notifyChange(inst)}},_restrictMinMax:function(inst,date){var minDate=this._getMinMaxDate(inst,"min");var maxDate=this._getMinMaxDate(inst,"max");date=(minDate&&date<minDate?minDate:date);date=(maxDate&&date>maxDate?maxDate:date);return date},_notifyChange:function(inst){var onChange=this._get(inst,"onChangeMonthYear");if(onChange){onChange.apply((inst.input?inst.input[0]:null),[inst.selectedYear,inst.selectedMonth+1,inst])}},_getNumberOfMonths:function(inst){var numMonths=this._get(inst,"numberOfMonths");return(numMonths==null?[1,1]:(typeof numMonths=="number"?[1,numMonths]:numMonths))},_getMinMaxDate:function(inst,minMax){return this._determineDate(inst,this._get(inst,minMax+"Date"),null)},_getDaysInMonth:function(year,month){return 32-new Date(year,month,32).getDate()},_getFirstDayOfMonth:function(year,month){return new Date(year,month,1).getDay()},_canAdjustMonth:function(inst,offset,curYear,curMonth){var numMonths=this._getNumberOfMonths(inst);var date=this._daylightSavingAdjust(new Date(curYear,curMonth+(offset<0?offset:numMonths[0]*numMonths[1]),1));if(offset<0){date.setDate(this._getDaysInMonth(date.getFullYear(),date.getMonth()))}return this._isInRange(inst,date)},_isInRange:function(inst,date){var minDate=this._getMinMaxDate(inst,"min");var maxDate=this._getMinMaxDate(inst,"max");return((!minDate||date.getTime()>=minDate.getTime())&&(!maxDate||date.getTime()<=maxDate.getTime()))},_getFormatConfig:function(inst){var shortYearCutoff=this._get(inst,"shortYearCutoff");shortYearCutoff=(typeof shortYearCutoff!="string"?shortYearCutoff:new Date().getFullYear()%100+parseInt(shortYearCutoff,10));return{shortYearCutoff:shortYearCutoff,dayNamesShort:this._get(inst,"dayNamesShort"),dayNames:this._get(inst,"dayNames"),monthNamesShort:this._get(inst,"monthNamesShort"),monthNames:this._get(inst,"monthNames")}},_formatDate:function(inst,day,month,year){if(!day){inst.currentDay=inst.selectedDay;inst.currentMonth=inst.selectedMonth;inst.currentYear=inst.selectedYear}var date=(day?(typeof day=="object"?day:this._daylightSavingAdjust(new Date(year,month,day))):this._daylightSavingAdjust(new Date(inst.currentYear,inst.currentMonth,inst.currentDay)));return this.formatDate(this._get(inst,"dateFormat"),date,this._getFormatConfig(inst))}});function extendRemove(target,props){$.extend(target,props);for(var name in props){if(props[name]==null||props[name]==undefined){target[name]=props[name]}}return target}function isArray(a){return(a&&(($.browser.safari&&typeof a=="object"&&a.length)||(a.constructor&&a.constructor.toString().match(/\Array\(\)/))))}$.fn.datepicker=function(options){if(!$.datepicker.initialized){$(document).mousedown($.datepicker._checkExternalClick).find("body").append($.datepicker.dpDiv);$.datepicker.initialized=true}var otherArgs=Array.prototype.slice.call(arguments,1);if(typeof options=="string"&&(options=="isDisabled"||options=="getDate"||options=="widget")){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}if(options=="option"&&arguments.length==2&&typeof arguments[1]=="string"){return $.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this[0]].concat(otherArgs))}return this.each(function(){typeof options=="string"?$.datepicker["_"+options+"Datepicker"].apply($.datepicker,[this].concat(otherArgs)):$.datepicker._attachDatepicker(this,options)})};$.datepicker=new Datepicker();$.datepicker.initialized=false;$.datepicker.uuid=new Date().getTime();$.datepicker.version="1.8.2";window["DP_jQuery_"+dpuuid]=$})(jQuery);
/*!
 * jQuery UI Widget 1.8.2
 *
 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Widget
 */
(function(b){var j=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add(this).each(function(){b(this).triggerHandler("remove")});return j.call(b(this),a,c)})};b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h,a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend({},c.options);b[e][a].prototype=
b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.substring(0,1)==="_")return h;e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==undefined){h=i;return false}}):this.each(function(){var g=
b.data(this,a);if(g){d&&g.option(d);g._init()}else b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){this.element=b(c).data(this.widgetName,this);this.options=b.extend(true,{},this.options,b.metadata&&b.metadata.get(c)[this.widgetName],a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();
this._init()},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")},widget:function(){return this.element},option:function(a,c){var d=a,e=this;if(arguments.length===0)return b.extend({},e.options);if(typeof a==="string"){if(c===undefined)return this.options[a];d={};d[a]=c}b.each(d,function(f,
h){e._setOption(f,h)});return e},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this},enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=
b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery);

/*!
 * jQuery UI Mouse 1.8.2
 *
 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Mouse
 *
 * Depends:
 *	jquery.ui.widget.js
 */
(function(c){c.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var a=this;this.element.bind("mousedown."+this.widgetName,function(b){return a._mouseDown(b)}).bind("click."+this.widgetName,function(b){if(a._preventClickEvent){a._preventClickEvent=false;b.stopImmediatePropagation();return false}});this.started=false},_mouseDestroy:function(){this.element.unbind("."+this.widgetName)},_mouseDown:function(a){a.originalEvent=a.originalEvent||{};if(!a.originalEvent.mouseHandled){this._mouseStarted&&
this._mouseUp(a);this._mouseDownEvent=a;var b=this,e=a.which==1,f=typeof this.options.cancel=="string"?c(a.target).parents().add(a.target).filter(this.options.cancel).length:false;if(!e||f||!this._mouseCapture(a))return true;this.mouseDelayMet=!this.options.delay;if(!this.mouseDelayMet)this._mouseDelayTimer=setTimeout(function(){b.mouseDelayMet=true},this.options.delay);if(this._mouseDistanceMet(a)&&this._mouseDelayMet(a)){this._mouseStarted=this._mouseStart(a)!==false;if(!this._mouseStarted){a.preventDefault();
return true}}this._mouseMoveDelegate=function(d){return b._mouseMove(d)};this._mouseUpDelegate=function(d){return b._mouseUp(d)};c(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate);c.browser.safari||a.preventDefault();return a.originalEvent.mouseHandled=true}},_mouseMove:function(a){if(c.browser.msie&&!a.button)return this._mouseUp(a);if(this._mouseStarted){this._mouseDrag(a);return a.preventDefault()}if(this._mouseDistanceMet(a)&&
this._mouseDelayMet(a))(this._mouseStarted=this._mouseStart(this._mouseDownEvent,a)!==false)?this._mouseDrag(a):this._mouseUp(a);return!this._mouseStarted},_mouseUp:function(a){c(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate);if(this._mouseStarted){this._mouseStarted=false;this._preventClickEvent=a.target==this._mouseDownEvent.target;this._mouseStop(a)}return false},_mouseDistanceMet:function(a){return Math.max(Math.abs(this._mouseDownEvent.pageX-
a.pageX),Math.abs(this._mouseDownEvent.pageY-a.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return true}})})(jQuery);

/*
 * jQuery UI Slider 1.8.2
 *
 * Copyright (c) 2010 AUTHORS.txt (http://jqueryui.com/about)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Slider
 *
 * Depends:
 *	jquery.ui.core.js
 *	jquery.ui.mouse.js
 *	jquery.ui.widget.js
 */
(function(d){d.widget("ui.slider",d.ui.mouse,{widgetEventPrefix:"slide",options:{animate:false,distance:0,max:100,min:0,orientation:"horizontal",range:false,step:1,value:0,values:null},_create:function(){var a=this,b=this.options;this._mouseSliding=this._keySliding=false;this._animateOff=true;this._handleIndex=null;this._detectOrientation();this._mouseInit();this.element.addClass("ui-slider ui-slider-"+this.orientation+" ui-widget ui-widget-content ui-corner-all");b.disabled&&this.element.addClass("ui-slider-disabled ui-disabled");
this.range=d([]);if(b.range){if(b.range===true){this.range=d("<div></div>");if(!b.values)b.values=[this._valueMin(),this._valueMin()];if(b.values.length&&b.values.length!==2)b.values=[b.values[0],b.values[0]]}else this.range=d("<div></div>");this.range.appendTo(this.element).addClass("ui-slider-range");if(b.range==="min"||b.range==="max")this.range.addClass("ui-slider-range-"+b.range);this.range.addClass("ui-widget-header")}d(".ui-slider-handle",this.element).length===0&&d("<a href='#'></a>").appendTo(this.element).addClass("ui-slider-handle");
if(b.values&&b.values.length)for(;d(".ui-slider-handle",this.element).length<b.values.length;)d("<a href='#'></a>").appendTo(this.element).addClass("ui-slider-handle");this.handles=d(".ui-slider-handle",this.element).addClass("ui-state-default ui-corner-all");this.handle=this.handles.eq(0);this.handles.add(this.range).filter("a").click(function(c){c.preventDefault()}).hover(function(){b.disabled||d(this).addClass("ui-state-hover")},function(){d(this).removeClass("ui-state-hover")}).focus(function(){if(b.disabled)d(this).blur();
else{d(".ui-slider .ui-state-focus").removeClass("ui-state-focus");d(this).addClass("ui-state-focus")}}).blur(function(){d(this).removeClass("ui-state-focus")});this.handles.each(function(c){d(this).data("index.ui-slider-handle",c)});this.handles.keydown(function(c){var e=true,f=d(this).data("index.ui-slider-handle"),g,h,i;if(!a.options.disabled){switch(c.keyCode){case d.ui.keyCode.HOME:case d.ui.keyCode.END:case d.ui.keyCode.PAGE_UP:case d.ui.keyCode.PAGE_DOWN:case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:e=
false;if(!a._keySliding){a._keySliding=true;d(this).addClass("ui-state-active");g=a._start(c,f);if(g===false)return}break}i=a.options.step;g=a.options.values&&a.options.values.length?(h=a.values(f)):(h=a.value());switch(c.keyCode){case d.ui.keyCode.HOME:h=a._valueMin();break;case d.ui.keyCode.END:h=a._valueMax();break;case d.ui.keyCode.PAGE_UP:h=a._trimAlignValue(g+(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.PAGE_DOWN:h=a._trimAlignValue(g-(a._valueMax()-a._valueMin())/5);break;case d.ui.keyCode.UP:case d.ui.keyCode.RIGHT:if(g===
a._valueMax())return;h=a._trimAlignValue(g+i);break;case d.ui.keyCode.DOWN:case d.ui.keyCode.LEFT:if(g===a._valueMin())return;h=a._trimAlignValue(g-i);break}a._slide(c,f,h);return e}}).keyup(function(c){var e=d(this).data("index.ui-slider-handle");if(a._keySliding){a._keySliding=false;a._stop(c,e);a._change(c,e);d(this).removeClass("ui-state-active")}});this._refreshValue();this._animateOff=false},destroy:function(){this.handles.remove();this.range.remove();this.element.removeClass("ui-slider ui-slider-horizontal ui-slider-vertical ui-slider-disabled ui-widget ui-widget-content ui-corner-all").removeData("slider").unbind(".slider");
this._mouseDestroy();return this},_mouseCapture:function(a){var b=this.options,c,e,f,g,h,i;if(b.disabled)return false;this.elementSize={width:this.element.outerWidth(),height:this.element.outerHeight()};this.elementOffset=this.element.offset();c={x:a.pageX,y:a.pageY};e=this._normValueFromMouse(c);f=this._valueMax()-this._valueMin()+1;h=this;this.handles.each(function(j){var k=Math.abs(e-h.values(j));if(f>k){f=k;g=d(this);i=j}});if(b.range===true&&this.values(1)===b.min){i+=1;g=d(this.handles[i])}if(this._start(a,
i)===false)return false;this._mouseSliding=true;h._handleIndex=i;g.addClass("ui-state-active").focus();b=g.offset();this._clickOffset=!d(a.target).parents().andSelf().is(".ui-slider-handle")?{left:0,top:0}:{left:a.pageX-b.left-g.width()/2,top:a.pageY-b.top-g.height()/2-(parseInt(g.css("borderTopWidth"),10)||0)-(parseInt(g.css("borderBottomWidth"),10)||0)+(parseInt(g.css("marginTop"),10)||0)};e=this._normValueFromMouse(c);this._slide(a,i,e);return this._animateOff=true},_mouseStart:function(){return true},
_mouseDrag:function(a){var b=this._normValueFromMouse({x:a.pageX,y:a.pageY});this._slide(a,this._handleIndex,b);return false},_mouseStop:function(a){this.handles.removeClass("ui-state-active");this._mouseSliding=false;this._stop(a,this._handleIndex);this._change(a,this._handleIndex);this._clickOffset=this._handleIndex=null;return this._animateOff=false},_detectOrientation:function(){this.orientation=this.options.orientation==="vertical"?"vertical":"horizontal"},_normValueFromMouse:function(a){var b;
if(this.orientation==="horizontal"){b=this.elementSize.width;a=a.x-this.elementOffset.left-(this._clickOffset?this._clickOffset.left:0)}else{b=this.elementSize.height;a=a.y-this.elementOffset.top-(this._clickOffset?this._clickOffset.top:0)}b=a/b;if(b>1)b=1;if(b<0)b=0;if(this.orientation==="vertical")b=1-b;a=this._valueMax()-this._valueMin();return this._trimAlignValue(this._valueMin()+b*a)},_start:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=
this.values(b);c.values=this.values()}return this._trigger("start",a,c)},_slide:function(a,b,c){var e;if(this.options.values&&this.options.values.length){e=this.values(b?0:1);if(this.options.values.length===2&&this.options.range===true&&(b===0&&c>e||b===1&&c<e))c=e;if(c!==this.values(b)){e=this.values();e[b]=c;a=this._trigger("slide",a,{handle:this.handles[b],value:c,values:e});this.values(b?0:1);a!==false&&this.values(b,c,true)}}else if(c!==this.value()){a=this._trigger("slide",a,{handle:this.handles[b],
value:c});a!==false&&this.value(c)}},_stop:function(a,b){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);c.values=this.values()}this._trigger("stop",a,c)},_change:function(a,b){if(!this._keySliding&&!this._mouseSliding){var c={handle:this.handles[b],value:this.value()};if(this.options.values&&this.options.values.length){c.value=this.values(b);c.values=this.values()}this._trigger("change",a,c)}},value:function(a){if(arguments.length){this.options.value=
this._trimAlignValue(a);this._refreshValue();this._change(null,0)}return this._value()},values:function(a,b){var c,e,f;if(arguments.length>1){this.options.values[a]=this._trimAlignValue(b);this._refreshValue();this._change(null,a)}if(arguments.length)if(d.isArray(arguments[0])){c=this.options.values;e=arguments[0];for(f=0;f<c.length;f+=1){c[f]=this._trimAlignValue(e[f]);this._change(null,f)}this._refreshValue()}else return this.options.values&&this.options.values.length?this._values(a):this.value();
else return this._values()},_setOption:function(a,b){var c,e=0;if(d.isArray(this.options.values))e=this.options.values.length;d.Widget.prototype._setOption.apply(this,arguments);switch(a){case "disabled":if(b){this.handles.filter(".ui-state-focus").blur();this.handles.removeClass("ui-state-hover");this.handles.attr("disabled","disabled");this.element.addClass("ui-disabled")}else{this.handles.removeAttr("disabled");this.element.removeClass("ui-disabled")}break;case "orientation":this._detectOrientation();
this.element.removeClass("ui-slider-horizontal ui-slider-vertical").addClass("ui-slider-"+this.orientation);this._refreshValue();break;case "value":this._animateOff=true;this._refreshValue();this._change(null,0);this._animateOff=false;break;case "values":this._animateOff=true;this._refreshValue();for(c=0;c<e;c+=1)this._change(null,c);this._animateOff=false;break}},_value:function(){var a=this.options.value;return a=this._trimAlignValue(a)},_values:function(a){var b,c;if(arguments.length){b=this.options.values[a];
return b=this._trimAlignValue(b)}else{b=this.options.values.slice();for(c=0;c<b.length;c+=1)b[c]=this._trimAlignValue(b[c]);return b}},_trimAlignValue:function(a){if(a<this._valueMin())return this._valueMin();if(a>this._valueMax())return this._valueMax();var b=this.options.step>0?this.options.step:1,c=a%b;a=a-c;if(Math.abs(c)*2>=b)a+=c>0?b:-b;return parseFloat(a.toFixed(5))},_valueMin:function(){return this.options.min},_valueMax:function(){return this.options.max},_refreshValue:function(){var a=
this.options.range,b=this.options,c=this,e=!this._animateOff?b.animate:false,f,g={},h,i,j,k;if(this.options.values&&this.options.values.length)this.handles.each(function(l){f=(c.values(l)-c._valueMin())/(c._valueMax()-c._valueMin())*100;g[c.orientation==="horizontal"?"left":"bottom"]=f+"%";d(this).stop(1,1)[e?"animate":"css"](g,b.animate);if(c.options.range===true)if(c.orientation==="horizontal"){if(l===0)c.range.stop(1,1)[e?"animate":"css"]({left:f+"%"},b.animate);if(l===1)c.range[e?"animate":"css"]({width:f-
h+"%"},{queue:false,duration:b.animate})}else{if(l===0)c.range.stop(1,1)[e?"animate":"css"]({bottom:f+"%"},b.animate);if(l===1)c.range[e?"animate":"css"]({height:f-h+"%"},{queue:false,duration:b.animate})}h=f});else{i=this.value();j=this._valueMin();k=this._valueMax();f=k!==j?(i-j)/(k-j)*100:0;g[c.orientation==="horizontal"?"left":"bottom"]=f+"%";this.handle.stop(1,1)[e?"animate":"css"](g,b.animate);if(a==="min"&&this.orientation==="horizontal")this.range.stop(1,1)[e?"animate":"css"]({width:f+"%"},
b.animate);if(a==="max"&&this.orientation==="horizontal")this.range[e?"animate":"css"]({width:100-f+"%"},{queue:false,duration:b.animate});if(a==="min"&&this.orientation==="vertical")this.range.stop(1,1)[e?"animate":"css"]({height:f+"%"},b.animate);if(a==="max"&&this.orientation==="vertical")this.range[e?"animate":"css"]({height:100-f+"%"},{queue:false,duration:b.animate})}}});d.extend(d.ui.slider,{version:"1.8.2"})})(jQuery);


/*	

	jQuery pub/sub plugin by Peter Higgins (dante@dojotoolkit.org)

	Loosely based on Dojo publish/subscribe API, limited in scope. Rewritten blindly.

	Original is (c) Dojo Foundation 2004-2010. Released under either AFL or new BSD, see:
	http://dojofoundation.org/license for more information.

*/	

(function(d){

	// the topic/subscription hash
	var cache = {};

	d.publish = function(/* String */topic, /* Array? */args){
		// summary: 
		//		Publish some data on a named topic.
		// topic: String
		//		The channel to publish on
		// args: Array?
		//		The data to publish. Each array item is converted into an ordered
		//		arguments on the subscribed functions. 
		//
		// example:
		//		Publish stuff on '/some/topic'. Anything subscribed will be called
		//		with a function signature like: function(a,b,c){ ... }
		//
		//	|		$.publish("/some/topic", ["a","b","c"]);
		cache[topic] && d.each(cache[topic], function(){
			this.apply(d, args || []);
		});
	};

	d.subscribe = function(/* String */topic, /* Function */callback){
		// summary:
		//		Register a callback on a named topic.
		// topic: String
		//		The channel to subscribe to
		// callback: Function
		//		The handler event. Anytime something is $.publish'ed on a 
		//		subscribed channel, the callback will be called with the
		//		published array as ordered arguments.
		//
		// returns: Array
		//		A handle which can be used to unsubscribe this particular subscription.
		//	
		// example:
		//	|	$.subscribe("/some/topic", function(a, b, c){ /* handle data */ });
		//
		if(!cache[topic]){
			cache[topic] = [];
		}
		cache[topic].push(callback);
		return [topic, callback]; // Array
	};

	d.unsubscribe = function(/* Array */handle){
		// summary:
		//		Disconnect a subscribed function for a topic.
		// handle: Array
		//		The return value from a $.subscribe call.
		// example:
		//	|	var handle = $.subscribe("/something", function(){});
		//	|	$.unsubscribe(handle);
		
		var t = handle[0];
		cache[t] && d.each(cache[t], function(idx){
			if(this == handle[1]){
				cache[t].splice(idx, 1);
			}
		});
	};

})(jQuery);



/**
 *
 * Class creation utility  - (independent of js framework)
 * this alows classic styles of class creation
 *
 * var animal = Object.subClass {(
 *		init: function() {}, // constructor
 *		noise: function() {}
 *	});
 *
 *	var cat = animal.subClass {(
 *		init: function() {
 *		this._super() // call parent's constructor
 *		},
 *		noise: function() { } // override parent's noise function
 *	));
 */
if(!Object.subClass) {
	(function(){
	  var initializing = false,
		// Determine if functions can be serialized
		fnTest = (/xyz/).test(function(){xyz;}) ? (/\b_super\b/) : (/.*/);

	  // Create a new Class that inherits from this class
	  Object.subClass = function(prop) {
		var _super = this.prototype;

		// Instantiate a base class (but only create the instance,
		// don't run the init constructor)
		initializing = true;
		var proto = new this();
		initializing = false;
		// Copy the properties over onto the new prototype
		for (var name in prop) {
		  // Check if we're overwriting an existing function
		  proto[name] = typeof prop[name] == "function" &&
			typeof _super[name] == "function" && fnTest.test(prop[name]) ?
			(function(name, fn){
			  return function() {
				var tmp = this._super;

				// Add a new ._super() method that is the same method
				// but on the super-class
				this._super = _super[name];

				// The method only need to be bound temporarily, so we
				// remove it when we're done executing
				var ret = fn.apply(this, arguments);
				this._super = tmp;

				return ret;
			  };
			})(name, prop[name]) :
			prop[name];
		}

		// The dummy class constructor
		function BBClass() {
		  // All construction is actually done in the init method
		  if ( !initializing && this.init )
			this.init.apply(this, arguments);
		}

		// Populate our constructed prototype object
		BBClass.prototype = proto;

		// Enforce the constructor to be what we expect
		BBClass.constructor = BBClass;

		// And make this class extendable
		BBClass.subClass = arguments.callee;

		return BBClass;
	  };
	})();
}

/**
 * (IE only-- required by google AFS contract)
 * Sets the window status bar message.
 * @param string w
 */
function statusBar(msg) {
	window.status = msg || '';
	return true;
}


/**
 * Extention to jquery object
 */
(function($) {

	$.extend($, {

		/**
		 * Templating system
		 * usage:
		 * var data = {animal: "dog la\' bos"};
		 * $j.tmpl('the #{animal} jumped the fence', data);
		 */
		tmpl : function(str, data) {
			if (typeof data !== 'object') { return ''; }
			with (data) { // with needs to stay
				return str.replace(/\\?#\{([^{}]+)\}/g, function (match, name) {
					if (match.charAt(0) == '\\') {
						return match.slice(1);
					}

					try {
						return eval('data.' + name); // eval needs to stay
					} catch (err){
						// nested object might not exist - Abort! Abort!
						return '';
					}
				});
			}
		},

		/**
		 * Returns the size of an object that's acting like an associative array
		 * @param object - obj
		 * @returns number
		 */
		getSize: function(obj) {
			var size = 0, key;
			for (key in obj) {
				if (obj.hasOwnProperty(key)) {
					size++;
				}
			}
			return size;
		},

		/**
		 * Returns object values
		 * @param object - obj
		 * @returns array
		 */
		getValues: function(obj) {
			var values = [];
			for (var property in obj) {
				values.push(obj[property]);
			}
			return values;
		},

		/**
		 * Returns object keys
		 * @param object - obj
		 * @returns array
		 */
		getKeys: function(obj) {
			var keys = [];
			for (var property in obj) {
				keys.push(property);
			}
			return keys;
		},

		/**
		 * Converts an obj {foo: 'bar'} into a string ?foo=bar
		 * @param object - obj
		 * @returns string
		 */
		toQueryString:function(obj) {

			if (typeof obj !== 'object') { return ''; }
			var queryString = '';

			$j.each(obj, function(key, value) {
				queryString += key + '=' + encodeURIComponent(value) + '&';
			});

			// remove last '&'
			queryString = queryString.slice(0, queryString.length-1);

			return queryString;
		},

		/**
		 * Parses a query string into a useable object
		 * e.g. '?x=1&y=2&z[]=a&z[]=b'.toQueryObject()
		 *     ==> {x: '1', y: '2', z: ['a', 'b']}
		 * @param string - str
		 * @returns object
		 */
		toQueryObject: function(str) {
			if (str.indexOf('=') < 1) {
				return {};
			}

			var clean_str = (str.substring(0, 1) === '?' ? str.slice(1) : str),
				values = {};

			$j.each(clean_str.split('&'), function(i, pair) {
				var parts = pair.split('=', 2),
					key = unescape(parts[0].replace(/\[\]$/, '')),
					val = unescape(parts[1]);
				if (!(key in values)) {
					values[key] = val;
				} else if ($j.isArray(values[key])) {
					values[key].push(val);
				} else {
					values[key] = [values[key], val];
				}
			});

			return values;
		},

		isBoolean: function(o) {
			if (typeof o == "object" && o !== null) {
				return (typeof o.valueOf() === "boolean");
			} else {
				return (typeof o === "boolean");
			}
		},

		isNull: function(o) {
			return (o === null);
		},

		isUndefined: function(o) {
			return (typeof o === "undefined");
		},

		isNullOrUndefined: function(o) {
				return $.isNull(o) || $.isUndefined(o);
		},

		isString: function(o) {
			return (typeof o === "string");
		},

		isArray: function(o) {
			return (typeof o === 'object' && o instanceof Array);
		},

		emptyString: function(str) {
			if ($.isNullOrUndefined(str)) {
				return true;
			} else if ($.isString(str)) {
				return str.replace(/^\s+|\s+$/g,"").length === 0;
			}
			return false;
		},

		startsWith: function(str, val) {
			if ($.isString(str)) {
				return (str.indexOf(val) === 0);
			}
			return false;
		},

		stringFormat: function() {
			var s = arguments[0];
			for (var i = 0; i < arguments.length - 1; i++) {
				var reg = new RegExp("\\{" + i + "\\}", "gm");
				s = s.replace(reg, arguments[i + 1]);
			}

			return s;
		}
	});

	/**
	 * Extend the jQuery UI DatePicker to accept an option entitled calendarPostfix.
	 * the contents of the input will be appended to the calendar.
	 */
	$.extend($.datepicker, {
		/**
		 * link to the normal datepicker html generation method
		 */
		super_generateHTML: $.datepicker._generateHTML,

		/**
		 * Override the html generation method to append the html provided
		 * in the calendarPostfix option to the calendar.
		 */
		_generateHTML: function (inst) {
			var calendar = this.super_generateHTML(inst);
			var post_calendar_html = this._get(inst, 'calendarPostfix');
			return calendar + (post_calendar_html ? post_calendar_html : '');
		}
	});		
})(jQuery);

var Utils = {

	/** sleep...gooo to sleep
	 * Blocking sleep using the AJAX_Sleep class in common
	 */
	sleep: function(seconds) {
		var args = {
			seconds: seconds
		};
		var data = {
			service: 'Sleep.fetch',
			args: JSON.stringify(args)
		};
		$j.ajax({
			url: '/ajax/',
			data: data,
			async: false
		});
	},

	/**
	 * compares two objects and returns true if their properties and values are equal
	 * This function does not* process functions, we're just comparing objects as key/value pairs
	 *
	 * @param {object} obj1
	 * @param {object} obj2
	 * @returns true/false - if objects are equal or not
	 * @type bool
	 */
	sameObj: function(obj1, obj2) {
		// check if we already have targeted ads open
		// based off the current args

		if ($j.getSize(obj1) === 0 || $j.getSize(obj2) === 0) {
			return false;
		}

		for (var prop in obj1) {
			if (obj1[prop]) {
				if (typeof obj1[prop] !== 'function') {
					if (obj1[prop] !== obj2[prop]) {
						return false;
					}
				}

			} else if (obj2[prop]) {
				// obj1[prop] is null but obj2[prop] isn't
				return false;
			}
		}

		for (prop in obj2) {
			if (typeof obj1[prop] === 'undefined') {
				return false;
			}
		}

		return true;
	},

	/**
	 * Setup required divs as jquery objects
	 *
	 */
	setRequiredDivs: function(divs, obj) {

		$j.each(divs, function(index, div) {
			var divID = obj[div];
			obj[div] = $j('#' + divID);

			if (obj[div].length != 1) {
				throw {
					name: 'InvalidId',
					message: 'Invalid div ID: ' + divID
				};
			}
		});
	 },

	 /**
	  * Sets up divs as jquery objects
	  * Since these divs are NOT required - if the div doesn't exist
	  * the object will be an empty array
	  */
	setDivs: function(divs, obj) {
		$j.each(divs, function(index, div) {
			var divID = obj[div];
			obj[div] = $j('#' + divID);
		});
	},

	/**
	 * Copy all of the properties into the provided object.
	 *
	 * @param Object|Function		obj				The object that will be
	 * 		copied to.
	 * @param Object|Function		properties		The properties that will be
	 * 		copied over.
	 * @returns Object|Function		The updated object.
	 */
	extend: function(obj, properties) {
		for(var property in properties) { obj[property] = properties[property]; }
		return obj;
	},

	/**
	 * Get the value of a cookie stored under the given name.
	 *
	 * @param String		name	The name under which the cookied was stored.
	 * @return Mixed		Returns a string if the cookie was founded, false if
	 *		the cookie does not exist.
	 */
	getCookie: function(name) {
		var result = false;

		// Look for the position of the cookie name
		var pos = document.cookie.indexOf(name + '=');
		if(pos != -1) {
			var start = pos + name.length + 1;
			var end = document.cookie.indexOf(';', start);
			if(end == -1) { end = document.cookie.length; }

			result = document.cookie.substring(start, end);
		}

		return result;
	},

	/**
	 * Get an element.
	 *
	 * @param Element|String		element		Either the element itself of the
	 * 		ID of the element.
	 * @returns Element		The element.
	 */
	getElement: function(element) {
		if(Utils.type(element) === 'string') {
			element = $j('#' + element)[0];
		}

		return element;
	},

	/**
	 * Retrieve the data stored at the provided key in the provided object.
	 *
	 * @param Object		options				The options.
	 * @param String		key					The key of the item to get.
	 * @param Mixed			default_value		The value to return if the
	 * 		provided key does not exist.
	 * @returns Mixed
	 */
	getOption: function(options, key, default_value) {
		return options ? Utils.pick(options[key], default_value) : default_value;
	},

	/**
	 * Merges any number of objects recursively without referencing them or
	 * their sub-objects.
	 *
	 * @returns Object		The object that is created as a result of merging
	 * 		all the objects passed in.
	 */
	merge: function() {
		var args = [{}];
		for(var i = 0, l = arguments.length; i < l; ++i) { args.push(arguments[i]); }
		return Utils.mixin.apply(null, args);
	},

	/**
	 * Mix all the following arguments into the provided mix.
	 */
	mixin: function(mix) {
		for(var i = 1, l = arguments.length; i < l; ++i) {
			var object = arguments[i];
			if(Utils.type(object) !== 'object') { continue; }

			for(var key in object) {
				var op = object[key], mp = mix[key];
				mix[key] = (mp && Utils.type(op) === 'object' && Utils.type(mp) === 'object') ?
					Utils.mixin(mp, op) : Utils.unlink(op);
			}
		}

		return mix;
	},

	/**
	 * Return the first item that is NOT undefined or null if all of them are
	 * undefined.
	 *
	 * @returns Mixed
	 */
	pick: function() {
		for(var i = 0; i < arguments.length; ++i) {
			if(arguments[i] !== undefined) { return arguments[i]; }
		}

		return null;
	},

	/**
	 * Reset the provided object.
	 *
	 * @param Object|Function		obj		The object or function to reset.
	 * @param String				key		The key of the item to reset.
	 */
	reset: function(obj, key) {
		if(Utils.type(key) === false) {
			for(var p in obj) { Utils.reset(obj, p); }
			return obj;
		}

		delete obj[key];
		switch(Utils.type(obj[key])) {
			case 'object':
				var F = function() {};
				F.prototype = obj[key];
				var i = new F;
				obj[key] = Utils.reset(i);
				break;
			case 'array':
				obj[key] = Utils.unlink(obj[key]);
				break;
			default:
				break;
		}

		return obj;
	},

	/**
	 * Set a cookie value under the given name for the given number of
	 * expiration offset.
	 *
	 * The available options are:
	 * 		domain - (String) The domain under which the cookie is stored.
	 * 			Defaults to the the value that exists in window.location.host.
	 * 		offset - (int) The offset of time in the future until the cookie
	 * 			will expire. Defaults to 0 (which means the cookie last for the
	 * 			browsing session).
	 * 		type - (String) The type for the offset. Possible values for this
	 * 			includes: seconds, minutes, hours, days, and years.
	 *
	 * @param String		name		The name under which the cookie will be
	 * 		stored.
	 * @param String		value		The value for the cookie.
	 * @param Object		options		Various other options. Optional.
	 * @return void
	 */
	setCookie: function(name, value, options) {
		// Get the expiration date
		var expires = '';
		if(Utils.getOption(options, 'offset', false)) {
			// Get the correct offset value
			// Do NOT add breaks in here as the final result of the offset needs
			// to be in milliseconds
			switch(options.type) {
				case 'years': options.offset *= 365;
				case 'days': options.offset *= 24;
				case 'hours': options.offset *= 60;
				case 'minutes': options.offset *= 60;
				case 'seconds': options.offset *= 1000;
				default: break;
			}

			// Increment the current date by the given offset
			var date = new Date();
			date.setTime(date.getTime() + options.offset);

			// Set the expire string
			expires = ';expires=' + date.toGMTString();
		}

		// Set the cookie
		document.cookie = name + '=' + value + expires + ';domain=' + Utils.getOption(options, 'domain', window.location.host) + ';path=/';
	},

	/**
	 * Make sure the provided object is an array.
	 *
	 * @param Mixed		obj		The provided data.
	 * @returns Array
	 */
	splat: function(obj) {
		var type = Utils.type(obj);
		return type ? ((type !== 'array' && type !== 'arguments') ? [obj] : obj) : [];
	},

	/**
	 * Remove references from the provided object.
	 *
	 * @param Mixed		obj		The object to remove references from.
	 * @returns Mixed		The provided object with references removed.
	 */
	unlink: function(obj) {
		var unlinked;
		switch(Utils.type(obj)) {
			case 'object':
				unlinked = {};
				for(var p in obj) { unlinked[p] = Utils.unlink(obj[p]); }
				break;
			case 'array':
				unlinked = [];
				for(var i = 0, l = obj.length; i < l; ++i) { unlinked[i] = Utils.unlink(obj[i]); }
				break;
			default: return obj;
		}

		return unlinked;
	},

	/**
	 * Get the type of the provided object.
	 *
	 * The following is the list of possible types that can be returned:
	 * 	- array
	 * 	- date
	 * 	- regexp
	 * 	- element
	 * 	- textnode
	 * 	- whitespace
	 * 	- arguments (this is the arguments object that is part of every function)
	 * 	- collection (an HTMLCollection)
	 * 	- boolean
	 * 	- number
	 * 	- string
	 * 	- object
	 *
	 * @param Mixed		object		The object to test.
	 * @returns String|false		Returns the type of the object or false if
	 * 		the object is null or undefined.
	 */
	type: function(obj) {
		if(obj == undefined) { return false; }
		else if(obj instanceof Array) { return 'array'; }
		else if(obj instanceof Date) { return 'date'; }
		else if(obj instanceof RegExp) { return 'regexp'; }
		else if(obj.nodeName) {
			switch(obj.nodeType) {
				case 1: return 'element';
				case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
				default:
					break;
			}
		} else if(typeof obj.length === 'number') {
			if(obj.callee) return 'arguments';
			else if(obj.item) return 'collection';
		}

		return typeof obj;
	}
};


/* --------- Native javascript extentions ------- */

/**
 * Returns the size of an object that's acting like an associative array
 * @deprecated - use $j.getSize(obj)
 */
Object.size = function(obj) {
	var size = 0, key;
	for (key in obj) {
		if (obj.hasOwnProperty(key)) {
			size++;
		}
	}
	return size;
};


/**
 * Parses a query string into a useable object
 * e.g. '?x=1&y=2&z[]=a&z[]=b'.toQueryObject()
 *     ==> {x: '1', y: '2', z: ['a', 'b']}
 */
String.prototype.toQueryObject = function() {
	if (this.indexOf('=') < 1) {
		return {};
	}

	var _this = (this.substring(0, 1) === '?' ? this.slice(1) : this),
	    values = {};

	$j.each(_this.split('&'), function(i, pair) {
		var parts = pair.split('=', 2),
		    key = unescape(parts[0].replace(/\[\]$/, '')),
		    val = unescape(parts[1]);
		if (!(key in values)) {
			values[key] = val;
		} else if ($j.isArray(values[key])) {
			values[key].push(val);
		} else {
			values[key] = [values[key], val];
		}
	});

	return values;
};


/**
 *
 * @deprecated - use $.toQuerString(obj) instead
 */
Object.toQueryString = function(obj) {
	if (typeof obj !== 'object') {
		return '';
	}

	var queryString = '';

	$j.each(obj, function(key, value) {
		queryString += key + '=' + encodeURIComponent(value) + '&';
	});

	// remove last '&'
	queryString = queryString.slice(0, queryString.length-1);

	return queryString;
};

BBDebug = {
	/**
	 * Controls whether or not debug messages are enabled
	 * 
	 * @ var bool
	 */
	enabled: false,
	
	/**
	 * Handle to the console object
	 * 
	 * @var object
	 */
	console: null,

	/**
	 * Used to determine if init is complete
	 *
	 * @var bool
	 */
	initComplete: false,


	init: function() {
		if(BBDebug.initComplete) return;
		BBDebug.initComplete = true;

		//No, no, that's not Mr. Debug.  It stands for Money Rain Debug!
		var loc = window.location.toString();
		if(loc.indexOf("mr_debug") != -1) {
			BBDebug.enable(true);
			BBDebug.log("Enabling BBDebug from QS");
		}
	},

	/**
	 * Enables/Disables debugging
	 * 
	 * @param bool e
	 * @return void
	 */
	enable: function(e) {
		BBDebug.enabled = e;
	},
	
	/**
	 * Pass through to console.log()
	 * 
	 * @param String message
	 * @return void
	 */
	log: function(message, obj) {
		BBDebug.init();
		var c = BBDebug.getConsole();
		
		if(c === null) return;
		
		if (typeof obj !== 'undefined') {
			c.log(message, obj);
		} else {
			c.log(message);
		}
	},

	/**
	 * Pass through to console.error()
	 * 
	 * @param String message
	 * @return void
	 */
	error: function(message, obj) {
		BBDebug.init();
		var c = BBDebug.getConsole();
		
		if(c === null) return;
		
		if (typeof obj !== 'undefined') {
			c.error(message, obj);
		} else {
			c.error(message);
		}
	},

	/**
	 * Pass through to console.dir()
	 * 
	 * @param Object obj
	 * @return void
	 */
	dir: function(obj) {
		BBDebug.init();
		var c = BBDebug.getConsole();
		
		if(c === null) return;
		
		c.dir(obj);
	},
	
	/**
	 * Pass through to console.trace()
	 * 
	 * @return void
	 */
	trace: function() {
		BBDebug.init();
		var c = BBDebug.getConsole();
		
		if(c === null) return;
		
		c.trace();
	},
	
	
	/**
	 * Pass through to console.group or groupCollapsed
	 * 
	 * @var title string for grouping title
	 * @var collapse boolean to indicated collapsed grouping
	 * 
	 * @return void
	 */
	group: function( title, collapse) {
		BBDebug.init();
		var console = this.getConsole();
		if(console) {
			if(collapse && console.groupCollapsed) {
				console.groupCollapsed(title);
			}
			else if(console.group) {
				console.group(title);
			}
		}
		
	},
	
	/**
	 * Pass through to console.groupEnd
	 * 
	 * @return void
	 */
	groupEnd: function( title, collapse) {
		BBDebug.init();
		var console = this.getConsole();
		if(console && console.groupEnd) {
			console.groupEnd();
		}
	},
	
	
	/**
	 * Takes care of setting up FirebugLite if it's needed and returns the console object or null
	 * 
	 * @return Object
	 */
	getConsole: function() {
		BBDebug.init();
		if(!BBDebug.enabled) return null;
		
		if(BBDebug.console === null) {
			if(typeof console != 'undefined') {
				BBDebug.console = console;
			}
			else if(typeof firebug != 'undefined') {
				firebug.initConsole();
				firebug.init();

			    if(typeof console != 'undefined') {
			    	BBDebug.console = firebug.console;
			    	
			    }
			}
		}
		
		return BBDebug.console;
	},
	
	/**
	 * Logs an error to the BB app error log server side
	 * 
	 * @var string msg Error message
	 * @var string url URL of the offending script.  If missing this will default to window.location
	 * @var int linenumber Line number of the error if available
	 */
	logErrorToServer: function(msg, url, linenumber){
		BBDebug.init();
		if(typeof msg == "undefined") msg = "";
		if(typeof linenumber == "undefined") linenumber = "";
		if(typeof url == "undefined") url = window.location;
		
		var error_url = "/js_error_log.php?msg=" + encodeURIComponent(msg) + "&js_url=" +
						encodeURIComponent(url) + "&line=" + encodeURIComponent(linenumber) +
						"&url=" + encodeURIComponent(window.location);

		var img = new Image();
		img.src=error_url;
		
		return false;
	}
};

//Attach a function to onerror to catch all errors and log to the server
window.onerror = BBDebug.logErrorToServer;

	

/**
 * Simple class that acts as a container for metadata displayed on the page
 */
var BBMetaData = Object.subClass({

	/**
	 * Set the ID of this ad
	 *
	 * @param int id
	 */
	init: function(id) {
		// IMPORTANT: This must be declared here (constructor, not a class
		// variable) or else all instances will share the same metaData hash.
		this.metaData = {'id': id};
	},

	/**
	 * Returns the ID of this ad.  Really just a wrapper of getMetaData
	 * 
	 * @return int
	 */
	getID: function() {
		return this.getMetaData("id");
	},
		
	/**
	 * Returns the value of the requested meta data or null if not set
	 * 
	 * @param string key Key for the meta data
	 * @return mixed
	 */
	getMetaData: function(key) {
		return (!$j.isUndefined(this.metaData[key]) ? this.metaData[key] : null);
	},
		
	/**
	 * Sets a meta data value for this ad
	 * 
	 * @param string key Name of the property
	 * @param string value Value of the property
	 * @return this
	 */
	setMetaData: function(key, value) {
		this.metaData[key] = value;
		return this;
	},
		
	/**
	 * Utility function to call setMetaData for each element in the given data object
	 * 
	 * @param Object data Object containing properties, ie {prop1: "val1", prop2: "val2"}
	 * @return this
	 */
	setMetaDataMulti: function(data) {
		for(var key in data) {
			this.setMetaData(key, data[key]);
		}
		return this;
	},
		
	/**
	 * Executes a prototype template with this ads meta data
	 * 
	 * @param Template temp Prototype template
	 * @return string
	 */
	display: function(temp) {
		return $j.tmpl(temp, this.metaData);
	}
});

/**
 * Subclass of BBMetaData to represent a BB ad
 */
var BBAd = BBMetaData.subClass({});
//Static BBAd methods/properties

/**
 * Object pool for use with BBAd.singleton
 * @var Object objectPool
 */
BBAd.objectPool = {};

/**
 * Singleton method for BBAd
 * 
 * @param int id ID of the ad
 * @return BBAd
 */
BBAd.singleton = function(id) {
	if($j.isUndefined(BBAd.objectPool[id])) {
		BBAd.objectPool[id] = new BBAd(id);
	}
	
	return BBAd.objectPool[id];
	
};

/**
 * Subclass of BBMetaData to represent a TA Check Rates offer
 */
var TACheckRates = BBMetaData.subClass({});

//Static TACheckRates methods/properties

/**
 * Object pool for use with TACheckRates.singleton
 * @var Object objectPool
 */
TACheckRates.objectPool = {};

/**
 * Singleton method for TACheckRates
 * 
 * @param int id ID of the ad
 * @return TACheckRates
 */
TACheckRates.singleton = function(id) {
    if($j.isUndefined(TACheckRates.objectPool[id])) {
        TACheckRates.objectPool[id] = new TACheckRates(id);
    }
    
    return TACheckRates.objectPool[id];
    
};

/**
 * Make AJAX requests to STM AJAX framework endpoint
 */
(function($) {
	$.extend({
		bbget: function(service, args, callback, type) {
			if (typeof type == "undefined") {
				type = "json";
			}
			var params = {service: service, args: JSON.stringify(args)};
			$.get("/ajax/", params, callback, type);
			return true;
		}
	});
})(jQuery);

var BBIPLocation = Object.subClass({

	/**
	 * Should we display the DHTML layer that offers users
	 * a choice to use a different BB affiliate (.com vs co.uk)?
	 */
	af_redirect: false,
	
	/**
	 * Input objects we need to update when we have the user's location
	 */
	inputs: null,

	/**
	 * User location if it could be determined
	 */
	location: null,

	/**
	 * Have we already made an AJAX call to try to get the user's location?
	 */
	request_done: false,

	/**
	 * AJAX service to hit
	 */
	service: 'LocationSuggest.ip',

	/**
	 * Type of location string to request (airport vs city)
	 */
	type: null,

	/**
	 * Set information common to all inputs this request might be used to update
	 *
	 * @param string type
	 * @param bool af_redirect
	 */
	init: function(type, af_redirect) {
		this.type = type;
		this.af_redirect = af_redirect;
		this.inputs = [];
	},

	/**
	 * Register an input to be populated by the results of an IP location
	 * AJAX request if it is not already registered. Return true if the element
	 * was registered and a request was made.
	 *
	 * @param string input_id
	 * @param bool overwrite
	 * @return bool
	 */
	register: function(input_id, overwrite) {
		var input = $j('#' + input_id);
		if (input.length <= 0) {
			return false;
		}

		if (-1 === $j.inArray(input, this.inputs)) {
			input.data('overwrite', overwrite);
			this.inputs.push(input);
		}

		// Maybe this is being called after the AJAX call already
		// returned. Set the value of the input with the result
		// we got back
		if (this.location) {
			this.updateLocation();
			return false;
		}

		var icup = BookingBuddy.getQSParam('icup');
		var cookie_val = BookingBuddy.getCookie('iploc');
		if (!icup && cookie_val) {
			// value came from user cookie. validate it after populating the input
			this.updateLocation(cookie_val, true);
			return false;
		}

		// Another input already made an AJAX request
		// so we don't want to make another one
		if (this.request_done) {
			return false;
		} else {
			this.request_done = true;
		}

		return this.serviceRequest(icup);
	},

	/**
	 * Store an IP location and update any currently registered
	 * inputs with the value, optionally update validation for
	 * the input to avoid an AJAX request
	 *
	 * @param string loc
	 * @param bool do_validation
	 * @return void
	 */
	updateLocation: function(loc, do_validation, country) {
		if (!$j.isNullOrUndefined(loc)) {
			this.location = loc;
		}

		$j.each(this.inputs, $j.proxy(function(index, input) {
			if (!input.val() || input.data('overwrite')) {
				input.val(this.location);
				// unless requested otherwise, update the LS list of valid
				// locations to include this one.
				if (!do_validation) {
					this.updateElementValidation(input, country);
				}
				// blur as if a user had entered this value to make sure it
				// triggers anything counting on user input to do something
				input.blur();
			}
		}, this));

		BookingBuddy.createCookie('iploc', this.location, 1440);
	},

	/**
	 * Update the location validation list of this input if it is a
	 * smartElement and is using locationSuggest
	 *
	 * @param jQuery input
	 * @return void
	 */
	updateElementValidation: function(input, country) {
		var smartElement = input.data('smartElement');
		if (smartElement && smartElement.locationSuggest) {
			smartElement.locationSuggest.addValidLocation(this.location, { country: country });
		}
	},

	/**
	 * Make a request to the IP location AJAX service passing along
	 * any query string override
	 *
	 * @param string ip_override
	 * @return bool
	 */
	serviceRequest: function(ip_override) {
		var args = {
			type: this.type
		};

		if (this.af_redirect && BookingBuddy.affiliateName) {
			args.af_redirect = this.af_redirect;
			args.af = BookingBuddy.affiliateName;
		}

		if (!$j.isNullOrUndefined(ip_override)) {
			args.icup = ip_override;
		}

		$j.bbget(this.service, args, $j.proxy(this.serviceCallback, this));
		return true;
	},

	/**
	 * Handle possible responses from the AJAX IP location service
	 *
	 * @param Object data
	 * @return bool
	 */
	serviceCallback: function(data) {
		if (!data) {
			return false;
		}

		if (data.iplocation) {
			this.updateLocation(data.iplocation, false, data.country);
		}

		if (data.affiliate_redirect && this.af_redirect) {
			this.displayAffiliateRedirect(data.affiliate_redirect);
		}
		
		return true;
	},

	/**
	 * If we got a chunk of HTML back from the AJAX call display it on the page
	 * and add some functionality to it
	 *
	 * @param string source
	 * @return void
	 */
	displayAffiliateRedirect: function(source) {
		$j('<div></div>').html(source).appendTo(document.body);

		var popup = new DHTMLPopup_Affiliate('dhtml_affiliate');
		popup.setPageMask('page_mask');
		popup.show();
	
		$j('#dhtml_af_img_0, #dhtml_af_link_0').click( function() { 
			popup.submit('dhtml_af_0', 'affiliate', 'save');																			 
		});
	
		$j('#dhtml_af_img_1, #dhtml_af_link_1').click( function() { 
			popup.submit('dhtml_af_1', 'affiliate', 'save');
			omnitureSendEvents('event26', 'A BB.com user is selecting an international site');
		});
	
		var dhtmlAffiliateDiv = $j('#dhtml_affiliate');
		dhtmlAffiliateDiv.bind('dhtmlpopup:af:success', function(event, url) {
			popup.hide();
			// only redirect when going to a different domain
			if (url && -1 == url.indexOf(BookingBuddy.domain)) {
				window.location.href = url;
			}
		});
	
		dhtmlAffiliateDiv.bind('dhtmlpopup:af:error', $j.proxy(popup.hide, popup));
		$j('#dhtml_affiliate_close').click($j.proxy(popup.hide, popup));
	}
});

/**
 * Keep track of IP location population objects based on
 * the type of value they are requesting
 */								   
BBIPLocation.object_pool = {};

/**
 * Register an input to be populated based on a user's IP address
 *
 * @param String type
 * @param String input_id
 * @param bool overwrite
 * @param bool af_redirect
 * @return BBIPLocation
 */
BBIPLocation.populate = function(type, input_id, overwrite, af_redirect) {
	if ($j.isNullOrUndefined(BBIPLocation.object_pool[type])) {
		BBIPLocation.object_pool[type] = new BBIPLocation(type, af_redirect);
	}

	return BBIPLocation.object_pool[type].register(input_id, overwrite);
};


/**
 * Load the contents of a chunk asynchronously, append it to the page,
 * and run a user-supplied callback that expects a the ID of the
 * element being loaded as an argument. Just run the callback if
 * the element is already on the page.
 */
(function($) {
	$.extend({
		
		/**
		 * Load the given chunk over AJAX that will provide a top level
		 * HTML element matching the given selector, then run the given callback
		 * with the selector as an argument. The chunk name will be namespaced on
		 * the server side to prevent arbitrary execution. Optionally extra params 
		 * can be passed to the chunk using the extra_data argument. These params 
		 * will be avaiable to the content using getData.
		 *
		 * @param string elm_selector
		 * @param string chunk_name
		 * @param function callback
		 * @param object extra_data
		 * @return bool
		 */
		asyncLoad: function(elm_selector, chunk_name, callback, extra_data) {
			if ($(elm_selector).length > 0) {
				callback(elm_selector);
				return true;
			}

			var params = {
				'id': chunk_name, 
				'af': BookingBuddy.affiliateName,
				'extra': $.extend({}, extra_data)
			};

			var ajaxHelper = function(data) {
				$(data).appendTo('body');
				
				// no element. handle gracefully
				if (!$(elm_selector).length) {
					BBDebug.log('$.asyncLoad: Could not run callback, missing: ' + elm_selector);
					return;
				}

				callback(elm_selector);
			};

			$.bbget('ContentLoader.load', params, ajaxHelper);
			return true;
		}
	});
})(jQuery);


/**
 * Container class to hold search data by input
 * @class  BBSearchData
 */
function BBSearchData(modes) { 

	/**
	 * inputmap by searchmode and input_name
	 * @private
	 * @type object
	 */
	var BBInputMap = {
		air: {
			c1:		'departure_city',
			d1:		'departure_date',
			t1:		'departure_time',
			c2:		'arrival_city',
			d2:		'return_date',
			t2:		'return_time',
			ntrv:	'num_travelers',
			st:		'search_type',
			sc: 	'service_class',
			ns:		'non_stop'
		},
		hotel: {
			c2:		'arrival_city',
			d1:		'arrival_date',
			d2:		'departure_date',
			prv:	'provider',
			ntrv:	'num_travelers',
			nrms:	'num_rooms'
		},
		car: {
			c2:		'pickup_city',
			d1:		'pickup_date',
			t1:		'pickup_time',
			d2:		'dropoff_date',
			t2:		'dropoff_time',
			da: 	'driver_age'
		},
		vacation: {
			c1:		'departure_city',
			d1:		'departure_date',
			c2:		'arrival_city',
			d2:		'return_date',
			ntrv:	'num_travelers'
		},
		vacation_rental: {
			c2:		'arrival_city',
			d1:		'arrival_date',
			d2:		'departure_date',
			nrms:	'num_bedrooms'
		},
		cruise: {	
			c3:		'destination',
			d3:		'travel_month',
			clin:	'cruise_line',
			clen:	'cruise_length',
			o55:	'over_55'
		},
		holiday: {
			c1:		'departure_city',
			c2:		'arrival_city',
			d1:		'departure_date',
			d2:		'return_date',
			flx:	'flex_days',
			ppp:	'price_per_person',
			bb:		'board_basis',
			ntrv:	'num_travelers',
			nchl:	'num_children'
		}
	};


	/**
	 * ShortInput names
	 * @private
	 * @type object
	 */
	var shortInputNames = {
		ac:		'arrival_city',
		dc:		'departure_city',
		pc:		'pickup_city',
		dd:		'departure_date',
		ad:		'arrival_date',
		rd:		'return_date',
		dt:		'departure_time',
		rt:		'return_time',
		ns:		'non_stop',
		sc:		'service_class',
		nt:		'num_travelers',
		st:		'search_type',
		dest:	'destination',
		tm:		'travel_month',
		cl:		'cruise_line'
	};

	/**
	 * Set Search Data 
	 * @param {string} - input - input name   - 'departure_city'
	 * @param {string} - value - input value  - 'boston'
	 * @param {string} - searchMode -  'air,hotel, etc.,'
	 * @returns null or value set
	 * @type null/object
	 */
	this.setData = function(input, value, searchMode) {
		if (typeof BBInputMap[searchMode] === 'undefined')  {
			// Default to Air searchMode
			searchMode = 'air';
		}


		// convert short input names to regular input names - ac to arrival_city
		if (typeof shortInputNames[input] !== 'undefined') {
			input = shortInputNames[input];
		}

		var inputMap = BBInputMap[searchMode];
		for (var i in inputMap) {
			if (inputMap[i] == input) {
				this[i] = value;	// this[c1] = boston 
				break;
			}
		}

		return this[i];	
	};

	/** 
	 * Get search data by input name and searchmode
	 * @param {string} - input - input name - 'departure_city'
	 * @param {string} - searchMode -  'air,hotel, etc.,'
	 * @returns object containing the search data for given input - 'this[c1] = boston'
	 * @type object/null
	 */
	this.getData = function(input, searchMode) {
		// convert short input names to regular input names - ac to arrival_city
		if (typeof shortInputNames[input] !== 'undefined') {
			input = shortInputNames[input];
		}

		var inputMap = BBInputMap[searchMode];
		for (var i in inputMap) {
			if (inputMap[i] == input && typeof this[i] !== 'undefined') {
				return this[i];		// this[city_1] = boston
			}
		}
		return null;
	};

	/**
	 * Returns an object containing all search data relevant to given mode
	 * @param {string} - searchmode
	 * @returns an object containing all search data for given search mode
	 * @type object
	 */
	this.getDataByMode = function(searchMode) {
		var inputMap = BBInputMap[searchMode];			
		var data = new BBSearchData();
		for (var i in inputMap) {
			if (typeof this[i] !== 'undefined') {
				data[i] = this[i];	
			}
		}
		return data;
	};
		

	/**
	 * Display method for this object
	 * @param {string} searchMode
	 * @param {string} template
	 */
	this.display = function (searchMode, template) {

		var data = this.getDataByMode(searchMode);

		for (var input in data) {
			if (input.search(/d\d/) !== -1) {		// look for d1 or d2

				var date_obj = new Date();

				// Special date handling for cruise
				if (input == 'd3') {
					var date_parts = data[input].split("-");
					if (date_parts.length != 3) {
						break;
					}

					date_obj.setFullYear(date_parts[0]);
					date_obj.setMonth(date_parts[1] - 1);
					date_obj.setDate(date_parts[2]);
				}
				else {
					date_obj.setTime(data[input]);	
				}
				data[input] = {};

				data[input].month = (date_obj.getMonth()+1) < 10 ? '0' + (date_obj.getMonth()+1) : (date_obj.getMonth()+1);
				data[input].day = date_obj.getDate() < 10 ? '0' + date_obj.getDate() : date_obj.getDate();
				data[input].year = date_obj.getFullYear();

			}
		}

		return $j.tmpl(template, data);
	};

}

/**
 *
 * @class  BBSavedSearchData
 */
function BBSavedSearchData (cookie_name, max_recent_searches) {

	/**
	 * Cookie name
	 * @private
	 * @type string
	 */
	var _cookieName = (typeof cookie_name) !== 'undefined' ? cookie_name : 'bbSearches';

	/**
	 * Max number of recent searches per search mode
	 * @private
	 * @type number
	 */
	var _maxRecentSearches = (typeof max_recent_searches) !== 'undefined' ? max_recent_searches : 3;

	/**
	 * Contains the current state and recent searches by search mode
	 * @private
	 * @type object
	 */
	var _savedSearches = {
		cs: {},
		rs: {
			air: [],
			hotel: [],
			car: [],
			vacation: [],
			vacation_rental: [],
			cruise: [],
			holiday: []
		}
	};

	/**
	 * Clears all saved search data in this object
	 */
	this.empty = function() {
		_savedSearches.cs = new BBSearchData();
		$j.each(_savedSearches.rs, $j.proxy(function(mode, data) {
			_savedSearches.rs[mode] = [];
		}, this));
	};

	/**
	 * Returns  savedSearches object
	 * @returns savedSearches object
	 * @type object
	 */
	this.getSavedSearches = function() {
		return _savedSearches;
	};

	/**
	 * Returns the Current search object
	 * @returns Current search object
	 * @type obj/null
	 */
	this.getCurrentState = function() {
		return _savedSearches.cs;
	};

	/**
	 * Add current state data for given input
	 * @param {string} input  - input name		-  'departure_city, arrival_city, etc.,'
	 * @param {string} value   - input value	-  'boston, 02/01/2010, 3, etc.,'
	 * @param {string} searchMode  - input searchmode - 'air, hotel, etc.,'
	 */
	this.addCurrentSearchData = function(input, value, searchMode) {
		// for AB test ticket #32710
		if (input.indexOf('_city') > 0 && (value.toLowerCase() == 'to' || value.toLowerCase() == 'from')) {
			return;
		}

		if (_savedSearches.cs.setData(input, value, searchMode)) {
			this.storeSearches();
			BBDebug.log('-- added to current search ['+searchMode+']['+input+']='+value, _savedSearches.cs);
		}
	};

	/**
	 * Return current state data for given input
	 * @param input string - input name		-  'departure_city, arrival_city, etc.,'
	 * @param searchMode string - input searchmode - 'air, hotel, etc.,'
	 * @returns a string containing the value or null
	 * @type string/null
	 */
	this.getCurrentSearchData = function(input, searchMode) {
		return	_savedSearches.cs.getData(input, searchMode);
	};


	/**
	 * Checks if currentSearch object exists in recentSearchData
	 * based on compareValues
	 * @param searchMode
	 * @param currentSearch
	 * @param recentSearches
	 */
	this.isDuplicateSearch = function(searchMode, currentSearch, recentSearches) {
		var compareValues = ['c1','c2','d1','d2'];
		var sameValues = true;

		$j.each(recentSearches, function(index, recentSearch) {
			$j.each(compareValues, function(index, input) {
				if ((currentSearch[input] && recentSearch[input]) && currentSearch[input] === recentSearch[input]) {
					return true;	 // next iteration
				} else {
					sameValues = false;
					return sameValues; // break out of loop
				}
			});

			// Break out of loop if not same Values
			// else check the next recentSearch obj
			return !sameValues;
		});


		return sameValues;
	};

	/**
	 * Add RecentSearch
	 * @param {string}  searchMode - mode on which recent searches will stored - 'air, hotel, etc.,'
	 * @returns  _savedSearches object
	 * @type object
	 */
	this.addRecentSearchData = function(searchMode) {
		if (!_savedSearches.rs[searchMode]) { return null; }

		var currentSearch = _savedSearches.cs.getDataByMode(searchMode);
		var recentSearches = _savedSearches.rs[searchMode];

		if (recentSearches.length > 0 ) {
			if (this.isDuplicateSearch(searchMode, currentSearch, recentSearches)) {
					BBDebug.log('---- DUPLICATE Data not entered into recent searches');
					return {};
			}

			if (recentSearches.length == _maxRecentSearches) {
				recentSearches.pop();
			}
		}

		// add to the top of the array
		_savedSearches.rs[searchMode].unshift(currentSearch);
		this.storeSearches();

		return _savedSearches;
	};

	/**
	 * Return recent search data for based on input, searchmode and index
	 * @param {string} input - input name		-  'departure_city, arrival_city, etc.,'
	 * @param {string} searchMode - input searchmode - 'air, hotel, etc.,'
	 * @param {int}	   index - index number -
	 * @returns a string containing the value or null
	 * @type string/null
	 */
	this.getRecentSearchData = function(input, searchMode, index) {
		if (typeof index === 'undefined') { index = 0; }
		if (typeof _savedSearches.rs[searchMode] === 'undefined' || typeof _savedSearches.rs[searchMode][index] === 'undefined') {
			return null;
		}

		return _savedSearches.rs[searchMode][index].getData(input, searchMode);
	};

	/**
	 * Load data from the query string (if available)
	 * and override the current search data with it
	 */
	this.loadFromQueryString = function() {
		var params = null;

		try {
			// make sure to use jQuery.extend to clone queryString
			params = (BookingBuddy.queryString && $j.getSize(BookingBuddy.queryString) > 0) ? $j.extend({}, BookingBuddy.queryString) : $j.toQueryObject(window.location.search.replace(/\+/g,'%20'));
		} catch (err) {
			params = null;
		}

		if (params) {
			var searchMode = BookingBuddy.searchMode;

			var param_keys = $j.getKeys(params);		

			var mode_params = ['search_mode', 'searchtype'];
			var valid_modes = ['air', 'hotel', 'car', 'vacation', 'vacation_rental', 'cruise', 'holiday'];

			// The 'searchtype' param is used to override the default mode on the multi-mode main
			// widget for SEM landing pages. It needs to affect query string params loaded as well
			// but should be lower priority than the 'search_mode' param
			// Check for valid_modes, since searchtype some times can be "motel"
			$j.each(mode_params, function(index, key) {
				if ($j.inArray(key, param_keys) !== -1 && $j.inArray(params[key], valid_modes) !== -1) {
					searchMode = params[key];
					return false;
				}

				return true;
			});

			// convert departure_month and departure_day from the query string to *_date fields
			var param_keys_string = param_keys.toString();
			if (param_keys_string.indexOf('_month') !== -1 && param_keys_string.indexOf('_day') !== -1) {
				var dep_day, dep_month, dep_year;
				var ret_day, ret_month, ret_year;
				var parts, from, to;

				switch (searchMode) {
					case 'air':
					case 'vacation':
					case 'holiday':
						from = 'departure';
						to = 'return';
						break;
					case 'car':
						from = 'pickup';
						to = 'dropoff';
						break;
					case 'hotel':
					case 'vacation_rental':
						from = 'arrival';
						to = 'departure';
						break;
					default:
						// do nothing
						break;
				}

				if (from && params[from + '_month'] && params[from + '_day']) {
					// create departure date time stamp
					parts = params[from + '_month'].split(/[\s\+]/);
					dep_day =  params[from + '_day'];
					dep_month = parts[0];
					dep_year = parts[1];
					params[from + '_date'] = new Date(dep_year, (dep_month-1), dep_day).getTime();
				}

				if (to && params[to + '_month'] && params[to + '_day']) {
					// create return date time stamp
					parts = params[to + '_month'].split(/[\s\+]/);
					ret_day = params[to + '_day'];
					ret_month = parts[0];
					ret_year = parts[1];
					params[to + '_date'] = new Date(ret_year, (ret_month-1), ret_day).getTime();
				}
			}


			var now = new Date();
			var date, input_year;

			for (var input in params) {
				if (input.indexOf('_date') != -1) {

					// If the url string has the old format ie. (departure_month/departure_day)
					// it has been converted to a timestamp - so we'll account for that also
					if (isNaN(params[input])) {
						parts = params[input].split('/');

						switch (BookingBuddy.affiliateName) {
							case 'bookingbuddy_co_uk':
							case 'world_travel_guide':
							case 'uk_holiday_weather':
							case 'uk_airfaresflights':
							case 'holidaywatchdog':
								date = new Date(parts[2], (parts[1]-1), parts[0]);
								break;
							default:
								date = new Date(parts[2], (parts[0]-1), parts[1]);
								break;
						}
					} else {
						date = new Date();
						date.setTime(params[input]);
					}
					
					// bump dates in the past one year ahead
					// this is needed due to flash widgets - ticket #31742 (So we're not accounting for UK dates here)
					if (BookingBuddy.affiliateName != 'bookingbuddy_co_uk') {
						input_year = date.getFullYear();
	
						// if date is less than now minus one day
						while (date.getTime() < (now.getTime()-1000*60*60*24)) {
							input_year ++;
							date.setFullYear(input_year);
							BBDebug.log('Date input [' + input + '] -  got bumped one year ahead: ' + date.getTime());
						}
					}

					params[input] = date.getTime();
				}

				// special case for cruise - travel_month MM YYYY
				// Moving forward the url param will be changed to YYYY-MM-DD so
				// we'll check to make sure that we don't try to alter the right one
				if (searchMode == 'cruise' && input == 'travel_month' && params[input].length < 10) {
					var travel_month = params[input];
					params[input] = travel_month.substring(travel_month.length-4) + '-' + travel_month.substring(0,2) + '-01';
				}

				this.addCurrentSearchData(input, params[input], searchMode);
			}

			BBDebug.log('-- Current search data got overriden by the querystring');
		}
	};

	/**
	 * Gets stored state from cookie if it exists
	 * else - initializes new search object for current searches
	 */
	this.getStoredSearches = function() {
		// Load and parse the cookie
		var cookie = BookingBuddy.getCookie(_cookieName);
		if (cookie) {
			var o = JSON.parse(cookie);
			var parsed_json = (o) ? o : {};
			_savedSearches = parsed_json;

			var input;

			// Transform saved objects into BBSearchData objects
			// Current Search obj
			if (typeof _savedSearches.cs === 'object') {

				var savedCS = new BBSearchData();
				for (input in _savedSearches.cs) {
					savedCS[input] = _savedSearches.cs[input];
				}

				_savedSearches.cs = savedCS;
				delete savedCS;
			}

			// Recent Searches obj
			if (typeof _savedSearches.rs === 'object') {

				for (var searchMode in _savedSearches.rs) {

					if ($j.isArray(_savedSearches.rs[searchMode])) {

						$j.each(_savedSearches.rs[searchMode], function(index, recentSearch) {
							var savedRS = new BBSearchData();
							for (input in recentSearch) {
								savedRS[input] = recentSearch[input];
							}

							_savedSearches.rs[searchMode][index] = savedRS;
							delete savedRS;
						});
					}
				}

			}

		} else {
			// initialize BBSearchData obj
			_savedSearches.cs = new BBSearchData();
		}

		// Override cookie data with query string data
		this.loadFromQueryString();
	};

	/**
	 * Store searches into cookie
	 */
	this.storeSearches = function() {
		var cookie_data = encodeURIComponent(JSON.stringify(_savedSearches));
		BookingBuddy.createCookie(_cookieName, cookie_data, 60*24*30);
	};

	/**
	 * Returns a count of recent searches for a given searchMode
	 * @param {string} searchMode  - input searchmode - 'air, hotel, etc.,'
	 * @returns recent searches count by mode
	 * @type int
	 */
	this.getRecentSearchCount = function(searchMode) {
		if (typeof _savedSearches.rs[searchMode] === 'undefined') { return null; }
		return _savedSearches.rs[searchMode].length;
	};

	/**
	 * Display Recent search by searchMode and index
	 * @param {string} searchMode  - input searchmode - 'air, hotel, etc.,'
	 * @param {int} index - index of recent search
	 * @param {string} template - formatting template
	 * @returns a formatted string based on the template given
	 * @type string
	 */
	this.displayRecentSearch = function(searchMode, index, template) {
		if (typeof _savedSearches.rs[searchMode][index] === 'undefined') { return null; }
		if (typeof template === 'undefined') { template = 'Default'; }

		var recentSearch = _savedSearches.rs[searchMode][index];

		if (searchMode == 'air') {
			if (recentSearch.getData('departure_date', 'air')  && recentSearch.getData('return_date', 'air') && recentSearch.getData('st', 'air') == 'oneway') {
				template += '_oneway';
			} else {
				template += '_roundtrip';
			}
		}

		return recentSearch.display(searchMode,	BookingBuddy.Strings.RS[searchMode][template]);
	};


	// Initialize _savedSearches obj
	this.getStoredSearches();
}

// Static BBSavedSearchData methods/properties

/**
 * Object pool for use with BBSavedSearchData.singleton()
 *
 * @var Object objectPool
 */
BBSavedSearchData.prototype.objectPool = {};

/**
 * Singleton method for BBSavedSearchData
 *
 * @param String cookie_name
 * @param int [max_recent_searches]
 */
BBSavedSearchData.singleton = function(cookie_name, max_recent_searches) {
	var hash = cookie_name || '____blank';

	if (typeof BBSavedSearchData.prototype.objectPool[hash] == 'undefined') {
		BBSavedSearchData.prototype.objectPool[hash] = new BBSavedSearchData(cookie_name, max_recent_searches);
	}

	return BBSavedSearchData.prototype.objectPool[hash];
};

/**
 * This class handles the retrieval and display of targeted ads
 * based on ad ids and targetted inputs given
 * @class BBTargeting
 */

var BBTargeting = Object.subClass({

	/**
	 * Holds ids for all ads retreived
	 * @type array
	 */
	adIDs: [],

	/**
	 * Holds ids for targeted inputs
	 * @type array
	 */
	inputs: [],

	/**
	 * Determines how many adds per row
	 * @type number
	 */
	adsPerRow: 4,

	/**
	 * Id of the ad container div
	 * @type string
	 */
	adContainerDiv: 'BBLTWrapper',

	/**
	 * Div id on which ads will be inserted into
	 * @type string
	 */
	adDisplayDiv: 'BBLTAds',

	/**
	 * Div id for targeted ads title
	 * @type string
	 */
	titleDisplayDiv: 'BBLTHeader',

	/**
	 * Holds the template to generate ad html from the JSON data
	 * @type string
	 */
	adTemplate: 'Default',

	/**
	 * Holds the template to generate the title html from the JSON data
	 * @type string
	 */
	titleTemplate: 'Default',

	/**
	 * Holds args sent on previous ajax call
	 * This is to avoid consecutive calls with same data
	 * @type object
	 */
	submittedArgs: {},

	/**
	 * Targeted input searchMode
	 * @type string
	 */
	searchMode: null,

	/**
	 * @constructor
	 * @param {string} input_id - id of targeted inpust
	 * @param {array} ad_ids - targeted ad ids
	 * @returns true if init went smoothly
	 */
	init: function(input_ids, ad_ids){
		if (typeof ad_ids === 'undefined') {
			// BookingBuddy.targetedAdIds  should be outputted on the page
			if (typeof BookingBuddy.targetedAdIds === 'undefined') { return false; }
			if (BookingBuddy.targetedAdIds.length === 0) { return false; }

			ad_ids = BookingBuddy.targetedAdIds;
		}

		if (typeof input_ids === 'undefined' || ad_ids === 0) { return false; }

		// init divs
		try {
			// Setup required ids
			var requiredDivs = ['adDisplayDiv', 'adContainerDiv'];
			Utils.setRequiredDivs(requiredDivs, this);

			// Add ad ids to array
			$j.each(ad_ids, $j.proxy(function(index, ad_id) { this.addAdID(ad_id); }, this));
			$j.each(input_ids, $j.proxy(function(index, input_id) { this.addInput(input_id); }, this));

		} catch (e) {
			BBDebug.error(e.name  +  ' EXCEPTION: ' + e.message);
			return false;
		}


		var onLoadUpdate = true;
		// Update targeting on load
		$j.each(this.inputs, function(index, input) {
			if (input.hasClass('LSInvalid') || input.val() === '') {
				onLoadUpdate = false;
			}
		});

		if (onLoadUpdate) {
			this.updateHandler();
		}


		return true;
	},

	/**
	 * @throws InvalidId if id doesn't exist
	 * @param {string} input_id
	 */
	addInput: function(input_id) {
		var input = $j("#" + input_id);
		if (input.length != 1) {
			throw {
				name: 'InvalidId',
				message: "Invalid BBTargeting Input ID: " + input_id
			};
		}

		// Set searchMode based on data pulled from input
		var _smartElement = input.data('smartElement');
		this.searchMode = _smartElement.smartForm.mode;

		this.inputs.push(input);

		// If the input has the TargetingNoUpdate class, targeted ads will not be updated
		// if the input value is changed after pageload. This is useful for pages where the
		// targeted location value is guaranteed to be passed in the querystring, and changing
		// the location causes the page to reload (presumably by submitting a form).
		if (input.hasClass("TargetingNoUpdate")) {
			return true;
		}

		if (input.hasClass("locationSuggest")) {
			input.bind("LocationSuggest:valid", {obj: this, input: input}, $j.proxy(this.updateHandler, this));
		} else {
			var inputEvent = input.is('select,input[@type=radio]') ? 'change' : 'blur';
			input.bind(inputEvent, {obj: this, input: input}, $j.proxy(this.updateHandler, this));
		}

		return true;

	},

	/**
	 * Preloads logo image for ad and add its id to the adIds array
	 * @param {Number} id -
	 */
	addAdID:  function(id) {
		// preload images and add to ad Array
		var img = new Image();
		img.src =  BBAd.singleton(id).getMetaData('logo');
		this.adIDs.push(id);
	},

	/**
	 * Sends ajax request to retrieve ads based on inputs and ids
	 * @param {object} event -
	 */
	updateHandler: function(event) {
		var args = {};
		$j.each(this.inputs, function(index, input){
			var input_name = input.attr("name");

			if(input.attr("type") == "radio"){
				if (input.is(":checked")) {
					args[input_name] = input.val();
				}
			}
			else{
				args[input_name] = input.val();
			}
		});

		args.m = this.searchMode;
		args.ids = this.adIDs.join(':');


		// Only send new* requests
		if (!Utils.sameObj(args, this.submittedArgs)) {

			// if adContainerDiv is visible - close it
			if (this.adContainerDiv.is(':visible')) {
				this.adContainerDiv.slideUp('slow', function() {
					$j(this).trigger('BBTargeting:containerClosed');
				});
			}

			BBDebug.log("------ Updating targeting");

			$j.bbget('LocationTargeting.targeted', args, $j.proxy(this.processAds, this));
			this.submittedArgs = args;
		}
	},

	/**
	 * Processes ads received from the ajax call
	 * @param {object} data - data received from the ajax call
	 * @return undefined - if we don't have ad ids
	 */
	processAds: function(data) {
		BBDebug.log('BBTargeting data: ', data);

		if (!data || typeof data.ids === 'undefined' || data.ids.length === 0) { return; }

		var ads = [];
		var bbad;

		// Get non-priced ads
		$j.each(data.ids, function(index, ad) {
			bbad = BBAd.singleton(ad.ad_id);
			if (ad.is_priced != 'Y') {
				ads.push(bbad);
			}
		});
		delete bbad;

		// make sure we have something and update divs with info
		if (ads.length > 0) {
			this.updateAdDiv(ads);
			this.updateTitleDiv(data.msg);
		}
	},

	/**
	 * Updates the ad div with the ads returned
	 * @param {object} ads -
	 */
	updateAdDiv: function(ads) {

		BBDebug.log('-- updateAdDiv');

		var html = '';
		var row;
		var adTemplate = this.adTemplate;

		while (ads.length > 0) {
			// split ads array based on ads per row
			row = ads.splice(0, this.adsPerRow);

			html += '<div class = "BBSearchLogos' + row.length + '">';
			$j.each(row, function(index, ad) {
				html += ad.display(BookingBuddy.Strings.LT.ad[adTemplate]);
			});
			html += '</div>';
		}

		// Update div, fire an event and then show the div
		this.adDisplayDiv.html(html);
		this.adDisplayDiv.trigger("BBTargeting:adsUpdated");

		this.showAdDiv();
	},

	/**
	 * Updates title div based on data from the ajax request
	 * @param {object} msg - contains point_1 and point_2, used to determine which title to use
	 * @throws InvalidId if id doesn't exist
	 */
	updateTitleDiv:  function(msg) {

		// If we have a valid titleDisplayDiv - check to see if it exists
		var titleDisplayDiv = $j('#' + this.titleDisplayDiv);
		if (titleDisplayDiv.length != 1) {
			throw {
				name: 'InvalidId',
				message: "Invalid BBTargeting title display div ID: " + this.titleDisplayDiv
			};
		}

		var searchMode = this.searchMode;
		var template = this.titleTemplate;

		if (searchMode == 'air') {
			if (msg['point_1'] && msg['point_2']) {
				template += '_RoundTrip';
			} else {
				if(msg['point_1']) {
					template += '_From';
				} else {
					template += '_To';
				}
			}
		} else if (searchMode == 'vacation') {
			if (!msg['point_2'] && msg['point_1']) {
				template += '_From';
			} else if (msg['point_2']) {
				template += '_To';
			}
		}

		var html = $j.tmpl(BookingBuddy.Strings.LT[searchMode][template], msg);
		$j(titleDisplayDiv).html(html);
		$j(titleDisplayDiv).trigger("BBTargeting:titleUpdated");
	},

	/**
	 * Displays the div containing the ads
	 */
	showAdDiv: function() {

		this.adContainerDiv.slideDown('slow', function(){
			$j(this).trigger('BBTargeting:adContainerOpen');
		});
	}
});


BookingBuddy.submitHandlers = {

	/**
	 * Array of prepop windows
	 * If it contains any windows, these will be used for search windows first
	 * before new windows are opened
	 */
	prepopWindows: [],

	/**
	 * List of all open searchWindows
	 */
	searchWindows: [],

	/**
	 * Options needed to track positioning and window names for multiple windows search
	 */
	checkedMultiWindowOptions: {
		winTop: 	35,	
		winTopInc: 	35,
		winLeft: 	screen.width - 925,
		winLeftInc: 35
	},


	/**
	 * @param {object} - contains window options for each of the submit handlers
	 */
	windowOptions: {

		/**
		 * Allow other scripts to over write where the window pops open
		 * Below are the default options.  Left unchanged the
		 * BookingBuddy.submitHandlers.windowOptions.options will return defaults.
		 */
		safari:		(($j.browser.webkit && !navigator.appVersion.toLowerCase().match("chrome")) ? true : false), 

		/**
		 * window left position
		 */
		left:  		(screen.width - 925 > 0 ? screen.width - 925 : 0),

		/**
		 * window options for multi window submission on widgets
		 */
		multiAdWidget: "status=1,toolbar=1,location=1,menubar=1,resizable=1,scrollbars=1,height=800,width=1024",		

		/**
		 *
		 */
		options: function() {	
			var options =	(($j.browser.msie || BookingBuddy.submitHandlers.windowOptions.safari) ? 'toolbar=0' : 'toolbar=1') +
							',location=1,menubar=0,resizable=1,scrollbars=1,height=525,width=925,top=35,left=' + 
							BookingBuddy.submitHandlers.windowOptions.left;

			return options;
		}
	},

	/**
	 * Default submit handler behavior for generic form submission from a smartForm.
	 * This will serialize the form fields (both smartElements and regular form elements)
	 * into a url string and either open the url in a new window if a target is declared or
	 * refresh the current window with the new url
	 *
	 * @param {string} - form name
	 * @param {string} - form target value
	 * @param {object} - event object
	 */
	defaultHandler: function(form, target, e) {
		e.stopPropagation();
		e.preventDefault();

		var smartForm 	= $j.data( form,"smartForm"),
			action 		= $j(form).attr("action"),
		 	url 		= action + (action.indexOf('?') != -1 ? '&' : '?') + $j(form).serialize();

		smartForm.submitHandlerData["action"] = action;
		smartForm.submitHandlerData["target"] = target;
		smartForm.submitHandlerData["url"] = url;


		if (target && target === '_top'){
			if(!smartForm.settings.debug) {
				top.location.href = url;
			}
			return false;
		}

		var window_name = 'BBwin' +  new Date().getTime();
		smartForm.submitHandlerData["window_name"] = window_name;

		if(!smartForm.settings.debug) {
			var w = window.open(url, window_name);

			if (!w) { return false; }
			form.target = window_name;
			w.focus();
		}

		return false;
	},

	/**
     * Handler to pass through to HTML form defaults. Useful when you want smartForm
     * validation but want to submit via POST or some such
     *
     * @param {string} - form name
     * @param {string} - form target value
     * @param {object} - event object
     */
    noHandler: function(form, target, e) {
        return true;
    },

	/**
	 * Handler for submitting to multiple site from a widget
	 *
	 * @param {string} - form name
	 * @param {string} - form target value
	 * @param {object} - event object
	 */
	multiAdWidgetHandler: function(form ,target, e) {
		e.stopPropagation();
		e.preventDefault();
		var _checked_multi = $j(form).find(".multi_site:checked");
		if (_checked_multi.length < 1) {
			return BookingBuddy.submitHandlers.defaultHandler(form, target, e);
		} else {
			var _smartForm = $j.data( form,"smartForm");

			var _action = $j(form).attr("action");
			var _url = _action + (_action.indexOf('?') != -1 ? '&' : '?') + $j(form).serialize();
			var _window_name = 'BBwin' + new Date().getTime();

			var _bbSite = window.open(_url, _window_name,  this.windowOptions.multiAdWidget);

		    if (!_bbSite) {
		        return false;
		    }

		   var _adSite = new Array();
		   var _site_base = $j("#siteDomainBase").html();
		   var _arr_city_field = _smartForm.mode + "_arrival_city";
		   var _options =  this.windowOptions.multiAdWidget;
		   _checked_multi.each( function() {
		    	var _ad_id = $j(this).attr("id");
		    	var _blank_url = $j('#blank_form_'+_ad_id).length > 0 ? $j('#blank_form_'+_ad_id).html() : false;
		    	var _missing_req_field = false;
		    	if($j("#" + _arr_city_field).length > 0) {
		    		_missing_req_field = $j.emptyString($j("#" + _arr_city_field).val());
		    	}
		    	var _url = "http://rd."+ _site_base +"?r="+_ad_id+"&" + $j(form).serialize();
		    	if (_blank_url && _missing_req_field ) {
		    		_url = _blank_url;
				}
		    	_window_name = _ad_id;
			    var _w = window.open(_url, _window_name, _options);

			    if (_w) {
			    	_adSite.push(_w);
			    	_w.focus();
			    }

		    });

		   _bbSite.focus();
		}
		return false;
	},

	/**
	 * Sets the form to redirect to the correct place.
	 * @param {jquery object}	- form 
	 * @returns boolean
	 */
	setFormRedirectUrl: function($search_form) {
		if (!$j('#siteRedirectUrl').text()) { 
			return false; 
		}

		var redirect_url 	= $j('#siteRedirectUrl').text();

		$search_form.attr('action', redirect_url);

		// Ad any qs params in the RD url to the form so they get submitted properly
		// TODO:  Use $.toQueryObject instead of the manual QS parsing below.
		var parsedUrl = redirect_url.split('?');
		if(parsedUrl.length > 1){

			qs = parsedUrl[1];
			paramPairs = qs.split('&');

			for(var i = 0; i < paramPairs.length; i++){
				param = paramPairs[i].split('=');
				if(param.length == 2){
					BookingBuddy.submitHandlers.setOrAddFormValue(param[0], param[1], $search_form);
				}
			}
		}

		return true;
	},

	/**
	   Adds hidden inputs or sets them if they exist already.
	 */
	setOrAddFormValue: function(name, value, $search_form){
		var $input = $search_form.find("input[name='" + name + "']");
		if ($input.length === 0) {
			$j("<input />", { name : name, type: "hidden", value: value }).appendTo($search_form);
		} else {
			$input.val(value);
		}
	},

	/**
	 * Creates a given amount of windows and adds already open windows to the array
	 * This will check if the available windows are closed or open and factor it
	 * into the total number of windows you want
	 * The function saves the stagger in CheckedMultiWindowOptions but only if you
	 * are opening more than one window
	 * @param {int}		- number of window to open including the windows in avail_windows
	 * @param {array} 	- (optional) array of window objects
	 * @returns an array of objects containing a window object and window name
	 */
	openMultiWindow: function(num_windows, avail_windows) {	
		var BSH 			= BookingBuddy.submitHandlers,
			win_top			= BSH.checkedMultiWindowOptions.winTop,
			top_inc			= BSH.checkedMultiWindowOptions.winTopInc,
			win_left		= BSH.checkedMultiWindowOptions.winLeft,	
			left_inc		= BSH.checkedMultiWindowOptions.winLeftInc,
			window_name		= '',
			window_obj		= null,
			windows			= [],
			toolbar			= (($j.browser.msie || BSH.windowOptions.safari) ? 'toolbar=0' : 'toolbar=1'),
			window_options	= "status=1," + toolbar + ",location=1,menubar=0,resizable=1,scrollbars=1,height=525,width=925," +
								"top=" + win_top +  ",left=" + win_left;
		
		if ($j.isArray(avail_windows)) {
			$j.each(avail_windows, function(index, obj){
				if (!obj.win_obj.closed  && num_windows > 0) {
					windows.push({win_obj: obj.win_obj, win_name: obj.win_name});
					num_windows--;
					obj.win_obj.focus();
				}
			});
		}

		// BBUK resets the checkedMultiWindowOptions settings after any multi-window
		// search that doesn't trigger the popup blocker
		var resetAfterOpening = false;
		if (BookingBuddy.affiliateName == 'bookingbuddy_co_uk' && num_windows > 1) {
			resetAfterOpening = true;
		}

		while (0 < num_windows) {
			// new window 
			window_name = "bb_search_" + num_windows + '_' + Number(new Date());
			window_obj = window.open("", window_name, window_options);
		
			if (!window_obj) {
				num_windows--;

				// If a window was blocked, don't reset the window options since we
				// want windows opened from the DHTML layer to be part of the cascade
				if (resetAfterOpening) {
					resetAfterOpening = false;
				}

				continue;
			}
		
			windows.push({win_obj: window_obj, win_name: window_name});
			
			// Adjust window position for the next window
			// If adjusting these values 
			win_top			+= 35;
			win_left		= Math.max(win_left - 35, 336);
			window_options	= "status=1," + toolbar + ",location=1,menubar=0,resizable=1,scrollbars=1,height=525,width=925,"  +
							"top=" + win_top +  ",left=" + win_left;
			num_windows--;
		}

		if (resetAfterOpening) {
			BSH.resetCheckedMultiWindowOptions();
		} else {
			BSH.checkedMultiWindowOptions.winTop = win_top;
			BSH.checkedMultiWindowOptions.winLeft = win_left;
		}

		return windows;	
	},

	/**
	 * Resets the checkedMultiWindowOptions array to its default values
	 */
	resetCheckedMultiWindowOptions: function() {
		var BSH = BookingBuddy.submitHandlers;
		
		BSH.checkedMultiWindowOptions.winTop = 35;
		BSH.checkedMultiWindowOptions.winLeft = screen.width - 925;

		return true;
	},
	
	
	
	/**
	 * @deprecated
	 * BBUK Hotel Step2 Page submit handler for TA Check Rates ads
	 * This handler takes care of validating the Check Rates form and building some additional
	 * information about the checked-off ads to pass to BookingBuddy.UK.submitCheckedSearches
	 *
	 * @param {string} - form name
	 * @param {string} - form target value
	 * @param {object} - event object
	 */
	deprecated_bbukSubmitCheckRates:  function(form, target, e) {
		e.stopPropagation();
		e.preventDefault();
		
		var smartForm = $j.data(form, "smartForm");
		smartForm.submitHandlerData["trigger_id"] = $j(smartForm.submitTrigger).attr("id");
		smartForm.submitHandlerData["submit_type"] = $j(smartForm.submitTrigger).hasClass("runSearch") ? "runSearch" : "default";
		
		if (smartForm.submitTrigger && $j(smartForm.submitTrigger).hasClass("runSearch")) {
			var allCheckboxes = $j(form).find(".adIdCheckBox:visible");
			var checked = $j(form).find(".adIdCheckBox:visible:checked");
			smartForm.submitHandlerData["checked_ads"] = checked.length;
			
			if (checked.length > 0) {
				// Form is valid, submit the search
				$j("#" + smartForm.id + "_aderrors").hide();
				if (!smartForm.settings.debug) {
					BookingBuddy.UK.submitCheckedSearches(smartForm.id, allCheckboxes);
				}
				$j("#dhtml_hotel_search").hide();
			} else {
				smartForm.valid = false;
				$j("#" + smartForm.id + "_aderrors").show();
			}
		}
		
		return false;
	}
};


/**
 * Draw a pretty dropdown that acts like a <select> element from a user's point 
 * of view. Triggers events on the root element for display and user interaction.
 */
(function($) {

	$.extend($.fn, {
		
		/**
		 * Create a dhtmlDropdown object and store it as data of this jQuery element
		 */
		dhtmlDropdownFactory: function(ad_ids, options) {
			if (!this.length) {
				return null;
			}

			var dropdown = $.data(this[0], 'dhtmlDropdown');
			if (dropdown) {
				return dropdown;
			}

			if ($.isNullOrUndefined(ad_ids)) {
				ad_ids = [];
			}

			dropdown = new $.dhtmlDropdown(this[0], ad_ids, options);
			$.data(this[0], 'dhtmlDropdown', dropdown);

			// init has be called after the constructor and after we store the
			// dropdown object on the HTML element since it fires an event
			// and handlers rely on being able to access the dropdown object
			// as data of the HTML element.
			dropdown.init();

			return dropdown;
		}
	});

	/**
	 * Constructor
	 */
	$.dhtmlDropdown = function(elm, ad_ids, options) {
		// create a new "main" element as a child of the element we were given
		// and pretty much ignore the given element from here on out. events
		// will be triggered on the new element but they will also propogate up
		// to the parent so any observers can be set on that element. so there.
		this.element = $('<div></div>').attr('class', $.dhtmlDropdown.constants.CLASS_MAIN).prependTo(elm);
		this.entries_element = null; // element containing dropdown items
		this.current_element = null; // element for ad currently selected

		// use an array to store display order, not an object, since not
		// all browsers iterate through an object in the same order
		this.entry_order = $.merge([], ad_ids);
		this.entry_display = {};

		$.each(this.entry_order, $.proxy(function(index, ad_id) {
			this.entry_display[ad_id] = (null !== BBAd.singleton(ad_id));
		}, this));

		this.is_open = false;
		this.options = $.extend({}, $.dhtmlDropdown.defaults, options);
		this.value = null;
	};

	$.extend($.dhtmlDropdown, {
		
		/**
		 * Events and CSS classes used by this object
		 */
		constants: {
			EVENT_SHOW: 'DHTMLDropdown:show',
			EVENT_HIDE: 'DHTMLDropdown:hide',
			EVENT_READY: 'DHTMLDropdown:ready',
			EVENT_SELECT: 'DHTMLDropdown:select',

			CLASS_MAIN: 'dropdown_display',
			CLASS_BUTTON: 'dropdown_button',
			CLASS_CURRENT: 'dropdown_text',
			CLASS_OPTION_LIST: 'dropdown_options',
			CLASS_OPTION_ENTRY: 'dropdown_option'
		},

		/**
		 * Default values for options that can be specified during object creation
		 */
		defaults: {
			list_position: 'top',
			initialText: 'Search More Sites...',
			tpt_premium: null,
			tpt_basic: null,
			tpt_placeholder: null
		},

		/**
		 * Instance methods
		 */
		prototype: {

			/**
			 * Setup event handlers, set the default ad, and build the option list.
			 *
			 * @return - This object
			 */
			init: function() {
				this.element.click($.proxy(this._handleMainClick, this));
				// build the button
				$('<div></div>').attr('class', $.dhtmlDropdown.constants.CLASS_BUTTON).appendTo(this.element);

				// set first option to contain "search more sites..."
				this._setCurrent(this.options.initialText);

				this._buildEntries();
				this.element.trigger($.dhtmlDropdown.constants.EVENT_READY);
				return this;
			},

			/**
			 * Set or get the id of the currently selected BBAd
			 *
			 * @param {int} - ID of the ad
			 * @return {mixed} - Current dropdown value
			 */
			val: function(ad_id) {
				if (!$.isUndefined(ad_id)) {
					this.value = ad_id;
				}

				return this.value;
			},

			/**
			 * Updates the dropdowns current value, sets it as the current ad
			 * and then triggers the select event.
			 *
			 * @param {int} - ID of the ad
			 */
			updateCurrent: function(ad_id) {
				this.val(ad_id);
				this._setCurrent();
				this.element.trigger($j.dhtmlDropdown.constants.EVENT_SELECT);	
			},

			/**
			 * Display or hide the option list based on if we are already
			 * showing it or not.
			 *
			 * @return - This object
			 */
			toggle: function() {
				if (this.is_open) {
					this._hideEntries();
				} else {
					this._showEntries();
				}

				return this;
			},

			/**
			 * Force the entries in the dropdown to be redrawn (tabbed searches
			 * page on BB mucks with the ads that are in the dropdown so the
			 * list of ads needs to be rebuilt).
			 *
			 * @return - This object
			 */
			redraw: function() {
				this.entries_element.each(function(index, elm) {
					$(elm).unbind();
				});

				this.entries_element.remove();

				// set first option to contain "search more sites..."
				this._setCurrent(this.options.initialText);
				this._buildEntries();
				return this;
			},

			/**
			 * Add an entry to be displayed as part of the option list. Optionally add
			 * it to the begining of the list instead of the end. The list must be redrawn 
			 * before this will have any effect
			 *
			 * @param {int} - ID of the BBAd
			 * @param {bool} - Should it be added to the begining of the dropdown
			 * @return - This object
			 */
			insert: function(ad_id, unshift) {
				this.entry_display[ad_id] = (null !== BBAd.singleton(ad_id));

				if (-1 === $.inArray(ad_id, this.entry_order)) {
					if (!$.isUndefined(unshift) && unshift) {
						this.entry_order.unshift(ad_id);
					} else {
						this.entry_order.push(ad_id);
					}
				}

				return this;
			},

			/**
			 * Set a particular ad not to be displayed as part of the option
			 * list. The list must be redrawn before this will have any effect.
			 *
			 * @return - This object
			 */
			remove: function(ad_id) {
				this.entry_display[ad_id] = false;
				return this;
			},

			/**
			 * resets the dropdown
			 * The list must be redrawn before this will have any effect.
			 */
			reset: function() {
				this.entry_display = {};
				return this;
			},

			/**
			 * Display and position the option list and set a handler to close
			 * it when the user clicks anywhere else on the page.
			 *
			 * @return {undefined}
			 */
			_showEntries: function() {
				this._positionEntries();
				this.entries_element.show().focus();
				this.is_open = true;
				$(document).one('click.dhtmlDropdown', $.proxy(function(e) {
					e.stopPropagation();
					e.preventDefault();
					this._hideEntries();
					return false;
				}, this));

				this.element.trigger($.dhtmlDropdown.constants.EVENT_SHOW);
			},

			/**
			 * Hide the option list and trigger an event
			 *
			 * @return {undefined}
			 */
			_hideEntries: function() {
				this.entries_element.hide().blur();
				this.is_open = false;

				$(document).unbind('click.dhtmlDropdown');
				this.element.trigger($.dhtmlDropdown.constants.EVENT_HIDE);
			},

			/**
			 * Position the option list based on the position of the main
			 * dropdown element and if we are supposed to display above or 
			 * below it.
			 *
			 * @return {jQuery} - Option list element
			 */
			_positionEntries: function() {
				var base_pos = this.element.position();
				this.entries_element.css('left', (base_pos.left + 'px'));
				
				// use .outerHeight() because .height() returns crazy results in IE8
				if ('top' === this.options.list_position) {
					this.entries_element.css('top',	(base_pos.top - this.entries_element.outerHeight()) + 'px');
				} else if ('bottom' === this.options.list_position) {
					this.entries_element.css('top', (base_pos.top + this.element.outerHeight()) + 'px');
				}

				
				
				return this.entries_element;
			},

			/**
			 * Draw the option for the current value of the dropdown (placeholder
			 * entry if there is no value set) and display it.
			 *
			 * @return {undefined}
			 */
			_setCurrent: function(optionText) {
				var ad_id = this.val();
				var current = null;

				if ($.isNullOrUndefined(ad_id)) {
					// sets the display text ie. "search more sites... " (if passed in)
					if (!$.isNullOrUndefined(optionText)) {
						current = optionText;	
					} else {
						current = this._getAdElm();
					}
				} else {
					current = this._getAdElm(BBAd.singleton(ad_id));
				}
				
				if (!this.current_element) {
					this.current_element = $('<div></div>').attr({
						'class': $.dhtmlDropdown.constants.CLASS_CURRENT
					});

					this.element.prepend(this.current_element);
				}
				
				this.current_element.html(current);
			},

			/**
			 * Construct the option list element, each option element, and
			 * any needed placeholder elements. Stores a reference to the
			 * jQuery element created.
			 *
			 * This will automatically sort the list of ads based on tier
			 * (i.e. premium vs basic)
			 *
			 * @return {undefined}
			 */
			_buildEntries: function() {
				var entry_list = $('<div></div>').hide().attr({
					'class': $.dhtmlDropdown.constants.CLASS_OPTION_LIST
				});

				var self = this,
					premium = [],
					basic = [],
					ad = null,
					obj = null;

				// Iterate over all of the ads and sort them by type
				// (i.e. basic vs premium)
				$.each(this.entry_order, function(index, ad_id) {
					ad = BBAd.singleton(ad_id);

					// ad has been removed or is invalid, skip it
					if (!self.entry_display[ad_id] || !ad.getMetaData('display_name')) {
						return true;
					}

					// build the ad object and add it to the propper list
					obj = {
						html: self._getEntryElm(ad),
						display_name: ad.getMetaData('display_name'),
						id: ad_id
					};

					if (self._isPremiumAd(ad)) {
						premium.push(obj);
					} else {
						basic.push(obj);
					}

					return true;
				});

				if (premium.length > 0) {
					// spacer
					this._getEntryElm().appendTo(entry_list);

					// append premium ads to the list
					$.each(premium, function(index, obj) {
						if (obj.html) {
							obj.html.appendTo(entry_list);
						}
					});

					// spacer
					this._getEntryElm().appendTo(entry_list);
				}

				// Append the basic ads to the list
				$.each(basic, function(index, obj) {
					if (obj.html) {
						obj.html.appendTo(entry_list);
					}
				});

				this.entries_element = entry_list;
				this.element.append(entry_list);
			},

			/**
			 * Get an element for this ad wrapped in dropdown option markup
			 *
			 * @param {BBAd}
			 * @return {jQuery}
			 */
			_getEntryElm: function(ad) {
				var ad_id = null;
				if (!$.isUndefined(ad)) {
					ad_id = ad.getID();
				}

				// Wrap each ad div in an option div and attach a click handler
				// Divider or placeholder options set the value of the dropdown to null
				return $('<div></div>').attr({
					'class': $.dhtmlDropdown.constants.CLASS_OPTION_ENTRY
				}).bind('click', {'id': ad_id}, $.proxy(this._handleEntryClick, this)).append(this._getAdElm(ad));
			},

			/**
			 * Get an element based on the template for this particular ad
			 *
			 * @param {BBAd}
			 * @return {jQuery}
			 */
			_getAdElm: function(ad) {
				var elm = null;

				if ($.isUndefined(ad)) {
					elm = $j.tmpl(this.options.tpt_placeholder, {});
				} else {
					var tpt = this._isPremiumAd(ad) ? this.options.tpt_premium : this.options.tpt_basic;

					var css = ' height: 16px;';

					// This should only happen on the tabbed browsing page - 
					// If we have an ad on the dropdown that has a logo but isn't a dropdown ad 
					// - it's a css sprite logo containing a bigger and smaller version of the ad and we have to adjust its css
					if (ad.getMetaData('ad_tier.name') && ad.getMetaData('ad_tier.name').indexOf('dropdown') == -1) {
						css = ' height: 31px; background-position: 0 -65px;';
					// BBS-2441: TODO: the above code if block is for compatibility
					// with the old tab browsing code, while the below is for the
					// new tab browsing code. When the old code goes away, remove
					// the above if block
					} else if (ad.getMetaData('tier') && ad.getMetaData('tier').indexOf('dropdown') == -1) {
						css = ' height: 31px; background-position: 0 -65px;';
					}

					elm = $j.tmpl(tpt,{ 
						'ad_id': ad.getID(),
						'label': ad.getMetaData('display_name'),
						'logo_url': ad.getMetaData('logo'),
						'css'  : css
					});
				}

				return elm;
			},

			/**
			 * Is this a premium ad (does it have a logo)?
			 *
			 * @param {BBAd}
			 * @return {bool}
			 */
			_isPremiumAd: function(ad) {
				return !$.emptyString(ad.getMetaData('logo')); 
			},

			/**
			 * Click handler for main dropdown element
			 *
			 * @param {event}
			 * @return {bool}
			 */
			_handleMainClick: function(e) {
				e.preventDefault();
				e.stopPropagation();
				this.toggle();
				return false;
			},

			/**
			 * Click handler for individual dropdown options. Expects to be passed
			 * the ID of the ad associated with the dropdown option as event data.
			 *
			 * @param {event}
			 * @return {bool}
			 */
			_handleEntryClick: function(e) {
				e.preventDefault();
				e.stopPropagation();
				this.toggle(); // IMPORTANT! This needs to be called before .trigger()
				this.updateCurrent(e.data.id);
				return false;
			}
		}
	});
})(jQuery);


/**
 * The DHTML dropdown code above tries not to make any assumptions. However,
 * by making some assumptions we can eliminate a lot of code that would otherwise
 * be duplicated. The code below contains methods that may or may not be useful
 * for your particular site.
 */
(function($) {

	$.extend($.dhtmlDropdown, {
		utils: {

			/**
			 * Set the name and id of the dropdown checkbox on user
			 * selection.
			 */
			revSearch: function(form, dropdown) {
				var checkbox = $(form).find('.dropdown_checkbox .BBInputCheckBox');
				var label = $(form).find('.dropdown_checkbox label');

				$(dropdown).bind($.dhtmlDropdown.constants.EVENT_SELECT + ' ' + $.dhtmlDropdown.constants.EVENT_READY, function(e) {
					var dropdown_val = $(dropdown).data('dhtmlDropdown').val();
					if (null === dropdown_val) {
						checkbox.removeAttr('checked');
						return true;
					}

					var ad = BBAd.singleton(dropdown_val);
					if (null === ad || !checkbox.length) {
						return true;
					}

					checkbox.attr({
						'id': 'check_' + ad.getID(),
						'name': ad.getMetaData('tracking_name')
					});

					// only check the box if this is an event from actual user interaction
					if (e.type === $.dhtmlDropdown.constants.EVENT_SELECT) {
						checkbox.attr('checked', 'checked').change();
					}

					label.attr('for', 'check_' + ad.getID());
					return true;
				});
			},

			/**
			 * Make the dropdown button act like a submit button and
			 * set the name and id on user selection. Short circuit the
			 * user event when the dropdown has no value.
			 */
			oneSearch: function(form, dropdown) {
				var dropdown_button = $(dropdown).find('.BBInputButton');
				dropdown_button.addClass('submitTrigger');

				$(dropdown_button).click(function(e) {
					if (!$(dropdown).data('dhtmlDropdown').val()) {
						e.stopPropagation();
						e.preventDefault();
						return false;
					}

					return true;
				});

				$(dropdown).bind($.dhtmlDropdown.constants.EVENT_SELECT, function(e) {
					var ad = BBAd.singleton($(dropdown).data('dhtmlDropdown').val());
					if (null === ad || !dropdown_button.length) {
						return false;
					}
					
					dropdown_button.attr({
						'id': 'bb_dropdown_' + ad.getID(),
						'name': ad.getMetaData('tracking_name')
					});

					dropdown_button.click();
					return false;
				});
			},

			/**
			 * Make the dropdown button update the submit button for the
			 * dropdown and set the name and id on user selection. Short
			 * circuit the user event when the dropdown has no value.
			 */
			classicSearch: function(form, dropdown) {
				var dropdown_button = $(dropdown).find('.BBInputButton');

				var submitButton = $(dropdown).parent().find('.BBInputButton.runSearch');

				$(dropdown).bind($.dhtmlDropdown.constants.EVENT_SELECT, function(e) {
					var ad = BBAd.singleton($(dropdown).data('dhtmlDropdown').val());
					if (null === ad || !dropdown_button.length) {
						return false;
					}

					submitButton.attr({
						'id': 'bb_dropdown_submit_' + ad.getID(),
						'name': ad.getMetaData('tracking_name')
					});

					return false;
				});
			},

			/**
			 * Given the dropdown root jQuery element, return the entry
			 * with text matching the specified value (case insensitive).
			 *
			 * @param {jQuery} - The dropdown root element
			 * @param {String} - Search term
			 */
			searchOptions: function(dropdown, value) {
				var elm = null;
				var regex = new RegExp(value, 'i');
				var option_class = '.' +  $.dhtmlDropdown.constants.CLASS_OPTION_ENTRY;

				$(dropdown).find(option_class).filter(function(index) {
					if (regex.test($(this).text())) {
						elm = $(this);
						return true;
					}

					return false;
				});

				return elm;
			}
		}
	});

})(jQuery);


/**
 * Location Suggest
 */
var LocationSuggest = Object.subClass({
	/**
	 * Input to attach LocationSuggest to
	 * @type jQuery
	 */
	input: null,

	/**
	 * Hidden input object to populate a value into
	 * @type jQuery
	 */
	hiddenTarget: null,

	/**
	 * Name of the service to be used to fetch suggestions
	 * @type String
	 */
	suggestServiceName: "LocationSuggest.suggest",

	/**
	 * Name of the service to be used to validate
	 * @type String
	 */
	validateServiceName: "LocationSuggest.validate",

	/**
	 *	Amount of time in ms to wait before firing off a location suggest request
	 *	@type int
	 */
	suggestTimeout: 200,

	/**
	 * Timer ID of the typing timout
	 * @type int
	 */
	suggestTimeoutID: null,

	/**
	 * Array of suggestion types associated with the currently targeted input
	 * @type array
	 */
	suggestionTypes: null,

	/**
	 *	The maximum number of suggestions to show.  If -1 all suggestions returned will be shown
	 *	@type int
	 */
	maxSuggestions: 15,

	/**
	 *	Default text that may appear in the input field and should not be validated
	 *	@type int
	 */
	defaultText: null,

	/**
	 * Handle for the XmlHttpRequest object
	 * @type XmlHttpRequest
	 */
	ajaxTransport: null,

	/**
	 * Array of the current suggestions used for caching things
	 * @type array
	 */
	currentSuggestions: null,

	/**
	 * Array of known valid Airport locations.  Used for validation
	 * @type Object
	 */
	airportValidLocations: null,

	/**
	 * Array of known valid City locations.  Used for validation
	 * @type Object
	 */
	cityValidLocations: null,

	/**
	 *	Contains a list of country codes to limit what is returned from LS.  ie Only display locations in the US
	 *	@type array
	 */
	country: null,

	/**
	 * Extra data to be sent with the location suggest request.
	 *
	 * @type Object
	 */
	extra_data: null,

	/**
	 * Whether or not validation should even occur at all.
	 * @var Boolean
	 */
	shouldValidate: true,

	/**
	 * Constructor.
	 *
	 * @param String		input_id
	 * @param Array			types
	 * @param String		hidden_target_id
	 * @param Object		extra_data				Optional. Any extra data to
	 * 		be sent along.
	 * @returns LocationSuggest
	 */
	init: function(input_id, types, hidden_target_id, extra_data) {
		// initialize these here since they'll be shared among all instances
		// if they are initialized when declared above
		this.airportValidLocations = {};
		this.cityValidLocations = {};
		this.extra_data = $j.extend({}, extra_data);

		//Initialize the inputs and suggestion types
		this.suggestionTypes = types;
		this.input = $j("#" + input_id);

		try {
			if(this.suggestionTypes.length < 1) {
				throw {
					name: 'SuggestionTypeRequired',
					message:	"You must specify at least one type suggestion type"
				};
			}

			if(this.input.length != 1) {
				throw {
					name: 'InvalidInput',
					message: "Invalid input given for location suggest: " + input_id
				};
			}

		} catch (e) {
			BBDebug.log(e.name  +  ' EXCEPTION: ' + e.message);
			return false;
		}


		this.hiddenTarget = $j.emptyString(hidden_target_id) ?  null : $j("#" + hidden_target_id).length > 0 ?  $j("#" + hidden_target_id): null;

		//Shut off browser auto-complete. This is non-valid HTML, but it's well supported
		this.input.attr('autocomplete', 'off');
		this.input.bind('keyup', {obj: this}, this.keyUpHandler);
		this.input.bind('blur', {obj: this}, this.handleBlur);

		// Boolean flags for determining what types of suggestions this LS object returns
		this.airportsOnly = $j.inArray("airport", this.suggestionTypes) !== -1 && $j.inArray("city", this.suggestionTypes) === -1;
		this.citiesOnly = $j.inArray("city", this.suggestionTypes) !== -1 && $j.inArray("airport", this.suggestionTypes) === -1;
		this.airportsOrCities = $j.inArray("airport", this.suggestionTypes) > -1 && $j.inArray("city", this.suggestionTypes) > -1;

		return this;
	},

	/**
	 *	Sets the maximum number of suggestions to be shown
	 *
	 *	@param max int Number of suggestions to show
	 */
	setMaxSuggestions: function(max) {
		this.maxSuggestions = parseInt(max, 10);
	},

	/**
	 * Builds a list of suggestion based off of the internal cache or requests a new set from the server
	 *
	 */
	updateSuggestions: function() {
		var inputVal = this.input.val().toLowerCase();
		//Don't want to bother with suggestions if the input is really short
		if($j.trim(inputVal).length < 3) {
			this.closeSuggest();
			return;
		}

		if(this.suggestTimeoutID !== null) {
			clearTimeout(this.suggestTimeoutID);
		}

		var __callback = function(data) {
			try {
				if(data) {
					this.showSuggestions(data);
				}
				else {
					this.closeSuggest();
					this.currentSuggestions = null;
				}
			} catch (e) {
				this.closeSuggest();
				this.currentSuggestions = null;
				return;
			}
		};

		var __updateSuggestionsHelper = function() {
			var query = this.input.val();
			this.suggestTimeoutID = null;

			var args = $j.extend( {input: query, types: this.suggestionTypes, limit: this.maxSuggestions}, this.extra_data);

			if(this.country !== null) {
				args.country = this.country;
			}

			this.ajaxTransport = $j.bbget(this.suggestServiceName, args, jQuery.proxy(__callback, this));
		};

		this.suggestTimeoutID = setTimeout(jQuery.proxy(__updateSuggestionsHelper, this), this.suggestTimeout);
	},

	/**
	 * Maintains the list of locations that are known to be valid
	 *
	 * @param String loc Valid location name
	 * @param object data Data to store for the location
	 */
	addValidLocation: function(loc, data) {
		// verify data is set since this is used to confirm if
		// the location has already been validated
		if (!data) {
			data = {};
		}

		if ((/\(\w{3}\)/).test(loc)) {
			if (typeof this.airportValidLocations[loc] == "undefined") {
				this.airportValidLocations[loc] = data;
			}
		} else {
			if (typeof this.cityValidLocations[loc] == "undefined") {
				this.cityValidLocations[loc] = data;
			}
		}
	},

	/**
	 * Return true if this is a location that has been checked and
	 * found valid and is a supported type, false otherwise.
	 *
	 * @param String loc
	 * @param String type
	 * @return mixed
	 */
	isValidLocation: function(loc, type) {
		if (type === 'city') {
			return (typeof this.cityValidLocations[loc] != 'undefined') ? this.cityValidLocations[loc] : false;
		} else if (type === 'airport') {
			return (typeof this.airportValidLocations[loc] != 'undefined') ? this.airportValidLocations[loc] : false;
		}

		return false;
	},

	/**
	 * Triggers validation of the input.  Checks the list of valid locations first, if not present
	 * an AJAX request will be made to validate it server side. Two events can be triggered on the
	 * input, LocationSuggest:valid and LocationSuggest:invalid.  These can be bound to attach
	 * custom behavior.
	 *
	 * @return void
	 */
	validate: function( eventSuffix ) {
		if(!this.shouldValidate) { return; }

		var _validEvent = "LocationSuggest:valid";
		var _invalidEvent = "LocationSuggest:invalid";
		if(!$j.emptyString( eventSuffix)) {
			_validEvent = _validEvent + ":" + eventSuffix;
			_invalidEvent = _invalidEvent + ":" + eventSuffix;
		}

		if(this.input.hasClass("LSNoValidate")) {
			this.input.addClass("LSValid");
			this.input.removeClass("LSInvalid");
			this.input.trigger(_validEvent);
			return;
		}

		var value = this.input.val();

		if($j.emptyString(value) || value.length < 2) {
			this.input.addClass("LSInvalid");
			this.input.removeClass("LSValid");
			this.input.trigger(_invalidEvent);
			return;
		}

		if(!$j.emptyString(this.defaultText) && this.defaultText === value) {
			return;
		}

		var valid = false;

		// check for locationSuggestion types and if we already have this value
		if (this.input.hasClass('airport') && this.input.hasClass('city')) {
			valid = (this.isValidLocation(value, 'airport') || this.isValidLocation(value, 'city'));
		} else if (this.input.hasClass('airport')) {
			valid = this.isValidLocation(value, 'airport');
		} else if (this.input.hasClass('city')) {
			valid = this.isValidLocation(value, 'city');
		}

		// Location has already been validated
		if (valid) {
			this.input.removeClass("LSInvalid");
			this.input.addClass("LSValid");

			// store the country of the current valid city on the input
			if (valid.country) {
				this.input.data('country', valid.country);
			}
			this.input.trigger(_validEvent);
			return;
		}

		//This location isn't in our list, so we're going to have to validate via AJAX
		var args = $j.extend({input: value, types: this.suggestionTypes}, this.extra_data);

		if (this.country) {
			args.country = this.country;
		}

		var cache_key = this.validateServiceName + JSON.stringify(args);

		var validateCallback = function(data) {
			if ($j.isNullOrUndefined(data)) {
				// No valid data response from the call back, log the error
				BBDebug.error("LocationSuggest.prototype.validate: no valid data response from ajax call to LocationSuggest.validate service");
			} else if (data.valid == 1) {
				this.addValidLocation(data.value, { country: data.country });
				this.input.val(data.value);
				this.input.data('country', data.country);
				this.input.removeClass("LSInvalid");
				this.input.removeClass("LSUnsupportedLocation");
				this.input.addClass("LSValid");
				this.input.trigger(_validEvent);
			} else {
				this.input.addClass("LSInvalid");
				this.input.removeClass("LSValid");

				var unsupported = false;
				for (index in data.suggestions) {
					if (data.suggestions[index].supported === true) {
						this.addValidLocation(data.suggestions[index].value,
																	{ country: data.suggestions[index].country });
					} else {
						unsupported = true;
					}
				}

				// clear out possible old unsupported searches
				this.input.removeClass("LSUnsupportedLocation");
				if (('supported' in data && !data.supported) || unsupported) {
					this.input.addClass("LSUnsupportedLocation");
				}

				this.input.trigger(_invalidEvent, [data.suggestions]);
			}
		};

		// Add to the list of callbacks
		LocationSuggest.ValidationCache.addCallback(cache_key, $j.proxy(validateCallback, this));

		if(LocationSuggest.ValidationCache.cache[cache_key]) {
			// If we already have the result, just run it
			LocationSuggest.ValidationCache.runCallbacks(cache_key);
		} else if(!LocationSuggest.ValidationCache.fetching[cache_key]) {
			// Otherwise if there isn't already a request to get the results, fetch it
			LocationSuggest.ValidationCache.fetching[cache_key] = true;
			this.ajaxTransport = $j.bbget(this.validateServiceName, args, function(response) {
				LocationSuggest.ValidationCache.addResult(cache_key, response);
			});
		}
	},

	/**
	 * Common method for cleaning airport specific information from a suggestion
	 *
	 * @param suggestion
	 */
	convertAirportToCity: function( suggestion ) {
		//Remove the airport code if it exists from the arrival city
		var _val = suggestion;
		var parenIndex = suggestion.indexOf('(');
		if (parenIndex != -1) {
			//_val = suggestion.substring(0, parenIndex).strip();
			_val = $j.trim(suggestion.substring(0, parenIndex));
		}

		// Remove  - 'All Airports' if its exists from the arrival city
		var allAirportsIndex = suggestion.indexOf('- All Airports');
		if (allAirportsIndex != -1){
			//_val = suggestion.substring(0, allAirportsIndex).strip();
			_val = $j.trim(suggestion.substring(0, allAirportsIndex));
		}
		return _val;
	},

	/**
	 * Common method for determining if a suggestion is airport specific
	 *
	 * @param suggestion
	 */
	suggestionIsAirport: function( suggestion ) {
		var parenIndex = suggestion.indexOf('(');
		var allAirportsIndex = suggestion.indexOf('- All Airports');

		return parenIndex > 0 || allAirportsIndex  > 0;
	},

	/**
	 * Constructs a list of suggestions based on the array of strings given.
	 *
	 * @param suggestions array Array of strings used for the suggestions
	 */
	showSuggestions: function(suggestions) {
		this.closeSuggest();
		if(suggestions.length === 0) { return; }
		//Create new UL element
		var pos = {};

		pos = $j(this.input).offset();
		var ul = $j("<ul />", {
			id: 'BBLocationSuggest',
			css: { position: 'absolute', left: pos.left + 'px', top: (pos.top + $j(this.input).outerHeight()) + 'px'}
		});
		var inputValue = this.input.val();

		$j.each(suggestions, $j.proxy(function(index, item) {
			if((this.maxSuggestions != -1) && (index >= this.maxSuggestions)) { return; }

			var display, value;
			if(typeof item == 'string') {
				display = item;
				value = item;
			} else {
				display = item.display;
				value = item.value;
			}

			display = this.boldSuggestionText(display, inputValue);

			var li = $j('<li></li>');
			li.append(display);
			li.attr('suggestValue', value);

			li.bind("mouseover", $j.proxy( function() { this.setSelectedIndex(index); }, this));

			ul.append(li);

			//Add this suggestion to the list of valid locations
			this.addValidLocation(value, { country: item.country });
		}, this));

		$j(document.body).append(ul);
		BookingBuddy.toggleCovered(ul, 'hidden');

		this.currentSuggestions = suggestions;
	},

	/*
	 * Closes the suggestion div if it is open
	 */
	closeSuggest: function() {
		var list = $j('#BBLocationSuggest');
		if(list.length !== 0) {
			list.remove();
			BookingBuddy.toggleCovered(list.get(0), '');
		}
	},

	/*
	 *	Places bold tags inside suggestion around matches to input and returns the resulting string
	 *
	 *	ie boldSuggestionText('Boston, MA (BOS)', 'bos') will return '<b>Bos</b>ton, MA (<b>BOS</b>)'
	 *
	 *	@param suggestion string Text to do replacement on
	 *	@param input string string to match against
	 *	@return string
	 */
	boldSuggestionText: function(suggestion, value) {
		value = value.replace('(', '\\(');
		value = value.replace(')', '\\)');

		var reg = new RegExp(value, 'gi');

		var matches = suggestion.match(reg);

		if(matches) {
			$j.each(matches, function(index, matchText){
				suggestion = suggestion.replace(matchText, '<b>' + matchText + '</b>');
			});
		}

		return suggestion;
	},

	/**
	 *	Sets the selected item in the suggestions to selIndex.  Returns true if successful, false otherwise
	 *
	 *	@param selIndex int Index of the element to select
	 *	@return bool
	 */
	setSelectedIndex: function(selIndex) {
		var items = $j('#BBLocationSuggest li');
		var selSet = false;

		if (selIndex < items.length) {
			items.removeClass('selected');
			$j('#BBLocationSuggest li:eq(' + selIndex + ')').addClass('selected');
			return true;
		}

		return false;
	},

	/**
	 *	Sets the current targets value to that of the selected option
	 */
	useSelected: function() {
		var selIndex = this.getSelectedIndex();
		if ((selIndex >= 0) && (selIndex < this.currentSuggestions.length)) {
			// if we have a separate field for the value, we display the display
			// and store the value
			if (this.hiddenTarget) {
				this.input.val(this.currentSuggestions[selIndex].display);
				this.hiddenTarget.val(this.currentSuggestions[selIndex].value);
			} else {
				this.input.val(this.currentSuggestions[selIndex].value);
			}

			this.closeSuggest();

			this.input.trigger("LocationSuggest:selection");
			this.validate();

			return true;
		}

		return false;
	},

	/**
	 *	Method to manually set a selected value to a location suggest field, primarily to be used for testing
	 */
	manualSetSelected: function( value ) {
		// if we have a separate field for the value, we display the display
		// and store the value
		if (this.hiddenTarget) {
			this.input.val(value);
			this.hiddenTarget.val(value);
		} else {
			this.input.val(value);
		}

		this.input.trigger("LocationSuggest:selection");
		this.validate();
	},

	/**
	 * Deals with selecting the next or previous option in the location suggest list
	 *
	 * @param event object Event object generated by the browser
	 */
	handleArrowKey: function (direction) {
		//Get Selected Index
		var selectedIndex = this.getSelectedIndex();
		var newSelectedIndex;

		switch(direction) {
		case 'down':
			newSelectedIndex = selectedIndex + 1;
			break;
		case 'up':
			newSelectedIndex = selectedIndex - 1;
			break;
		default:
			return;
		}

		this.setSelectedIndex(newSelectedIndex);
	},

	/**
	 *	Returns the index of the selected element or -1 if no element is selected
	 *
	 *	@return int
	 */
	getSelectedIndex: function() {
		var items = $j('#BBLocationSuggest li');
		var selIndex = -1;

		$j.each(items, function(index, item) {
			if($j(items.get(index)).hasClass('selected')) {
				selIndex = index;
			}
		});

		return selIndex;
	},

	/**
	 * Event handler for blur events on targeted fields
	 */
	handleBlur: function(event) {
		if(typeof event.data.obj == 'undefined') { return; }
		obj = event.data.obj;

		//Kill the timeout so we don't fire off a request after they've already left the field
		if(obj.suggestTimeoutID !== null) {
			clearTimeout(obj.suggestTimeoutID);
			obj.suggestTimeoutID = null;
		}

		//Kill the existing transport if it exists
		if(obj.ajaxTransport) {
			obj.ajaxTransport = null;
		}

		var selIndex = obj.getSelectedIndex();
		if(selIndex == -1) {
			if(!obj.setSelectedIndex(0)) {
				obj.validate();
				return;
			}
		}

		obj.useSelected();
	},

	/**
	 * Deals with the keyup event on targeted fields.
	 *
	 * @param event object Event object (Generated by the browser)
	 */
	keyUpHandler: function(event) {
		if (typeof event.data.obj == "undefined") {
			return false;
		}

		var obj = event.data.obj;

		switch(event.keyCode) {
		case 9:	//Tab key, will be handled seperately by the onBlur handler
		case 37: //Left arrow - Don't care about this
		case 39: //Right arrow - Don't care about this
		case 27: // ESC key
			return false;
		case 13: //Enter key
			return obj.useSelected();
		case 38: //Up arrow
			return obj.handleArrowKey('up');
		case 40: //Down arrow
			return obj.handleArrowKey('down');
		default:
			return obj.updateSuggestions();
		}
	}
});

/**
 * Validation can be cached globally so lets do that instead of making a request to the server each
 * and every time.
 *
 * @author Duc Tri Le
 * @version 0.1
 */
LocationSuggest.ValidationCache = {
	/**
	 * @type {Object<string, object>}	The cache.
	 */
	cache: {},

	/**
	 * @type {Object<string, Function>}		The list of functions to call once the results has been
	 * 		fetched.
	 */
	callbacks: {},

	/**
	 * @type {Object<string, boolean>}		Whether or not a request is already running to fetch the
	 * 		results.
	 */
	fetching: {},

	/**
	 * Run the provided callback once the provided search returned something.
	 *
	 * @param {string}		key		The cache key.
	 * @param {Function}	fn		The callback function.
	 * @return {void}
	 */
	addCallback: function(key, fn) {
		// Just run the callback if we already have the result
		if(LocationSuggest.ValidationCache.cache[key]) {
			fn(LocationSuggest.ValidationCache.cache[key]);
			return;
		}

		// Otherwise, add it to the stack
		if(!LocationSuggest.ValidationCache.callbacks[key]) {
			LocationSuggest.ValidationCache.callbacks[key] = [];
		}

		LocationSuggest.ValidationCache.callbacks[key].push(fn);
	},

	/**
	 * Add a result set and run the callbacks.
	 *
	 * @param {string}		key			The cache key.
	 * @param {Object}		result		The response from the server.
	 * @return {void}
	 */
	addResult: function(key, result) {
		LocationSuggest.ValidationCache.cache[key] = result;
		LocationSuggest.ValidationCache.runCallbacks(key);
	},

	/**
	 * Run all the callbacks.
	 *
	 * @param {string}		key		The cache key.
	 * @return {void}
	 */
	runCallbacks: function(key) {
		if(LocationSuggest.ValidationCache.cache[key] && LocationSuggest.ValidationCache.callbacks[key]) {
			var fn;
			while(LocationSuggest.ValidationCache.callbacks[key].length) {
				fn = LocationSuggest.ValidationCache.callbacks[key].shift();
				fn(LocationSuggest.ValidationCache.cache[key]);
			}
		}
	}
};

/**
 * This class deals with displaying a dropdown with suggested locations for an invalid location input
 * 
 * @param String input_id ID of the input to monitor for invalid locations
 * @return InvalidLocationDropdown
 */
var AircodePopup = {	
	input: null,

	popup: null,

	aircodeUrl: "/aircodes.php",

	show: function( inputElem  ) {
		this.input = inputElem;
		var _options =  "scrollbars=yes,width=440,height=320,resizable=yes";
		this.popup = window.open(this.aircodeUrl, "aircodes", _options);
	},

	populateInput: function(code) {
		BBDebug.log("Setting LS field value from aircodes popup: " + code);
		this.input.val(code);
		this.input.blur();
		this.input.trigger("LocationSuggest:selection");
		if (this.popup) {
			this.popup.close();
		}
		this.popup = null;
	}
};

/**
 * Displays a dhtml layer listing all available holiday destination locations.
 * Selecting a destination from the listing updates the parent and child
 * locations in the form.
 */
var HolidayPopup = {   
	locationInput: null,

	popup: null,

	show: function(inputId) {
		var self = this;

		this.locationInput = $j('#' + inputId);

		$j.asyncLoad('#dhtml_holiday_options', 'holiday_options', function(elm) {
			if (self.popup) {
				self.popup.show();
				return;
			}

			self.popup = new DHTMLPopup($j(elm).attr('id'), true);
			self.popup.show();

			var $wrapper = $j(elm);

			$wrapper.find("a.code_link").click( function(e) {
				e.preventDefault();
				var locationValue = $j(this).find("span.location_code").text();
				self.populateInput(locationValue);
			});

			$wrapper.find(".location_nav a").click( function(e) {
				e.preventDefault();

				$content = $wrapper.find('.location_content');
				var $target = $wrapper.find('a[name="' + $j(this).text() + '"]');

				var scrollTop = $content.scrollTop();
				scrollTop += $target.position().top;

				$content.animate({scrollTop: scrollTop}, 700);
			});
		});

	},

	populateInput: function(locationValue) {
		BBDebug.log("Setting LS field value from holiday codes popup: " + locationValue);

		this.locationInput.val(locationValue);
		this.locationInput.blur();

		if (this.popup) {
			this.popup.hide();
		}
	}
};


$j(document).ready(function() {
	$j(".air_codes_link").click( function(e) {
		var _elem_id = $j(this).attr("id").replace("_aircodes", "");
		if ($j("#" + _elem_id).length > 0) {
			AircodePopup.show($j("#" + _elem_id));
		} else {
			BBDebug.log("Unable to find input for air code link: " + _elem_id);
		}
		$j(this).blur();
	});

	$j(".holiday_codes_link").click( function(e) {
		e.stopPropagation();
		e.preventDefault();

		var elemId = $j(this).attr("id").replace("_codes", "");
		if ($j('#' + elemId)[0]) {
			HolidayPopup.show(elemId);
		} else {
			BBDebug.log("Unable to find input for air code link: " + elemId);
		}
		$j(this).blur();
	});
});

function InvalidLocationDropdown(input_id) {
	this.input = $j("#" + input_id);
	
	if (this.input.length != 1) {
		throw "Invalid input specificed for InvalidLocationDropdown: " + input_id;
	}
	
	var objectHandle = this;
	
	this.input.bind("LocationSuggest:invalid", $j.proxy( function(event, suggestions) { 
		BBDebug.log("InvalidLocationDropdown caught LocationSuggest:invalid  on LocationSuggest:invalid "+ event.currentTarget.id);
		this.invalidLocationEventHandler(suggestions); 
	}, this));

	this.input.bind("LocationSuggest:valid", $j.proxy( function(event) {this.removeSelect(); } , this));
}

$j.extend(InvalidLocationDropdown, {  
	
	prototype: {
		/**
		 * Handle to the input this object is supporting
		 * @var jQuery
		 */
		input: null,
		
		/**
		 * Handle for the select box created by this class
		 * @var jQuery
		 */
		select: null,
		
		
		aircodeUrl: "/aircodes.php",
		aircodePopup: null,
		/**
		 * Event listener to handle an input being marked invalid
		 * @param Event event Event object which should contain a reference to the InvalidLocationDropdown object
		 * @param Array suggestions Array of objects containing the suggested locations
		 * @return void
		 */
		invalidLocationEventHandler: function(suggestions) {
	  		// get the select's diplay property as it was prior to the event call, so when we re-create the select we can set it propperly
			// default to 'none'
			var dropdown_display = (!$j.isNullOrUndefined(this.select) && this.select.css('display') !== null) ? this.select.css('display') : 'none';

			this.removeSelect();
			//No suggestions? No dropdown!
			if ($j.isNullOrUndefined(suggestions) || suggestions.length === 0) {
				return;
			}

			this.select = $j("<select />", {
				css: {
					display: dropdown_display
				},
				change: $j.proxy( function() { this.invalidLocationDropdownChangeHandler(); } , this) 
			});
			this.select.addClass("validation_select");
			
			//Tack on some extra options
			this.select.append($j("<option/>", { value: "", text: "Suggested locations" }));
			
			$j.each(suggestions, $j.proxy(function(index, suggestion) {
				if (suggestion.supported) {
					var option = $j("<option/>", {
						value: suggestion.value,
						text: suggestion.display
					});
					this.select.append(option);
				}
			}, this));
			
			this.select.append($j("<option/>", { value: "", text: "----------------" }));
			this.select.append($j("<option/>", { value: "aircode_link", text: BookingBuddy.Strings.LS.ViewAirportList }));
						
			this.input.after(this.select);
			
		},
		
		/**
		 * Event handler for when the value of the dropdown is changed.  Sets the value of the associated
		 * input and triggers a blur on the input to force LocationSuggest to notice the change
		 * 
		 * @param Event event Event object which should contain a reference to the InvalidLocationDropdown object
		 * @return void
		 */
		invalidLocationDropdownChangeHandler: function() {
			var selected_value = this.select.val();
			
			if(!$j.emptyString(selected_value)) {
				if (selected_value === "aircode_link") {
					this.showAircodes();
				} else {
					this.input.val(selected_value);
					this.input.blur();
				}
			}
		},
		
		showAircodes: function() {
			AircodePopup.show(this.input);
			if ($j.isNullOrUndefined(AircodePopup.popup)) {
				BookingBuddy.Search.showBlockedPopUpMessage();
			}
		},
		
		/**
		 * Closes the dropdown. Used when adding a new dropdown or when the location is marked valid
		 * 
		 * @return void
		 */
		removeSelect: function() {
			if (!$j.isNullOrUndefined(this.select)) {
				this.select.remove();
				this.select = null;
			}
		}
	}
});




/* SiteCatalyst code version: H.9.
Copyright 1997-2007 Omniture, Inc. More info available at
http://www.omniture.com */
/* Specify the Report Suite ID(s) to track here */
var s=s_gi(s_account);

var domainName = document.domain.match(/\.([a-z0-9-]+\.[a-z0-9-]+)$/i);
if (domainName && (domainName[1].length < 8)) {
	domainName = document.domain.match(/\.([a-z0-9-]+\.[a-z0-9-]+\.[a-z0-9-]+)$/i);
}
domainName = domainName ? domainName[1] : 'bookingbuddy.com';

/************************** CONFIG SECTION **************************/
/* You may add or alter any code config here. */
/* Link Tracking Config */
s.trackDownloadLinks=false;
s.trackExternalLinks=true;
s.trackInlineStats=true;
s.linkDownloadFileTypes="exe,zip,wav,mp3,mov,mpg,avi,wmv,doc,pdf,xls";
s.linkInternalFilters="javascript:,slimg.com," + domainName;
s.linkLeaveQueryString=false;
s.linkTrackVars="None";
s.linkTrackEvents="None";

/* WARNING: Changing any of the below variables will cause drastic
changes to how your visitor data is collected.  Changes should only be
made when instructed to do so by your account manager.*/
s.trackingServer=s_trackingServer;
s.dc=112;
s.vmk="485017AC";

/* E-commerce Config */
s.currencyCode="USD";
s.eVarCFG="";

/* Set number of dots in host, so that cookies are set correctly */
s.cookieDomainPeriods="2";
s.fpCookieDomainPeriods="2";
if(domainName.indexOf('.co.uk')>-1) {
	s.cookieDomainPeriods="3";
	s.fpCookieDomainPeriods="3";
}

/* Plugin Config */
s.usePlugins=true;
function s_doPlugins(s) {
	/* Add calls to plugins here */

	var evar = '';

	// Write the value of pageName to eVar22 only once per session
	evar = s.getValOnce('eVar22', 'e_Var22',0);
	if (evar) s.eVar22 = s.pageName;

	// Attempt to get the source from the cookie
	var source = BookingBuddy.User.getSource('source');
	// Fall back on the query param, stripping any invalid chars
	if (!source) {
		source = s.getQueryParam('source').replace(/[^\w\-\.]/, '');
	}

	evar = s.getValOnce(source, 's_p1_s_campaign');
	if(evar) s.campaign = evar;

	evar = s.getValOnce(source, 's_p1_s_eVar7');
	if(evar) s.eVar7 = evar;

	evar = s.getValOnce(source, 's_p1_s_eVar9');
	if(evar) s.eVar9 = evar;

	evar = s.getValOnce(s.getQueryParam('taparam').replace(/[^\w\-\.]/, ''), 's_p1_s_eVar29');
	if (evar) s.eVar29 = evar;

	evar = s.getValOnce(s.getQueryParam('traqparam'), 's_p1_s_eVar31');
	if (evar) s.eVar31 = evar;

	// pubid param (should be  a publisher id value passed in  by BBDN)
	evar = s.getValOnce(s.getQueryParam('pubid'), 's_p1_s_eVar39');
	if (evar) s.eVar39 = evar;

	evar = s.getValOnce(s.getQueryParam('supmt'), 's_p1_s_eVar36');
	if (evar) s.eVar36 = evar;
}
s.doPlugins=s_doPlugins;

/* Wrapper for G code event tracking.  If the BB Demo is ever updated
   to H code, this can go away. */
function sendAnalyticsEvent() {
	s.pageName = s_pageName ? s_pageName : null;
	s.eVar10 = s_eVar10 ? s_eVar10 : null;
	if (s_pageName || s_eVar10) {
		s.t();
	}
}

/************************** PLUGINS SECTION *************************/
/* You may insert any plugins you wish to use here.                 */

/*
 * Plugin: getValOnce 0.2 - get a value once per session or number of days
 */
s.getValOnce=new Function("v","c","e",""
	+"var s=this,k=s.c_r(c),a=new Date;e=e?e:0;"
	+"if(v){"
		+"a.setTime(a.getTime()+e*86400000);"
		+"s.c_w(c,v,e?a:0);"
	+"}"
	+"return v==k?'':v"
);

/*
 * Plugin: getQueryParam 2.1 - return query string parameter(s)
 */
/*********************************************************************
* Function getQueryParam(p,d,u): Returns the query string parameter
*                 values for the parameters specified in p. If p is a
*                 list of names and multiple values are found, d
*                 separates the values found. If multiple values are
*                 found they are returned in the order in which they
*                 are specified in p.
*
*     p = comma delimited list of case insensitive query string
*         parameter names
*     d = (optional) delimiter used to separate query string parameter
*         values if multiple values are found. If omitted and multiple
*         parameters from p are found, the strings are appended to
*         eachother without a delimiter.
*     u = (optional) URL to take query string from. If omitted,
*         pageURL or window.location is used. If 'f', the
*         top-most frameset URL is used (in case you want to use the
*         URL from the address bar and the code is inside a frame).
*
* Returns:
*     - The query string parameter specified
*     - 'True' if the query string parameter exists without a value
*     - A delimited list of values if multiple parameters are found
*********************************************************************/

s.getQueryParam=new Function("p","d","u",""
	// object, return value, temp variables
	+"var s=this,v='',i,t;"
	+"d=d?d:'';"

	// u is the URL passed, the current URL or the top frameset URL
	+"u=u?u:(s.pageURL?s.pageURL:s.wd.location);"
	+"if(u=='f')u=s.gtfs().location;"

	// for each value in p
	+"while(p){"

		// get the index of the comma
		+"i=p.indexOf(',');"
		+"i=i<0?p.length:i;"

		// grab the parameter value associated with the first name
		+"t=s.p_gpv(p.substring(0,i),u+'');"

		// if p value was found, add to running v with delimiter
		// only add the delimiter if v has a value
		+"if(t)v+=v?d+t:t;"

		// take the first parameter off the list
		+"p=p.substring(i==p.length?i:i+1)"

	+"}"

	+"return v"
);

/*********************************************************************
* Function p_gpv(k): Get Parameter Value returns the value of the
*                 query string parameter, k, 'True' or ''.
*
*     k = query string parameter name (case insensitive)
*     u = URL to take query string from
*
* Returns:
*     - The query string parameter specified
*     - 'True' if the query string parameter exists without a value
*     - empty string
*********************************************************************/
s.p_gpv=new Function("k","u",""
	// value from key/value pair
	+"var s=this,v='',i=u.indexOf('?'),q;"

	// if k (key/value pair) and a query string exists in the URL
	+"if(k&&i>-1){"

		// q is query string without the question mark
		+"q=u.substring(i+1);"

		// s.pt with p_gvf finds value associated with k
		+"v=s.pt(q,'&','p_gvf',k)"

	+"}"

	// return value
	+"return v"
);

/*********************************************************************
* Function p_gvf(t,k): Get Parameter Value Function returns the value
*                 of the query string parameter, k, 'True' or ''.
*
*     t = query string token (one of the name value pairs)
*     k = query string parameter name (case insensitive)
*
* Returns:
*     - The query string parameter specified
*     - 'True' if the query string parameter exists without a value
*     - empty string
*********************************************************************/
s.p_gvf=new Function("t","k",""

	// check for a name=value pair (in case of && in the query string)
	+"if(t){"

		// p = parameter name, v = parameter value or 'True' if no value exists
		+"var s=this,"
		    +"i=t.indexOf('='),"
		    +"p=i<0?t:t.substring(0,i),"
		    +"v=i<0?'True':t.substring(i+1);"

		// if the p does not equal k, set v to ''
		+"if(p.toLowerCase()==k.toLowerCase())"
			+"return s.epa(v)"
	+"}"

	// return the URL decoded value
	+"return ''"
);

/************* DO NOT ALTER ANYTHING BELOW THIS LINE ! **************/
var s_objectID;function s_c2fe(f){var x='',s=0,e,a,b,c;while(1){e=
f.indexOf('"',s);b=f.indexOf('\\',s);c=f.indexOf("\n",s);if(e<0||(b>=
0&&b<e))e=b;if(e<0||(c>=0&&c<e))e=c;if(e>=0){x+=(e>s?f.substring(s,e):
'')+(e==c?'\\n':'\\'+f.substring(e,e+1));s=e+1}else return x
+f.substring(s)}return f}function s_c2fa(f){var s=f.indexOf('(')+1,e=
f.indexOf(')'),a='',c;while(s>=0&&s<e){c=f.substring(s,s+1);if(c==',')
a+='","';else if(("\n\r\t ").indexOf(c)<0)a+=c;s++}return a?'"'+a+'"':
a}function s_c2f(cc){cc=''+cc;var fc='var f=new Function(',s=
cc.indexOf(';',cc.indexOf('{')),e=cc.lastIndexOf('}'),o,a,d,q,c,f,h,x
fc+=s_c2fa(cc)+',"var s=new Object;';c=cc.substring(s+1,e);s=
c.indexOf('function');while(s>=0){d=1;q='';x=0;f=c.substring(s);a=
s_c2fa(f);e=o=c.indexOf('{',s);e++;while(d>0){h=c.substring(e,e+1);if(
q){if(h==q&&!x)q='';if(h=='\\')x=x?0:1;else x=0}else{if(h=='"'||h=="'"
)q=h;if(h=='{')d++;if(h=='}')d--}if(d>0)e++}c=c.substring(0,s)
+'new Function('+(a?a+',':'')+'"'+s_c2fe(c.substring(o+1,e))+'")'
+c.substring(e+1);s=c.indexOf('function')}fc+=s_c2fe(c)+';return s");'
eval(fc);return f}function s_gi(un,pg,ss){var c="function s_c(un,pg,s"
+"s){var s=this;s.wd=window;if(!s.wd.s_c_in){s.wd.s_c_il=new Array;s."
+"wd.s_c_in=0;}s._il=s.wd.s_c_il;s._in=s.wd.s_c_in;s._il[s._in]=s;s.w"
+"d.s_c_in++;s.m=function(m){return (''+m).indexOf('{')<0};s.fl=funct"
+"ion(x,l){return x?(''+x).substring(0,l):x};s.co=function(o){if(!o)r"
+"eturn o;var n=new Object,x;for(x in o)if(x.indexOf('select')<0&&x.i"
+"ndexOf('filter')<0)n[x]=o[x];return n};s.num=function(x){x=''+x;for"
+"(var p=0;p<x.length;p++)if(('0123456789').indexOf(x.substring(p,p+1"
+"))<0)return 0;return 1};s.rep=function(x,o,n){var i=x.indexOf(o);wh"
+"ile(x&&i>=0){x=x.substring(0,i)+n+x.substring(i+o.length);i=x.index"
+"Of(o,i+n.length)}return x};s.ape=function(x){var s=this,h='01234567"
+"89ABCDEF',i,c=s.charSet,n,l,e,y='';c=c?c.toUpperCase():'';if(x){x='"
+"'+x;if(c=='AUTO'&&('').charCodeAt){for(i=0;i<x.length;i++){c=x.subs"
+"tring(i,i+1);n=x.charCodeAt(i);if(n>127){l=0;e='';while(n||l<4){e=h"
+".substring(n%16,n%16+1)+e;n=parseInt(n/16);l++}y+='%u'+e}else if(c="
+"='+')y+='%2B';else y+=escape(c)}x=y}else{x=x?s.rep(escape(''+x),'+'"
+",'%2B'):x;if(x&&c&&s.em==1&&x.indexOf('%u')<0&&x.indexOf('%U')<0){i"
+"=x.indexOf('%');while(i>=0){i++;if(h.substring(8).indexOf(x.substri"
+"ng(i,i+1).toUpperCase())>=0)return x.substring(0,i)+'u00'+x.substri"
+"ng(i);i=x.indexOf('%',i)}}}}return x};s.epa=function(x){var s=this;"
+"return x?unescape(s.rep(''+x,'+',' ')):x};s.pt=function(x,d,f,a){va"
+"r s=this,t=x,z=0,y,r;while(t){y=t.indexOf(d);y=y<0?t.length:y;t=t.s"
+"ubstring(0,y);r=s.m(f)?s[f](t,a):f(t,a);if(r)return r;z+=y+d.length"
+";t=x.substring(z,x.length);t=z<x.length?t:''}return ''};s.isf=funct"
+"ion(t,a){var c=a.indexOf(':');if(c>=0)a=a.substring(0,c);if(t.subst"
+"ring(0,2)=='s_')t=t.substring(2);return (t!=''&&t==a)};s.fsf=functi"
+"on(t,a){var s=this;if(s.pt(a,',','isf',t))s.fsg+=(s.fsg!=''?',':'')"
+"+t;return 0};s.fs=function(x,f){var s=this;s.fsg='';s.pt(x,',','fsf"
+"',f);return s.fsg};s.c_d='';s.c_gdf=function(t,a){var s=this;if(!s."
+"num(t))return 1;return 0};s.c_gd=function(){var s=this,d=s.wd.locat"
+"ion.hostname,n=s.fpCookieDomainPeriods,p;if(!n)n=s.cookieDomainPeri"
+"ods;if(d&&!s.c_d){n=n?parseInt(n):2;n=n>2?n:2;p=d.lastIndexOf('.');"
+"if(p>=0){while(p>=0&&n>1){p=d.lastIndexOf('.',p-1);n--}s.c_d=p>0&&s"
+".pt(d,'.','c_gdf',0)?d.substring(p):d}}return s.c_d};s.c_r=function"
+"(k){var s=this;k=s.ape(k);var c=' '+s.d.cookie,i=c.indexOf(' '+k+'="
+"'),e=i<0?i:c.indexOf(';',i),v=i<0?'':s.epa(c.substring(i+2+k.length"
+",e<0?c.length:e));return v!='[[B]]'?v:''};s.c_w=function(k,v,e){var"
+" s=this,d=s.c_gd(),l=s.cookieLifetime,t;v=''+v;l=l?(''+l).toUpperCa"
+"se():'';if(e&&l!='SESSION'&&l!='NONE'){t=(v!=''?parseInt(l?l:0):-60"
+");if(t){e=new Date;e.setTime(e.getTime()+(t*1000))}}if(k&&l!='NONE'"
+"){s.d.cookie=k+'='+s.ape(v!=''?v:'[[B]]')+'; path=/;'+(e&&l!='SESSI"
+"ON'?' expires='+e.toGMTString()+';':'')+(d?' domain='+d+';':'');ret"
+"urn s.c_r(k)==v}return 0};s.eh=function(o,e,r,f){var s=this,b='s_'+"
+"e+'_'+s._in,n=-1,l,i,x;if(!s.ehl)s.ehl=new Array;l=s.ehl;for(i=0;i<"
+"l.length&&n<0;i++){if(l[i].o==o&&l[i].e==e)n=i}if(n<0){n=i;l[n]=new"
+" Object}x=l[n];x.o=o;x.e=e;f=r?x.b:f;if(r||f){x.b=r?0:o[e];x.o[e]=f"
+"}if(x.b){x.o[b]=x.b;return b}return 0};s.cet=function(f,a,t,o,b){va"
+"r s=this,r;if(s.apv>=5&&(!s.isopera||s.apv>=7))eval('try{r=s.m(f)?s"
+"[f](a):f(a)}catch(e){r=s.m(t)?s[t](e):t(e)}');else{if(s.ismac&&s.u."
+"indexOf('MSIE 4')>=0)r=s.m(b)?s[b](a):b(a);else{s.eh(s.wd,'onerror'"
+",0,o);r=s.m(f)?s[f](a):f(a);s.eh(s.wd,'onerror',1)}}return r};s.gtf"
+"set=function(e){var s=this;return s.tfs};s.gtfsoe=new Function('e',"
+"'var s=s_c_il['+s._in+'];s.eh(window,\"onerror\",1);s.etfs=1;var c="
+"s.t();if(c)s.d.write(c);s.etfs=0;return true');s.gtfsfb=function(a)"
+"{return window};s.gtfsf=function(w){var s=this,p=w.parent,l=w.locat"
+"ion;s.tfs=w;if(p&&p.location!=l&&p.location.host==l.host){s.tfs=p;r"
+"eturn s.gtfsf(s.tfs)}return s.tfs};s.gtfs=function(){var s=this;if("
+"!s.tfs){s.tfs=s.wd;if(!s.etfs)s.tfs=s.cet('gtfsf',s.tfs,'gtfset',s."
+"gtfsoe,'gtfsfb')}return s.tfs};s.mr=function(sess,q,ta){var s=this,"
+"dc=s.dc,t1=s.trackingServer,t2=s.trackingServerSecure,ns=s.visitorN"
+"amespace,unc=s.rep(s.fun,'_','-'),imn='s_i_'+s.fun,im,b,e,rs='http'"
+"+(s.ssl?'s':'')+'://'+(t1?(s.ssl&&t2?t2:t1):((ns?ns:(s.ssl?'102':un"
+"c))+'.'+(s.dc?s.dc:112)+'.2o7.net'))+'/b/ss/'+s.un+'/1/H.10-Pdvu-2/"
+"'+sess+'?[AQB]&ndh=1'+(q?q:'')+(s.q?s.q:'')+'&[AQE]';if(s.isie&&!s."
+"ismac){if(s.apv>5.5)rs=s.fl(rs,4095);else rs=s.fl(rs,2047)}if(s.d.i"
+"mages&&s.apv>=3&&(!s.isopera||s.apv>=7)&&(s.ns6<0||s.apv>=6.1)){"
+"im=s.wd[imn]=new Image;im.src=rs;if(rs.indexOf('&p"
+"e=')>=0&&(!ta||ta=='_self'||ta=='_top'||(s.wd.name&&ta==s.wd.name))"
+"){b=e=new Date;while(e.getTime()-b.getTime()<500)e=new Date}return "
+"''}return '<im'+'g sr'+'c=\"'+rs+'\" width=1 height=1 border=0 alt="
+"\"\">'};s.gg=function(v){var s=this;return s.wd['s_'+v]};s.glf=func"
+"tion(t,a){if(t.substring(0,2)=='s_')t=t.substring(2);var s=this,v=s"
+".gg(t);if(v)s[t]=v};s.gl=function(v){var s=this;if(s.pg)s.pt(v,',',"
+"'glf',0)};s.gv=function(v){var s=this;return s['vpm_'+v]?s['vpv_'+v"
+"]:(s[v]?s[v]:'')};s.havf=function(t,a){var s=this,b=t.substring(0,4"
+"),x=t.substring(4),n=parseInt(x),k='g_'+t,m='vpm_'+t,q=t,v=s.linkTr"
+"ackVars,e=s.linkTrackEvents;s[k]=s.gv(t);if(s.lnk||s.eo){v=v?v+','+"
+"s.vl_l:'';if(v&&!s.pt(v,',','isf',t))s[k]='';if(t=='events'&&e)s[k]"
+"=s.fs(s[k],e)}s[m]=0;if(t=='visitorID')q='vid';else if(t=='pageURL'"
+"){q='g';s[k]=s.fl(s[k],255)}else if(t=='referrer'){q='r';s[k]=s.fl("
+"s[k],255)}else if(t=='vmk')q='vmt';else if(t=='charSet'){q='ce';if("
+"s[k]&&s[k].toUpperCase()=='AUTO')s[k]='ISO8859-1';else if(s[k]&&s.e"
+"m==2)s[k]='UTF-8'}else if(t=='visitorNamespace')q='ns';else if(t=='"
+"cookieDomainPeriods')q='cdp';else if(t=='cookieLifetime')q='cl';els"
+"e if(t=='variableProvider')q='vvp';else if(t=='currencyCode')q='cc'"
+";else if(t=='channel')q='ch';else if(t=='transactionID')q='xact';el"
+"se if(t=='campaign')q='v0';else if(s.num(x)){if(b=='prop')q='c'+n;e"
+"lse if(b=='eVar')q='v'+n;else if(b=='hier'){q='h'+n;s[k]=s.fl(s[k],"
+"255)}}if(s[k]&&t!='linkName'&&t!='linkType')s.qav+='&'+q+'='+s.ape("
+"s[k]);return ''};s.hav=function(){var s=this;s.qav='';s.pt(s.vl_t,'"
+",','havf',0);return s.qav};s.lnf=function(t,h){t=t?t.toLowerCase():"
+"'';h=h?h.toLowerCase():'';var te=t.indexOf('=');if(t&&te>0&&h.index"
+"Of(t.substring(te+1))>=0)return t.substring(0,te);return ''};s.ln=f"
+"unction(h){var s=this,n=s.linkNames;if(n)return s.pt(n,',','lnf',h)"
+";return ''};s.ltdf=function(t,h){t=t?t.toLowerCase():'';h=h?h.toLow"
+"erCase():'';var qi=h.indexOf('?');h=qi>=0?h.substring(0,qi):h;if(t&"
+"&h.substring(h.length-(t.length+1))=='.'+t)return 1;return 0};s.lte"
+"f=function(t,h){t=t?t.toLowerCase():'';h=h?h.toLowerCase():'';if(t&"
+"&h.indexOf(t)>=0)return 1;return 0};s.lt=function(h){var s=this,lft"
+"=s.linkDownloadFileTypes,lef=s.linkExternalFilters,lif=s.linkIntern"
+"alFilters;lif=lif?lif:s.wd.location.hostname;h=h.toLowerCase();if(s"
+".trackDownloadLinks&&lft&&s.pt(lft,',','ltdf',h))return 'd';if(s.tr"
+"ackExternalLinks&&(lef||lif)&&(!lef||s.pt(lef,',','ltef',h))&&(!lif"
+"||!s.pt(lif,',','ltef',h)))return 'e';return ''};s.lc=new Function("
+"'e','var s=s_c_il['+s._in+'],b=s.eh(this,\"onclick\");s.lnk=s.co(th"
+"is);s.t();s.lnk=0;if(b)return this[b](e);return true');s.bc=new Fun"
+"ction('e','var s=s_c_il['+s._in+'],f;if(s.d&&s.d.all&&s.d.all.cppXY"
+"ctnr)return;s.eo=e.srcElement?e.srcElement:e.target;eval(\"try{if(s"
+".eo&&(s.eo.tagName||s.eo.parentElement||s.eo.parentNode))s.t()}catc"
+"h(f){}\");s.eo=0');s.ot=function(o){var a=o.type,b=o.tagName;return"
+" (a&&a.toUpperCase?a:b&&b.toUpperCase?b:o.href?'A':'').toUpperCase("
+")};s.oid=function(o){var s=this,t=s.ot(o),p=o.protocol,c=o.onclick,"
+"n='',x=0;if(!o.s_oid){if(o.href&&(t=='A'||t=='AREA')&&(!c||!p||p.to"
+"LowerCase().indexOf('javascript')<0))n=o.href;else if(c){n=s.rep(s."
+"rep(s.rep(s.rep(''+c,\"\\r\",''),\"\\n\",''),\"\\t\",''),' ','');x="
+"2}else if(o.value&&(t=='INPUT'||t=='SUBMIT')){n=o.value;x=3}else if"
+"(o.src&&t=='IMAGE')n=o.src;if(n){o.s_oid=s.fl(n,100);o.s_oidt=x}}re"
+"turn o.s_oid};s.rqf=function(t,un){var s=this,e=t.indexOf('='),u=e>"
+"=0?','+t.substring(0,e)+',':'';return u&&u.indexOf(','+un+',')>=0?s"
+".epa(t.substring(e+1)):''};s.rq=function(un){var s=this,c=un.indexO"
+"f(','),v=s.c_r('s_sq'),q='';if(c<0)return s.pt(v,'&','rqf',un);retu"
+"rn s.pt(un,',','rq',0)};s.sqp=function(t,a){var s=this,e=t.indexOf("
+"'='),q=e<0?'':s.epa(t.substring(e+1));s.sqq[q]='';if(e>=0)s.pt(t.su"
+"bstring(0,e),',','sqs',q);return 0};s.sqs=function(un,q){var s=this"
+";s.squ[un]=q;return 0};s.sq=function(q){var s=this,k='s_sq',v=s.c_r"
+"(k),x,c=0;s.sqq=new Object;s.squ=new Object;s.sqq[q]='';s.pt(v,'&',"
+"'sqp',0);s.pt(s.un,',','sqs',q);v='';for(x in s.squ)s.sqq[s.squ[x]]"
+"+=(s.sqq[s.squ[x]]?',':'')+x;for(x in s.sqq)if(x&&s.sqq[x]&&(x==q||"
+"c<2)){v+=(v?'&':'')+s.sqq[x]+'='+s.ape(x);c++}return s.c_w(k,v,0)};"
+"s.wdl=new Function('e','var s=s_c_il['+s._in+'],r=true,b=s.eh(s.wd,"
+"\"onload\"),i,o,oc;if(b)r=this[b](e);for(i=0;i<s.d.links.length;i++"
+"){o=s.d.links[i];oc=o.onclick?\"\"+o.onclick:\"\";if((oc.indexOf(\""
+"s_gs(\")<0||oc.indexOf(\".s_oc(\")>=0)&&oc.indexOf(\".tl(\")<0)s.eh"
+"(o,\"onclick\",0,s.lc);}return r');s.wds=function(){var s=this;if(s"
+".apv>3&&(!s.isie||!s.ismac||s.apv>=5)){if(s.b&&s.b.attachEvent)s.b."
+"attachEvent('onclick',s.bc);else if(s.b&&s.b.addEventListener)s.b.a"
+"ddEventListener('click',s.bc,false);else s.eh(s.wd,'onload',0,s.wdl"
+")}};s.vs=function(x){var s=this,v=s.visitorSampling,g=s.visitorSamp"
+"lingGroup,k='s_vsn_'+s.un+(g?'_'+g:''),n=s.c_r(k),e=new Date,y=e.ge"
+"tYear();e.setYear(y+10+(y<1900?1900:0));if(v){v*=100;if(!n){if(!s.c"
+"_w(k,x,e))return 0;n=x}if(n%10000>v)return 0}return 1};s.dyasmf=fun"
+"ction(t,m){if(t&&m&&m.indexOf(t)>=0)return 1;return 0};s.dyasf=func"
+"tion(t,m){var s=this,i=t?t.indexOf('='):-1,n,x;if(i>=0&&m){var n=t."
+"substring(0,i),x=t.substring(i+1);if(s.pt(x,',','dyasmf',m))return "
+"n}return 0};s.uns=function(){var s=this,x=s.dynamicAccountSelection"
+",l=s.dynamicAccountList,m=s.dynamicAccountMatch,n,i;s.un.toLowerCas"
+"e();if(x&&l){if(!m)m=s.wd.location.host;if(!m.toLowerCase)m=''+m;l="
+"l.toLowerCase();m=m.toLowerCase();n=s.pt(l,';','dyasf',m);if(n)s.un"
+"=n}i=s.un.indexOf(',');s.fun=i<0?s.un:s.un.substring(0,i)};s.sa=fun"
+"ction(un){var s=this;s.un=un;if(!s.oun)s.oun=un;else if((','+s.oun+"
+"',').indexOf(un)<0)s.oun+=','+un;s.uns()};s.t=function(){var s=this"
+",trk=1,tm=new Date,sed=Math&&Math.random?Math.floor(Math.random()*1"
+"0000000000000):tm.getTime(),sess='s'+Math.floor(tm.getTime()/108000"
+"00)%10+sed,yr=tm.getYear(),vt=tm.getDate()+'/'+tm.getMonth()+'/'+(y"
+"r<1900?yr+1900:yr)+' '+tm.getHours()+':'+tm.getMinutes()+':'+tm.get"
+"Seconds()+' '+tm.getDay()+' '+tm.getTimezoneOffset(),tfs=s.gtfs(),t"
+"a='',q='',qs='';s.gl(s.vl_g);s.uns();if(!s.q){var tl=tfs.location,a"
+",o,i,x='',c='',v='',p='',bw='',bh='',j='1.0',k=s.c_w('s_cc','true',"
+"0)?'Y':'N',hp='',ct='',pn=0,ps;if(String&&String.prototype){j=\"1.1"
+"\";if(j.match){j=\"1.2\";if(tm.setUTCDate){j=\"1.3\";if(s.isie&&s.i"
+"smac&&s.apv>=5)j=\"1.4\";if(pn.toPrecision){j=\"1.5\";a=new Array;i"
+"f(a.forEach){j=\"1.6\";i=0;o=new Object;eval(\"try{i=new Iterator(o"
+")}catch(e){}\");if(i&&i.next)j=\"1.7\"}}}}}if(s.apv>=4)x=screen.wid"
+"th+'x'+screen.height;if(s.isns||s.isopera){if(s.apv>=3){v=s.n.javaE"
+"nabled()?'Y':'N';if(s.apv>=4){c=screen.pixelDepth;bw=s.wd.innerWidt"
+"h;bh=s.wd.innerHeight;}}s.pl=s.n.plugins}else if(s.isie){if(s.apv>="
+"4){v=s.n.javaEnabled()?'Y':'N';c=screen.colorDepth;if(s.apv>=5){bw="
+"s.d.documentElement.offsetWidth;bh=s.d.documentElement.offsetHeight"
+";if(!s.ismac&&s.b){eval(\"try{s.b.addBehavior('#default#homePage');"
+"hp=s.b.isHomePage(tl)?'Y':'N'}catch(e){}\");eval(\"try{s.b.addBehav"
+"ior('#default#clientCaps');ct=s.b.connectionType}catch(e){}\")}}}el"
+"se r=''}if(s.pl)while(pn<s.pl.length&&pn<30){ps=s.fl(s.pl[pn].name,"
+"100)+';';if(p.indexOf(ps)<0)p+=ps;pn++}s.q=(x?'&s='+s.ape(x):'')+(c"
+"?'&c='+s.ape(c):'')+(j?'&j='+j:'')+(v?'&v='+v:'')+(k?'&k='+k:'')+(b"
+"w?'&bw='+bw:'')+(bh?'&bh='+bh:'')+(ct?'&ct='+s.ape(ct):'')+(hp?'&hp"
+"='+hp:'')+(p?'&p='+s.ape(p):'')}if(s.usePlugins)s.doPlugins(s);var "
+"l=s.wd.location,r=tfs.document.referrer;if(!s.pageURL)s.pageURL=l;i"
+"f(!s.referrer)s.referrer=r;if(s.lnk||s.eo){var o=s.eo?s.eo:s.lnk;if"
+"(!o)return '';var p=s.gv('pageName'),w=1,t=s.ot(o),n=s.oid(o),x=o.s"
+"_oidt,h,l,i,oc;if(s.eo&&o==s.eo){while(o&&!n&&t!='BODY'){o=o.parent"
+"Element?o.parentElement:o.parentNode;if(!o)return '';t=s.ot(o);n=s."
+"oid(o);x=o.s_oidt}oc=o.onclick?''+o.onclick:'';if((oc.indexOf(\"s_g"
+"s(\")>=0&&oc.indexOf(\".s_oc(\")<0)||oc.indexOf(\".tl(\")>=0)return"
+" ''}ta=n?o.target:1;h=o.href?o.href:'';i=h.indexOf('?');h=s.linkLea"
+"veQueryString||i<0?h:h.substring(0,i);l=s.linkName?s.linkName:s.ln("
+"h);t=s.linkType?s.linkType.toLowerCase():s.lt(h);if(t&&(h||l))q+='&"
+"pe=lnk_'+(t=='d'||t=='e'?s.ape(t):'o')+(h?'&pev1='+s.ape(h):'')+(l?"
+"'&pev2='+s.ape(l):'');else trk=0;if(s.trackInlineStats){if(!p){p=s."
+"gv('pageURL');w=0}t=s.ot(o);i=o.sourceIndex;if(s.gg('objectID')){n="
+"s.gg('objectID');x=1;i=1}if(p&&n&&t)qs='&pid='+s.ape(s.fl(p,255))+("
+"w?'&pidt='+w:'')+'&oid='+s.ape(s.fl(n,100))+(x?'&oidt='+x:'')+'&ot="
+"'+s.ape(t)+(i?'&oi='+i:'')}}if(!trk&&!qs)return '';if(s.p_r)s.p_r()"
+";var code='';if(trk&&s.vs(sed))code=s.mr(sess,(vt?'&t='+s.ape(vt):'"
+"')+s.hav()+q+(qs?qs:s.rq(s.un)),ta);s.sq(trk?'':qs);s.lnk=s.eo=s.li"
+"nkName=s.linkType=s.wd.s_objectID=s.ppu='';if(s.pg)s.wd.s_lnk=s.wd."
+"s_eo=s.wd.s_linkName=s.wd.s_linkType='';return code};s.tl=function("
+"o,t,n){var s=this;s.lnk=s.co(o);s.linkType=t;s.linkName=n;s.t()};s."
+"ssl=(s.wd.location.protocol.toLowerCase().indexOf('https')>=0);s.d="
+"document;s.b=s.d.body;s.n=navigator;s.u=s.n.userAgent;s.ns6=s.u.ind"
+"exOf('Netscape6/');var apn=s.n.appName,v=s.n.appVersion,ie=v.indexO"
+"f('MSIE '),o=s.u.indexOf('Opera '),i;if(v.indexOf('Opera')>=0||o>0)"
+"apn='Opera';s.isie=(apn=='Microsoft Internet Explorer');s.isns=(apn"
+"=='Netscape');s.isopera=(apn=='Opera');s.ismac=(s.u.indexOf('Mac')>"
+"=0);if(o>0)s.apv=parseFloat(s.u.substring(o+6));else if(ie>0){s.apv"
+"=parseInt(i=v.substring(ie+5));if(s.apv>3)s.apv=parseFloat(i)}else "
+"if(s.ns6>0)s.apv=parseFloat(s.u.substring(s.ns6+10));else s.apv=par"
+"seFloat(v);s.em=0;if(String.fromCharCode){i=escape(String.fromCharC"
+"ode(256)).toUpperCase();s.em=(i=='%C4%80'?2:(i=='%U0100'?1:0))}s.sa"
+"(un);s.vl_l='visitorID,vmk,ppu,charSet,visitorNamespace,cookieDomai"
+"nPeriods,cookieLifetime,pageName,pageURL,referrer,currencyCode,purc"
+"haseID';s.vl_t=s.vl_l+',variableProvider,channel,server,pageType,tr"
+"ansactionID,campaign,state,zip,events,products,linkName,linkType';f"
+"or(var n=1;n<51;n++)s.vl_t+=',prop'+n+',eVar'+n+',hier'+n;s.vl_g=s."
+"vl_t+',trackDownloadLinks,trackExternalLinks,trackInlineStats,linkL"
+"eaveQueryString,linkDownloadFileTypes,linkExternalFilters,linkInter"
+"nalFilters,linkNames';s.pg=pg;s.gl(s.vl_g);if(!ss)s.wds()}",
l=window.s_c_il,n=navigator,u=n.userAgent,v=n.appVersion,e=v.indexOf(
'MSIE '),m=u.indexOf('Netscape6/'),a,i,s;if(l)for(i=0;i<l.length;i++){
s=l[i];if(s.oun==un)return s;else if(s.fs(s.oun,un)){s.sa(un);return s
}}if(e>0){a=parseInt(i=v.substring(e+5));if(a>3)a=parseFloat(i)}
else if(m>0)a=parseFloat(u.substring(m+10));else a=parseFloat(v);if(a
>=5&&v.indexOf('Opera')<0&&u.indexOf('Opera')<0){eval(c);return new
s_c(un,pg,ss)}else s=s_c2f(c);return s(un,pg,ss)}function s_co(o){
var s=s_gi("^",1,1);return s.co(o)}function s_gs(un){var s=s_gi(un,1,1
);return s.t()}function s_dc(un){var s=s_gi(un,1);return s.t()}

/**
 * This file sets up general publishing events usually DOM events
 * Event publishing/subscription should be written like so for consistency
 *		- /EVENT/DESCRIPTION - i.e. /click/afsLink, /change/formInput
 *		- Arguments are passed as an array
 *			- $.pubish('/EVENT/DESCRIPTION', [ARGS]);
 *	
 * @requires	{lib/jquery-plugins/pubsub.js}
 * @see			{EventSub.js}
 *
 * Note: I'm leaving event.preventDefault() on the live bindigs on purpose
 * since that's what we had, even though event.preventDefault doesn't work on 
 * $.live. The next version of jquery will work though, so we'll deal with 
 * the expected behavior then.
 */


(function($){

	/**
	 * Clicks on AFS ad links 
	 * .com		=>  .inline_afs a
	 * deals 	=>  .afs_ad a
	 * @see { subscribe('/click/afsLink') }
	 */
	$('.inline_afs a, #afs_blind a, .afs_ad a, .BBafsAd a').live('click', function (event) {
		$.publish('/click/afsLink', [this]);
	});


	/**
	 * Click on a calendar field
	 * @see { subscribe('/click/calendar') }
	 */
	$('.checkHotelRates .hasDatepicker').live("click change", function() {
		$.publish('/click/checkHotelRates/calendar', [this]);
	});


	/**
	 * Active openLinkInNewWindow class links
	 * @see { subscribe('/click/newWindow')}
	 */
	$("a.newWindow").live('click', function(event) {
		event.stopPropagation();
		event.preventDefault();
		$.publish('/click/newWindow', [this]);
	});


	/**
	 * Active openNLWindow class links
	 * @see { subscribe('/click/openNLWindow') }
	 */
	$("a.openNLWindow").live('click', function(event) {
		event.stopPropagation();
		event.preventDefault();
		$.publish('/click/openNLWindow', [this]);
	});


	/**
	 * Publish click event of a ta check rates check box
	 */
	$('.BBInputCheckBox.taCheckRates').live('click', function(event) {
		$.publish('/click/checkbox/taCheckRates', [this]);
	});


	/**
	 * Hotel Rooms change events
	 * - event if rooms is 3
	 * @see { subscribe('/change/hotelNumRooms3Plus') }
     * TODO: May be cleaner to just trigger a 'hotelnumroomschanged' event and let a form handler do this checking for 3+ (KP)
	 */
	$('#hotel_num_rooms').live('change', function(event) {
		if ($(this).val() === '3') {
			event.stopPropagation();
			event.preventDefault();

			$.publish('/change/hotelNumRooms3Plus', [this]);
		}
	});


	/**
	 * Hotel planner go link 
	 * shows on a dhtml layer after the user has 
	 * clicked on 3+ rooms
	 * @see { subscribe('/click/hotelPlannerGo') }
	 */
	$('#hotelplanner_go').live('click', function(event) {
		$.publish('/click/hotelPlannerGo', [this]);

		// Hit the arrival_city blur event to start:
		// Why isn't this handled by whatever's listening for the above event? KP
		var checkCity = $('#hotel_arrival_city').blur();
		return !checkCity.hasClass('LSInvalid');
	});


	/**
	 * Track when a radio button is toggled on a widget
	 * Tieing to a custom event because not all products use pubsub.js
	 * @see { subscribe('/click/widgetRadioButton') }
	 */
	$("#bb_widget").live("STMToggle:complete", function() {
		$("#bb_widget .toggleActive").each(function() {
			$.publish('/click/widgetRadioButton', [this]);
		});
	});

	/**
	 * Handle the tracking that needs to be done when
	 * a Hotel Listing favorite is clicked.
	 * @see { subscribe('/click/hotelListingFavorites') }
	 */
	$(".hotelListingFavorites").live("click", function() {
		$.publish('/click/hotelListingFavorites', [this]);
	});

	/**
	 * Handle a user logging out of BB
	 * @see { subscribe('/click/logout') }
	 */
    $j('.logout').live('click', function(event) {
		event.stopPropagation();
		event.preventDefault();
		$.publish('/click/logout', [this]);
    });
    
    /**
     * Creates cookie for using inline subs
     * @see { subscirbe('click/inlineSubs)  }
     */
    $('.nl_sub_inline_ttd').live('click', function() {
    	$.publish('/click/inlineSubs', [this]);
    });
    

})(jQuery);

/**
 * This file sets up the subscription of several events
 * if there is a $.subscribe(...), there's a $.publish(...) - so grep is your friend!
 *
 * @requires	{lib/jquery-plugins/pubsub.js}
 * @see			{EventPub.js}
 * Event publishing/subscription should be written like so for consistency
 *		- /EVENT/DESCRIPTION - i.e. /click/afsLink, /change/formInput
 *		- NOTE: Even though an event can be passed in as an argument, 
 *		  event.preventDefault() or event.stopPropagation() should be done before 
 *		  publishing the event. Not all events should preventDefault/stopPropagation
 *
 */
(function($){

	/**
	* Clicks on individual deals on the deals Module
	* @param {object}			data: contains data needed for tracking
	* @param {html element}	elem: anchor tag that got clicked on
	*/
	$.subscribe('/click/bbdeal', function(data, elem) {
		var $link = $(elem).find('a');

		if ($link[0]) {
			url = $link.attr('href');
		} else {
			url = data.url;
		}

		if (url) {
			// if url open a new window with url 
			openDealWindow(url, $link);			
		}	

		if(BookingBuddy.OmnitureConfig){
			BookingBuddy.Tracking.trackDealClick($link, data.omnitureProductId, data.pageTitle, data.cpcCode);
		}else{
			//Legacy deal click tracking.
			omnitureDealClick($link, data.omnitureProductId, data.pageTitle, data.cpcCode);
		}
	});


	/**
	 * Clicks on links on the BookingBuddy deals popunder
	 * @param {object}			data: contains data needed for tracking (might be NULL)
	 * @param {html element}	elem: anchor tag that got clicked on
	 */
	$.subscribe('/click/dealsPopunderDealLink', function (elem, data) {

		if (data && data.url) {
			openDealWindow(data.url, elem);			

			if (BookingBuddy.OmnitureConfig) {
				BookingBuddy.Tracking.trackDealClick(elem, data.omnitureProductId, data.pageTitle, data.cpcCode);
			} else {
				//Legacy deal click tracking.
				omnitureDealClick(elem, data.omnitureProductId, data.pageTitle, data.cpcCode); 
			}

		} else {
			var left = (screen.width - 1096) /2,
				top = (screen.height - 825) /2;

			window.open(elem.href,'bbdealswindow','status=1,toolbar=1,location=1,menubar=1,resizable=1,scrollbars=1,height=729,width=1064,top='+ top + ',left='+left);
			if (BookingBuddy.OmnitureConfig) {
				BookingBuddy.Tracking.trackPopunderClick();
			} else {
				//Legacy deal click tracking.
				trackDealsPopunderClicks();
			}
		}	
	});


	/**
	 * Occurs on click of dartAd 
	 * Currently on step2.php and tabsearches.php
	 * @param {string} url: dartAd url "ad.doubleclick..."
	 * @param {string} window_opts: window.open options
	 */
	$.subscribe('/click/dartAd', function(url, window_opts) {
		window.open(decodeURIComponent(url), 'dartwindow', window_opts);
		omnitureDartClick(url);
	});

	/**
	 * Occurs on click of an AFS ad Link
	 * @param {html element}  elem: anchor tag that got clicked on
	 */
	$.subscribe('/click/afsLink', function(elem) {
		BookingBuddy.Tracking.trackAFSClick(elem);
	});


	/**
	 * Clicks on links with a newWindow class
	 * @param {html element}  elem: anchor tag that got clicked on
	 */
	$.subscribe('/click/newWindow', function(elem) {
		var $elem = $(elem),
			trackingTitle = null;

		if ($elem.find("span.linkId")[0]) {
			trackingTitle = $elem.find("span.linkId").html();

			// accounts for 
			// ta_photo, ta_amenities, ta_seereveiws
			if (trackingTitle.indexOf('ta_') !== -1) {
				STMClickstream.trackTAAIMClick(BookingBuddy.redirectUrl, BookingBuddy.taHotelPhotosMCID);
			}
		}

		openLinkInNewWindow(elem,trackingTitle);
		$elem.blur();
	});


	/**
	 * Opens the link href in a NL window.
	 * On failure, a popup blocker warning is shown (calling page must include markup)
	 * @param {html element}  elem: anchor tag that got clicked on
	 */
	$.subscribe('/click/openNLWindow', function(elem) {
		var window_name = 'latestNL',
			opts = 'status=1,resizable=1,scrollbars=1,height=608,width=795,top=35,left=0',
			url = $(elem).attr("href"),
			nl_window = window.open(url, window_name, opts);

		if ($.isNullOrUndefined(nl_window)) {
			BookingBuddy.Search.showBlockedPopUpMessage('BookingBuddyDealsBlockedPopUpDivID');
		} 

		$(elem).blur();
	});


	/**
	 * Track when a radio button is toggled on a widget
	 *	@param {html element}  elem: radio button input element that got toggled
	 */
	$.subscribe('/click/widgetRadioButton', function(elem) {
		var form_id = $(elem).attr("id").replace("_show-searchforms", ""),
			form	= $("form#" + form_id),
			mode 	= form_id.replace("_widget_form", "");

		if (form.length == 1 && !form.hasClass('UKLocale') && form.is(':visible')) {
			BookingBuddy.Tracking.trackSiteSectionVisit(mode);
		}

	});


	/**
	 * Find fare module to complete the form on step1
	 * @param {jQuery Object} widget: step 1 widget
	 * @param {object} data:   contains data needed for functionality/tracking
	 */
	$.subscribe('/click/FindFare/step1', function(widget, data) {
		var delay_time 	= 2000,
			overlay 	= new DHTMLPopup_Loader(widget.attr('id')),
			tracker 	= STMTracking.singleton();

		overlay.show();
		overlay.hide(delay_time);

		// after two seconds, populate the form values and re-hide the overlay div
		window.setTimeout(function() {
			$('#air_departure_city').val(data.from);
			$('#air_arrival_city').val(data.to);
			$('#air_date1').val(data.dep_date);
			$('#air_date2').val(data.ret_date);
		}, delay_time);

		// update the omniture description with what step it came from (Step 1)
		data.omniture_desc += "|S1";
		tracker.setVariable('eVar35', data.omniture_desc);
		tracker.send(data.omniture_desc);
	});


	/**
	 * Find fare module to load tabbed Browsing
	 * @param {object} data:   contains data needed for functionality/tracking
	 */
	$.subscribe('/click/FindFare/tabbedSearch', function(data) {
		// Load on tabbed browsing window
		var form_params 	= $j('.smartForm.bbSearch:first').serializeArray(),
			max 			= form_params.length,
			query_string 	= '',
			tracker 		= STMTracking.singleton();

		for (var i=0;  i < max ; i++) {
			if (form_params[i].name == "departure_date") {
				form_params[i].value = data.dep_date;

			} else if (form_params[i].name == "return_date") {
				form_params[i].value = data.ret_date;
			}
		}

		// convert object into query string
		query_string = $.param(form_params);
		BookingBuddy.TabbedWindowHandler.openTabbedWindow(query_string);

		if (data.page_type == 'deal_listing') {
			data.omniture_desc += "|HD1";
		} else {
			data.omniture_desc += "|S2";
		}
		tracker.setVariable('eVar35', data.omniture_desc);
		tracker.send(data.omniture_desc);
	});


	/**
	 * Click on hotel planner go link  
	 * @param {html element}  elem: anchor tag that got clicked on
	 */
	$.subscribe('/click/hotelPlannerGo', function(elem) {
		$('#hotel_arrival_city').bind({
			"LocationSuggest:invalid": function () {
				// submit and reset the form -- no danger of mucking anything up here
				$('#hotel_widget_form').submit();
				$('#hotelplanner_popup').hide();
				$('#page_mask').hide();
				$('#hotel_num_rooms').val('1').change();
				return false;
			},

			"LocationSuggest:valid": function () {
				var window_name = 'BBwin' +  new Date().getTime();
				// open the window here
				var searchWindow = window.open('', window_name, BookingBuddy.submitHandlers.windowOptions.oneSearch());
				$('#hotelplanner_go').attr('target', window_name);
				$('#hotelplanner_popup').hide();
				$('#page_mask').hide();
				$('#hotel_num_rooms').val('1').change();
				return true;
			}
		});
	});


	/**
	 * When a user check a taCheckRates checkbox launch a prepopwindow
	 * @param {html element}  elem: checkbox
	 */
	$.subscribe('/click/checkbox/taCheckRates', function(elem) {
		// launch prepop windows only for IE
		if (navigator.appName == 'Microsoft Internet Explorer') {
			BookingBuddy.bbSubmitHandlers.launchPrepop();
		}
	});


	/**
	 * When a user clicks/changes a calendar field
	 * @param {html element}  elem: calendar field
	 */
	$.subscribe('/click/checkHotelRates/calendar', function(elem) {
		// launch prepop windows only for IE 
		if (navigator.appName == 'Microsoft Internet Explorer') {
			BookingBuddy.bbSubmitHandlers.launchPrepop();
		}
	});


	/**
	 * Change event on hotel numRooms
	 *	@param {html element}  elem: anchor tag that got clicked on
	 */
	$.subscribe('/change/hotelNumRooms3Plus', function(elem){
		$.asyncLoad('#hotelplanner_popup', 'hotelplanner_popup', function(sel) {

			var hp_popup_offsets = {'left': 0, 'top': -100},
				hp_popup 		= new DHTMLPopup('hotelplanner_popup', false),
				newlink 		= $('#hotelplanner_go').attr('href') + '&r=i5826303&' + $('#hotel_widget_form').serialize();

			// Set up the HotelPlanner link here:
			$('#hotelplanner_go').attr('href', newlink);

			// Binding to close the HotelPlanner popup
			$('#hotelplanner_stay, #hotelplanner_popup_close').click(function() {
				$('#hotel_num_rooms').val('1').change();
			});

			// Finally, show the popup:
			hp_popup.setPageMask('page_mask');
			hp_popup.show(null, hp_popup_offsets);
		});
	});


	/**
	 * Occurs once a tab is added on tabbedBrowsing
	 * @param {TabBrowsing Object} - tab
	 */
	$.subscribe('/tabBrowsing/tabAdded', function(tab) {
		tab.trackAttemptedSearch().trackVendor();
	});

	/**
	 * Track when existing users log back in. This is currently only
	 * @param {jqueryObject} $form: form the user logged in from
	 * @param {bool} is_fare_alert_sub: fare alert subscription
	 * @param {bool} is_default_sub:  defualt subscription
	 * @param {bool} is_sa_sub:  sniqueAway subscription
	 */
	$.subscribe('/sub/login', function($form, isFareAlertSub, isDefaultSub, isSASub) {
		// if they login/subscribe through the deals module sign up box,
		// show a welcome message there
		if ($form.attr('id') === 'dealssub') {
			$form.find('.existing_reg').show();
			$form.find('.unsubscribed').hide();
			return;
		}

		if (isDefaultSub) {
			// Hide the bbdn deals sub box on a successful registration
			$('.deals_module.top_deals .deals_sub').slideUp();
		}
	});

	/**
	 * Track new subscriptions
	 * This is only published from stm_ajax_subscriptions.
	 * @param {jqueryObject} $form: form the user subscribed from
	 * @param {array} sub_types: array containig subscription types
	 */
	// $.subscribe('/sub/new', function($form, is_fare_alert_departure, is_fare_alert_route, is_default_sub, is_sa_sub) {
	$.subscribe('/sub/new', function($form, sub_types) {
		var isFromDealsModule = $form.attr('id') === 'dealssub';
		if (isFromDealsModule) {
			$form.find('.new_reg').show();
			$form.find('.unsubscribed').hide();
		}

		var tracker = STMTracking.singleton();

		// At this point tracker keeps ending up with an unwanted purchaseID property
		// FIXME: purchaseID shouldn't be happening in the first place
		if (tracker.getProperty('purchaseID')) {
			tracker.setProperty('purchaseID', null);	
		}

		$.each(sub_types, function(index, subscription) {

			if (subscription == 'Fare Alert Departure') {
				tracker.setEvent('event46');
			} else if (subscription == 'Fare Alert Route') {
				tracker.setEvent('event48');
			} else if (subscription == 'Default')  {
				tracker.setEvent('event49');
			} else if (subscription == 'Snique Away') {
				tracker.setEvent('event43');
			} else if (subscription == 'Hotel Watch') {
				tracker.setEvent('event50');
			}

			// set event4 for all regular subscriptions 
			if (subscription !== 'Snique Away' && !tracker.isEventSet('event4')) {
				tracker.setEvent('event4');
			}	
		});

		if ($.inArray('Default', sub_types))  {
			if (!isFromDealsModule) {
				// Hide the bbdn deals sub box on a successful registration
				$('.deals_module.top_deals .deals_sub').slideUp();
			}
		}

		tracker.send('A user has been subscribed to a newsletter');
	});


	/**
	 * Track showing of the Google Map dhtmlPopup  
	 */
	$.subscribe('/dhtmlPopup/googleMap', function() {
		var tracker = STMTracking.singleton();
		tracker.setVariable('eVar35', 'OnMap click');
		tracker.send('dhtml google map opened');
	});

	/**
	 * Track when Recent Searches modules is displayed
	 * @param {SmartFrom object} smartForm
	 */
	$.subscribe('/show/recentSearchData', function(smartForm) {
		setTimeout(function() {
			omnitureTrackBBRecentSearches(smartForm.mode);	
		}, 4000);
	});


	/**
	 * Track when .smartForm.checkHotelRates gets submitted
	 */
	$.subscribe('/submit/hotelCheckRates', function() {
		var tracker = STMTracking.singleton();
		tracker.setVariable('eVar35', 'Check Rates button');
		tracker.send('Check Rates Submission');
	});

	
	/**
	 * Submit event for a onesearch form
	 * @param {array}
	 * @param {jqueryObject}
	 */
	$.subscribe('/submit/oneSearchForm', function(ad_ids, $search_form){
		// Test bbs1499 loads as a one search 
		// so we'll let the tracking be handled by /page/search_redirect/
		if (BookingBuddy.abTests.bbs1499 === 'b') {
			return;
		}

		BookingBuddy.Tracking.trackBBSearch(ad_ids, false);
	});

	/**
	 * Submit event for a multipleSearch $search_form
	 * @param {array}
	 * @param {jqueryObject}
	 */
	$.subscribe('/submit/multipleSearchesForm', function(ad_ids, $search_form){
		BookingBuddy.Tracking.trackBBSearch(ad_ids);
	});

	/**
	 * Occurs on the search_redirect page once an ad has been clicked
	 * Gets triggered from the search_redirect template
	 * and from a popped window on tabbed browsing
	 * @param {object} data:  contains data needed for tracking
	 */
	$.subscribe('/page/search_redirect/', function(data) {
		BookingBuddy.Tracking.trackBBSearch([data.ad_id]);
	});
		

	/**
	 * Submit event for a checkRates $search_form
	 * @param {array}
	 * @param {jqueryObject}
	 */
	$.subscribe('/submit/checkRatesForm', function(ad_ids, $search_form) {
		BookingBuddy.Tracking.trackTACheckRates(ad_ids, $search_form);
	});
	
	/**
	 * BBS-1602: This subscribe call was added solely for use with this test
	 * If recipe A (default) wins, this can be removed
	 *
	 * Load event for bbdn deal modules
	 * @param {array}
	 * @param {jqueryObject}
	 */
	$.subscribe('/load/module/bbdeal', function(moduleType) {
		// only add test tracking for top deals bbdn modules
		if (moduleType !== 'top_deals') {
			return;
		}

		var bbs1602 = BookingBuddy.abTests.bbs1602;

		// show the deals sub for all recipes except the default
		if (bbs1602 && (bbs1602 === 'b' || bbs1602 === 'c' || bbs1602 === 'd')) {
			// set up the ip airport lookup
			BBIPLocation.populate('airport', 'dealssub_ac1', false, false);

			$('.deals_module.' + moduleType + ' .deals_sub').show();
		}
	});

	/**
	 * Click event for a Hotel Listing Favorite
	 * @param {HTMLElement} object triggering the event
	 */
	$.subscribe('/click/hotelListingFavorites', function(object) {
		var tracker = STMTracking.singleton();
		
		// At this point tracker keeps ending up with an unwanted purchaseID property
		// from the checkrates submit form... remove it
		if (tracker.getProperty('purchaseID')) {
			tracker.setProperty('purchaseID', null);	
		}
		
		tracker.setVariable('eVar35', 'Mark as Favorite');
		tracker.send('Mark as Favorites Submittion');
	});

	/**
	 * Click event for users logging out
	 * @param {HTMLElement} object triggering the event
	 */
	$.subscribe('/click/logout', function(elm) {
		var url = '/sub/logout.php',
			redirect = $(elm).find('.redirect').text();

		if (redirect) {
			url += '?url=' + redirect;
		}

		window.location = url;
	});
	
	/**
	 * When the splash page for the Traq Lander closes resize the window
	 */
	$.subscribe('/hide/TraqLander', function() {
		
		// Add an omniture on click call when users interact with the page
		// and they're coming from the popunder
		// Also prevent the popup from resizing and moving
		if (BookingBuddy.getQSParam('cross_promo')) {
			
			var evar14Val = "Hotel tabbed lander popunder";
			if(BookingBuddy.searchMode == "vacation"){
				evar14Val = "Vacation tabbed lander popunder";
			}
			
			tracker = STMTracking.singleton();
			tracker.setVariable("eVar14", evar14Val);
			tracker.send('Exit Popunder Cross Promo Tab Click');			
		} else {
			var x = screen.availWidth, 
				y = screen.availHeight, 
				top = 0, 
				left = 0;
			
			window.moveTo(top,left);
			window.resizeTo(x,y);
		} 

	});
	
	/**
	 * Creates a cookie that helps us decide if we want to show the step2 dhmtl popup or not.
	 * This solves a race condition. (user inline subs, searches, but no UI cookie yet when loading step2)
	 */
	$.subscribe('/click/inlineSubs', function() { 
		BookingBuddy.createCookie('inline_subs_used', true, 60 * 24);
	});

})(jQuery);


/*
 * Various classes for displaying and interacting with 
 * DHTML popups. Could include simple one screen popups
 * or multistep subscription popups. Expects calling code
 * to supply markup, style, and dimensions. Can optionally
 * use an element to black out the screen while the popup
 * is being displayed.
 *
 */

/**
 * Display and position a DHTML popup and, optionally, 
 * page mask. Uses 'dhtmlpopup:show' and 'dhtmlpopup:hide'
 * events on the popup element.
 */
(function($) {
	
	DHTMLPopup = Object.subClass( {

		/**
		 * Default cookie to prevent displaying twice in 24 hours
		 */
		cookie_name: null,
		
		/**
		 * Set the popup element
		 *
		 * @param mixed dhtml_id element or element ID
		 * @param bool use_iframe should an iframe be used to work around IE bugs.
		 *                        may not want this since iframes are very expensive
		 *                        to create
		 */
		init: function(dhtml_id, use_iframe) {
			this.popup = $("#" +dhtml_id);
			if ($.isNullOrUndefined(use_iframe)) {
				use_iframe = false;
			}
			this.id = dhtml_id;
			this.use_iframe = use_iframe;
			// Hookup close buttons
			$("#" +dhtml_id + " .closePopup").each( $.proxy( function(index, element) {
				// Find the closest parent popup element. This prevents
				// conflicts with nested popups
				var popup = $(element).closest('.dhtml_popup');

				// If the popup cannot be found or it's the same element as the outer parent,
				// use 'this' so the overridden hide function is used
				if (popup.length <= 0 || popup.attr('id') == dhtml_id) {
					popup = this;
				}

				$(element).click( function(e) {
					e.stopPropagation();
					e.preventDefault();
					popup.hide();
					return false;
				});
			}, this));

			$.data(this.popup.get(0), "dhtmlpopup", this);
			// Check to see if we should show immediately
			if(this.popup.hasClass("showNow")) {
				this.showNow();
			}
			this.initialized = true;
			this.popup.trigger('dhtmlpopup:init');
		},

		/**
		 * Set an alternate cookie name. Setting this to a false value will
		 * cause the popup to not limit display to once in a 24 hour period
		 *
		 * @param string cookie_name
		 */
		setCookieName: function(cookie_name) {
			this.cookie_name = cookie_name;
		},
			
		// Does nothing. Here for BC reasons
		setIframe: function(dummy) {},
		visible: function() {
			return $(this.popup).is(":visible");
		},
		/**
		 * Set the page mask element
		 *
		 * Should be an element with some sort of transparent PNG
		 * as a repeating background
		 *
		 * @param mixed mask_id element or element ID
		 */
		setPageMask: function(mask_id) {
			this.page_mask = $("#" +mask_id).length > 0 ? $("#" +mask_id) : false;
		},

		resizePageMask: function() {
			this.page_mask.css(
				{ 'height': $(document).height() + 'px', 'width' : $(document).width()+ 'px'}
			);
		},
		
		/**
		 * Display the popup element at the specified element or in
		 * the center of the screen.
		 *
		 * @param mixed elm_id element or element ID
		 * @param Hash offsets top and left values to additionally move the popup
		 * @return bool if the popup was displayed
		 */
		show: function(elm_id, offsets) {
			if (this.visible()) {
				return false;
			}

			// If the cookie has been enabled, create it if it doesn't exist
			// or don't show the popup if it does
			if (this.cookie_name) {
				if (!BookingBuddy.getCookie(this.cookie_name)) {
					BookingBuddy.createCookie(this.cookie_name, true, 60 * 24);
				} else {
					return false;
				}
			}

			if (this.page_mask && (!$.browser.msie || $.browser.version != 6)) {
				if(document.body) {
					this.resizePageMask();
					// allows the page mask to resize with the window, pervents user from
					// resizeing the window and seeing the edges of the modal
					$(window).bind('resize', $.proxy(this.resizePageMask, this));
				}
				// if using a mask make sure is is right underneath the popup
				if (this.popup.css('zIndex')) {
					$(this.page_mask).css('zIndex', this.popup.css('zIndex') - 1);
				}

				$(this.page_mask).show();
			}
			
			// set position here so the dhtmlpopup:show handler has
			// a chance to set the height / width of the popup
			var dims = null;
			if( $.isBoolean(elm_id) ) {
				dims = { top: 0, left: 0 };
			} else if ( $.isString(elm_id) ){
				elm_id = (/[\w\d]+/).exec(elm_id);	
				dims = elm_id ? $('#' + elm_id).position() : this._getCenterPos();
			} else {
				dims = elm_id ? elm_id.position() : this._getCenterPos();
			}


			// allow more flexibility in settings popup positions
			if (offsets) {
				dims.top = Math.max(dims.top+offsets.top, 0);
				dims.left = Math.max(dims.left+offsets.left, 0);
			}
			this.popup.css({top: dims.top + 'px', left: dims.left + 'px'});
			this.popup.show();
			this.popup.trigger('dhtmlpopup:show');
			
			if (!this.iframe && this.use_iframe) {
				var iframe_id = this.popup.attr('id') + '_iframe';
				this.iframe = this._createIframe(iframe_id);
			}

			if(this.use_iframe) {
				this.iframe.show();
			}
			return true;
		},

		showNow: function() {
			this.show();
		},
		
		/**
		 * Hide the popup, page mask, and iframe.
		 *
		 * Needs to be bound to the instance of this class
		 * when used as an event handler.
		 */
		hide: function(event) {
			if (this.iframe && this.use_iframe) {
				this.iframe.hide();
			}

			if (this.page_mask) {
				this.page_mask.hide();
			}

			this.popup.hide();
			this.popup.trigger('dhtmlpopup:hide');
		},

		/**
		 * Allow clicks on an arbitrary element to hide the popup.
		 * 
		 * @param String mask_id
		 */
		maskClick: function(mask_id) {
			$('#' + mask_id).bind('click', $.proxy(this.hide, this));
		},

		/**
		 * Creates and styles the hidden iframe in the same position and size
		 * as the popup
		 *
		 * @param Element elm
		 */
		_createIframe: function(iframe_id) {
			var iframe = $('iframe', {id: iframe_id, src: '', frameBorder: '0', scrolling: 'no'});

			// iframe should be lower than page mask or popup
			var z_index = this.page_mask ? this.page_mask.css('zIndex') : 
				this.popup.css('zIndex');

			iframe.css({
				'background-color': 'transparent',
				'display': 'none',
				'position': 'absolute',
				// make the iframe smaller than the popup to account
				// for any shaded border the popup may have. yet another
				// hack to work around IE being retarded
				'top': parseInt(this.popup.css('top'), 10) + 10 + 'px',
				'left': parseInt(this.popup.css('left'), 10) + 10 + 'px',
				'height': (this.popup.outerHeight() - 20) + 'px',
				'width': (this.popup.outerWidth() - 20)+ 'px',
				'border': '0',
				'z-index': z_index - 1
			});

			$(iframe).insertAfter(this.popup);
			return iframe;
		},

		/**
		 * Calculate the top left coordinate of the center of the
		 * browser window
		 *
		 * @return Hash
		 */
		_getCenterPos: function() {
			var dhtml_top = ($(window).height()  - this.popup.outerHeight()) / 2;
			var dhtml_left = ($(window).width() - this.popup.outerWidth()) / 2;

			return { top: dhtml_top, left: dhtml_left};
		}
	});

	/**
	 * DHTML Widget Layer
	 * Allows for the placement of multiple elements as part of the dhtml_layer
	 * The closing of any particular element will close all elements and remove
	 * the page_mask, if it's supplied.
	 * iframe mode is not supported.
	 */
	DHTMLPopup_Layer = DHTMLPopup.subClass({
		/**
		 * Constructor for the DHTML Widget layer
		 *
		 * @param {string} id (id of the object you want all events triggered to
		 * typically the div containing off of the widgets)
		 * @param {bool} use_iframe (not supported)
		 */
		init: function (id, use_iframe) {
			this.base = $('#' + id);
			this.max_zIndex = null;
			this.popups = [];
		},

		/**
		 * Adds a html element to the DHTML Layer
		 *
		 * @param {string} id (id of the html element to add to the layer)
		 */
		addElement: function (id) {
			var self = this;
			var tmp = $('#' + id);
			$("#" + id + " .closePopup").each(function(index, element) {
				// Find the closest parent popup element. This prevents
				// conflicts with nested popups
				var popup = $(element).closest('.dhtml_popup');

				// If the popup cannot be found or it's the same element as the outer parent,
				// use 'this' so the overridden hide function is used
				if (popup.length <= 0 || popup.attr('id') == id) {
					popup = tmp;
				}

				$(element).click( function(e) {
					self.hide();
				});
			});

			var index = tmp.css('zIndex');
			if (index && index > this.max_zIndex) {
				this.max_zIndex = index;
			}

			this.popups.push(tmp);
		},

		/**
		 * Show the layer
		 * All elements added to the layer will be displayed
		 * and at there current positions (generally defined
		 * via css)
		 * 
		 * @param {boolean} if true fades in the popups one by one 
		 */
		show: function(fade) {
			if (this.popups.length === 0) {
				return false;
			}
			
			if ($.isNullOrUndefined(fade)) {
				fade = false;
			}
			
			if (this.page_mask && (!$.browser.msie || $.browser.version != 6)) {
				if(document.body) {
					this.resizePageMask();
					// allows the page mask to resize with the window, pervents user from
					// resizeing the window and seeing the edges of the modal
					$(window).bind('resize', $.proxy(this.resizePageMask, this));
				}

				if (this.max_zIndex) {
					this.page_mask.css('zIndex', this.max_zIndex - 1);
				}

				$(this.page_mask).show();
			}
			if (!fade) {
				$.each(this.popups, function(index, popup) {
					popup.show();
				});
			} else {
				var new_popups = [],
					fadePopup = function(popups) {			
					popups.shift().fadeIn(1000, function() {
						new_popups.push($j(this));
						if (popups.length !== 0) { 
							fadePopup(popups);
						}
					});
					return new_popups;
				};
				
				this.popups = fadePopup(this.popups);
			}
			
			this.base.trigger('dhtmlpopup:show');
			return true;
		},

		hide: function(event) {
			if (this.page_mask) {
				this.page_mask.hide();
			}
			
			$.each(this.popups, function(index, popup) {
				popup.hide();
			});
			
			this.base.trigger('dhtmlpopup:hide');
		}
	});
	
	/**
	 * Display and manage a DHTML popup designed for
	 * subscribing users to a newsletter. Uses
	 * 'dhtmlpopup:sub:success' and 'dhtmlpopup:sub:error'
	 * events on the popup element.
	 * 
	 * BookingBuddy specific.
	 */
	DHTMLPopup_Sub = DHTMLPopup.subClass({
		/**
		 * Default endpoint for subscription requests
		 */
		api_url: '/ajax/sub.php',
	
		/**
		 * Default cookie to prevent displaying twice in 24 hours
		 */
		cookie_name: 'BBDHTMLPopup_Sub',

		
		init: function(dhtml_id, use_iframe) {
			this._super(dhtml_id, use_iframe);
			this.popup_form = $(this.popup).find('form');
		},
		/**
		 * Set an alternate Ajax script to perform the subscription
		 *
		 * @param string url
		 */
		setAPIURL: function(url) {
			this.api_url = url;
		},
	
		/**
		 * Submit the current form values to the sub API.
		 *
		 * Use events to notify calling code of the results.
		 *
		 * Event.memo may include 'message' to indicate the problem
		 * encountered during subscription. May also include the
		 * 'existing' flag to indicate if this a new subscription
		 * or an existing user.
		 *
		 * Needs to be bound to the instance of this class
		 * when used as an event handler.
		 */
		submit: function(e) {
			if(!this.submitting) {
				var params = this.popup_form.serialize();
				$.post(this.api_url, params, $.proxy(function(data) {
					if ('error' == data.status) {
						this.popup.trigger('dhtmlpopup:sub:error', [data]);
					} else {
						this.popup.trigger('dhtmlpopup:sub:success', [data]);
					}
					this.submitting = false;
				}, this), 'json');
			}
			return false;
		}
		
	});


	/**
	 * Display and manage a DHTML popup used to select
	 * and store a user's preferred BookingBuddy affiliate
	 * (BB.com vs BB.co.uk). Uses 'dhtmlpopup:af:success' and
	 * 'dhtmlpopup:af:error' events.
	 * 
	 * BookingBuddy specific.
	 */
	DHTMLPopup_Affiliate = DHTMLPopup.subClass({
	
		api_url: '/ajax/af.php',
		/**
		 * Set an alternate Ajax script to set the affiliate
		 *
		 * @param string url
		 */
		setAPIURL: function(url) {
			this.api_url = url;
		},
	
		/**
		 * Given the ID of a form element and the input names, submit
		 * the values to the AJAX backend notify calling code of the
		 * proper URL for the preferred affiliate. Optionally set
		 * a cookie storing the preference.
		 *
		 * Accepts form id and input names here since there are two forms,
		 * each representing an affiliate choice.
		 *
		 * @param string form_id
		 * @param string affiliate_id_name
		 * @param string set_cookie_name
		 */
		submit: function(form_id, affiliate_id_name, set_cookie_name) {
			var form = $('#' + form_id);
			var affiliate_id = form.find("input[name='" + affiliate_id_name + "']");
			var set_cookie = form.find("input[name='" + set_cookie_name + "']");
			
			var params = {
					af: affiliate_id.val(),
					sc: set_cookie.attr('checked')
			};

			$.post(this.api_url, params, $.proxy(function(data) {
				if ('error' == data.status) {
					this.popup.trigger('dhtmlpopup:af:error');
				} else {
					this.popup.trigger('dhtmlpopup:af:success', [data.url]);
				}
			}, this), 'json');
	
			return false;
		}
			
	});

	/**
	 * Display and manage a DHTML popup used to initiate a BBSearch
	 * for a specific hotel arrival city and provider
	 * 
	 * BookingBuddy specific.
	 */
	DHTMLPopup_CheckRates = DHTMLPopup.subClass({
		init: function(dhtml_id, use_iframe) {
			this._super(dhtml_id, use_iframe);
			// Hookup the check rate popup triggers
			this.triggers = $(".checkHotelRatesTrigger");
			this.triggers.live('click', $.proxy(function(e) {
				// The triggering  element will always have the hotel id as a suffix to the 
				// element id: trigger_elem_<HOTEL_ID>
				var _elem_id = $(e.currentTarget).attr("id");
				var _hotel_id = _elem_id.substr(_elem_id.lastIndexOf("_")+1);
				$("#hotelcheckrates_form_aderrors").hide();
				
				// For BBUK affiliates, show the popunder at the default position
				// so that it doesn't pop outside the borders of the iframe
				var use_default_pos = false;
				switch (BookingBuddy.affiliateName) {
					case 'world_travel_guide':
					case 'uk_holiday_weather':
					case 'uk_airfaresflights':
					case 'holidaywatchdog':
						use_default_pos = true;
						break;
						
					default:
						use_default_pos = false;
						break;
				}
				
				this.show(_hotel_id, use_default_pos);
	
			}, this));
			
		},
		show: function(hotel_id, use_default_pos) {
			var _pos = "check_rates_popup_location_"+hotel_id;
			if(!$.emptyString(hotel_id)) {
				this.hide();
	
				var popup_location = $("#check_rates_popup_location_"+hotel_id);
				var hotel_name = $("#hotel_name_"+hotel_id).html();
	
				// Set hotel provider
				if(!$.emptyString(hotel_name)) {
					$('#dhtml_hotel_search_title').html(hotel_name); 
					$('#hotelcheckrates_provider').val(hotel_name);
					$('#hotelcheckrates_provider').data('hotel_id', hotel_id);
					// Trigger change and blur events to make sure any bound actions take place (smart form common, current search update)
					$('#hotelcheckrates_provider').change();
					$('#hotelcheckrates_provider').blur();
				}

				if (use_default_pos) {
					if ($('#check_rates_popup_location').length > 0) {
						_pos = 'check_rates_popup_location';
					} else {
						_pos = false;
					}
				}
	
			} else {
				if ($('#check_rates_popup_location').length > 0) {
					_pos = 'check_rates_popup_location';
				} else {
					// just show it where it is
					_pos = false;
				}
			}
			this._super(_pos);
		},

		/**
		 * Show the popup immediately on page load.
		 * Override the parent function to handle determing the current
		 * hotel to load
		 */
		showNow: function() {
			var popup_location = $(".popup_location");
			if (popup_location.length > 0) {
				var elem_id = popup_location.attr('id');
				var hotel_id = elem_id.substr(elem_id.lastIndexOf("_") + 1);

				this.show(hotel_id, true);
			}
		}
	});

	/**
	 * Given the ID of some element on the page, display a transparent
	 * overlay on top of it with exactly the same size as the element
	 * and a loading spinner. Requires that applicable CSS be defined 
	 * for #widget_overlay and #widget_loader.
	 */
	DHTMLPopup_Loader = DHTMLPopup.subClass({
		init: function(dhtml_id, use_iframe) {
			// element to display on top of
			this.target = $('#' + dhtml_id);
			if ($('#widget_overlay').length) {
				this.overlay = $('#widget_overlay').hide();
			} else {
				this.overlay = $('<div id="widget_overlay"><div id="widget_loader"></div></div>').appendTo('body').hide();
			}
		},

		show: function(elm_id, offsets) {
			var position = this.target.offset();
			var height = this.target.height();
			var width = this.target.width();

			this.overlay.height(height).width(width);
			this.overlay.css({top: position.top + 'px', left: position.left + 'px'});
			this.overlay.show();
		},

		hide: function(delay_time) {
			if (!delay_time) {
				delay_time = 0;
			}

			// need to pass '0' to hide since css changes don't go into 
			// the 'fx' queue which is what the delay function uses by default
			this.overlay.delay(delay_time).hide(0);
		}
	});

	/**
	 * Display and manage a DHTML popup for loading a google map with optional
	 * controls and markers
	 * 
	 * BookingBuddy specific.
	 */
	DHTMLPopup_GoogleMap = DHTMLPopup.subClass({

		/**
		 * the stm_google_maps instance that will be loaded for this map
		 */
		map: null,

		/**
		 * the BookingBuddy.Maps.Data data holder for this map
		 */
		mapData: null,

		/**
		 * the loading dhtml popup for this map
		 */
		loader: null,

		/**
		 * Initialize the google map dhtml popup. Handle creating triggers for showing
		 * popup, triggers for toggling markers shown on the map, and a transparent
		 * loading screen to overlay on the map when content is being loaded.
		 *
		 * @param string id - element id of the dhtml popup wrapper
		 */
		init: function(dhtml_id) {
			var self = this;

			self._super(dhtml_id, false);

			var wrapper = $('#' + dhtml_id);
			if (!wrapper.length) {
				return;
			}

			self.mapId = wrapper.find('.dhtml_map').attr('id');
			if (!self.mapId) {
				return;
			}

			self.loader = new DHTMLPopup_Loader(self.mapId);

			// add a loading image over the map when content is being loaded
			$('#' + self.mapId).live(GoogleMap.constants.EVENT_LOADING + ' ' + GoogleMap.constants.EVENT_LOADED, function(e) {
				if (!self.loader) {
					return;
				}

				if (e.type == GoogleMap.constants.EVENT_LOADING) {
					self.loader.show();
				} else {
					self.loader.hide();
				}
			});

			// set up triggers for showing the map
			// to add a trigger element, the id must be in the following format:
			// actionType_actionId-unique_identifier
			self.triggers = $(".showDhtmlMap." + self.mapId);
			self.triggers.live('click', function(e) {
				e.stopPropagation();
				e.preventDefault();

				// the id of the trigger will have a prefix of the action
				// to perform when showing the map. Characters after the hyphen
				// are to provide a way to have unique ids
				var elem_id = $(e.currentTarget).attr("id");

				var parts = elem_id.split('-');
				var action = parts[0];

				self.show(action);
	
			});

			// add a way to arbitrarily trigger showing the dhtml popup
			wrapper.bind('stmmaps::showDhtmlMap', function(e, data) {
				if (data) {
					self.show(data.action);
				}
			});

			// set up the filters to toggle the markers on the map
			self.filters = wrapper.find(".filter");
			// use delegate here because there's a bug in IE when using
			// live() with the change event
			wrapper.delegate('input.filter', 'change', function(e) {

				if (!self.map) {
					return;
				}

				var elem = $(e.currentTarget);
				var name = elem.attr('name');

				var checked = elem.attr('checked');

				self.updateMap(name, checked);
			});
		},

		show: function(action) {
			this._super();
			// the map needs to be created after the map container is visible.
			// Otherwise, the map won't have the correct bounding coordinates
			if (!this.map) {
				this.mapData = BookingBuddy.Maps.Data.singleton(this.mapId);

				var center = this.mapData.getCenterPoint();
				var events = this.mapData.getEvents();
				this.map = $('#' + this.mapId).googleMapFactory(center, null, events);

			}

			this.updateMap(action, true);
		},

		hide: function(event) {

			if (this.map) {
				this.map.blur();
			}	

			this._super(event);
		},

		/**
		 * Update markers on the map with the given action and checked status
		 *
		 * @param string action - action to perform on the map; marker type and 
		 *	id. E.g. attraction_all or hotel_12345
		 * @param bool checked - flag to hide or show the markers being loaded
		 */
		updateMap: function(action, checked) {
			var parts = action.split('_');
			if (parts.length < 2) {
				return;
			}
			var actionType = parts[0];
			var actionId = parts[1];

			var data = null;

			// this currently assumes only one type of data will be loaded asynchronously
			// the else cases can be modified to account for specific types
			if (actionId == 'all') {
				if (actionType == 'hotel') {

					// load all hotels
					data = this.mapData.getLocations();
					BookingBuddy.Maps.loadLocationData(this.map, data, checked);

					this.filters.filter('.location').attr('checked', checked);

				} else if (actionType == 'attraction') {

					// load all attractions
					data = this.mapData.getPOIsByType('attraction');
					BookingBuddy.Maps.loadPOIs(this.map, data, checked);

					this.filters.filter('.attraction').attr('checked', checked);
				}
			} else {

				data = this.mapData.getLocation(actionId);
				BookingBuddy.Maps.loadLocationData(this.map, data, checked);

				this.filters.filter('.' + actionId).attr('checked', checked);

				// uncheck the 'all cities' filter if any location is unchecked
				if (!checked) {
					this.filters.filter('.all').attr('checked', false);
				}
			}
		}
	});
	
	/**
	 * Factory class to create DHTML popups
	 */
	DHTMLPopup_Factory =  {
		
		types: {
			base : DHTMLPopup,
			sub: DHTMLPopup_Sub,
			affiliate: DHTMLPopup_Affiliate,
			checkRates: DHTMLPopup_CheckRates,
			loader: DHTMLPopup_Loader,
			googleMap: DHTMLPopup_GoogleMap,
			layer: DHTMLPopup_Layer
		},

		create: function(options) {
			var _popup = null;
			if(!$.emptyString(options.id)) {
				var _type = this.types[options.type];
				if($.isNullOrUndefined(_type)) {
					_type = this.types.base;
				}
				_popup = new _type(options.id, options.use_iframe);
			}
			return _popup;
		}	
	};
	
})(jQuery);


/**
 * This class is BookingBuddy only due to it's use of BookingBuddy.getCookie in the first_click function.
 * It could be make universal by adding the getCookie to this class or moving it to a global common
 * function namespace
 */

// class constructor
var ExternalPopup = function(options) {
	// private members
	this.win = null;
	this.isPopped = false;
	this.firstClickPaused = false;

	// If options is a string, make it into an object
	if (options && options.params && (typeof options.params === 'string')) {
		var tmp = {};
		$j.each(options.params.split(','), function(index, value) {
			var key_value = value.split('=');
			if (key_value.length !== 2) {
				return;
			}

			tmp[key_value[0]] = key_value[1];
		});

		options.params = tmp;
	}

	// overload the default options if provided
	this.options = $j.extend(true, {}, ExternalPopup.defaults, options);
	// add the width and height options to the params option
	this.updateDimensions(this.options.width, this.options.height);
	this.init();
};

// add class constants
$j.extend(ExternalPopup, {
	TYPES: {NORMAL: 0, FIRSTCLICK: 1, ONEXIT: 2, EVERYCLICK: 3},
	LOCATIONS: {ABOVE: 0, UNDER: 1},
	PLACEMENTS: {TOPRIGHT: 0, BOTTOMRIGHT: 1, TOPLEFT: 2, BOTTOMLEFT: 3, CENTER: 4, PARENT: 5, CUSTOM: 6}
});

$j.extend(ExternalPopup, {
	defaults: {
		/**
		 * cookie to be used for first click check, will set value to true after
		 * launching the popup in the FIRSTCLICK type mode.
		 */
		cookieName: null,

		/**
		 *url for the popup
		 */
		url: 'about:blank',

		/**
		 * The name for the window, will be auto generated if null.
		 */
		name: null,

		/**
		 * poppup's window parameters
		 */
//		params: 'toolbar=0,location=0,directories=0,status=1,menubar=0,scrollbars=1,resizable=1',
		params: {
			toolbar: 0,
			location: 0,
			directories: 0,
			status: 1,
			menubar: 0,
			scrollbars: 1,
			resizable: 1
		},

		/**
		 * special behaviors of the popup
		 * options: NORMAL, FIRSTCLICK, ONEXIT, & EVERYCLICK
		 */
		type: ExternalPopup.TYPES.NORMAL,

		/**
		 * how the popup should launch.  ABOVE the calling window or UNDER the calling window
		 * options: ABOVE, UNDER
		 */
		location: ExternalPopup.LOCATIONS.ABOVE,

		/**
		 * where the popup should be drawn
		 */
		placement: ExternalPopup.PLACEMENTS.CUSTOM,

		/**
		 * where the popup should be located horizontally (x-position) when the
		 * placement is set to ExternalPopup.PLACEMENTS.CUSTOM Default to the
		 * middle of the screen
		 */
		posX: (screen.width / 2),

		/**
		 * where the popup should be located vertically (y-position) when the placement is set to ExternalPopup.PLACEMENTS.CUSTOM
		 * Default to the middle of the screen
		 */
		posY: (screen.height / 2),

		/**
		 * height of the popup
		 */
		height: 500,

		/**
		 * width of the popup
		 */
		width: 500,

		/**
		 * Delay in miliseconds to postpone the popping of the popup
		 * do not set a delay for ONEXIT popups, they will not be launched
		 */
		delay: null,

		/**
		 * CSS selector for element that will trigger the popup in EVERYCLICK mode
		 */
		triggerElement: ''
	},

	prototype: {
		/**
		 * initialize the external popup
		 * param opts associative array: options for popup
		 */
		init: function() {
			// listen for a ExternalPopup:SetUrl event and update the calling popup's url
			// the data param should be passed as an associated array with a definition for 'url'
			// which is the new url for the popup.
			$j(this).bind('ExternalPopup:setUrl', $j.proxy(function(event, data) {
				var newUrl = data['url'];
				if (newUrl) {
					this.setUrl(newUrl);
				}
			}, this));

			$j(this).bind('ExternalPopup:resizeWindow', $j.proxy(function(event, data) {
				var width = data.width,
					height = data.height;
				if (width && height) {
					this.resizeWindow(width, height);
				}
			}, this));

			// listen for a ExternalPopup:SetFirstClickPaused event and update the option if the
			// correct data member is passed
			$j(this).bind('ExternalPopup:setFirstClickPaused', $j.proxy(function(event, data) {
				var paused = data['paused'];
				if (!$j.isUndefined(paused)) {
					this.firstClickPaused = paused;
				}
			}, this));

			// launch the popup
			switch(this.options.type) {
			case ExternalPopup.TYPES.FIRSTCLICK:
				this.firstClick();
				break;
			case ExternalPopup.TYPES.ONEXIT:
				this.popOnExit();
				break;
			case ExternalPopup.TYPES.EVERYCLICK:
				this.everyClick();
				break;
			case ExternalPopup.TYPES.NORMAL:
				this.launch();
				break;
			default:
				break;
			}
		},

		/**
		 * Update the width and height members along with updates the params option
		 * with the new values.
		 *
		 * @param {int} width
		 * @param {int} height
		 */
		updateDimensions: function(width, height) {
			this.options.width = width;
			this.options.height = height;
			this.options.params.width = width;
			this.options.params.height = height;
		},

		/**
		 * updates the popup url member and the popup's href with the passed url
		 * param url string: new url for the popup
		 *
		 * @param {string} url
		 */
		setUrl: function(url) {
			this.options.url = url;
			if (this.win && !this.win.closed) {
				this.win.location.href = this.options.url;
			}
		},

		/**
		 * updates the popup window size to the size passed in
		 *
		 * @param {int} width
		 * @param {int} height
		 */
		resizeWindow: function(width, height) {
			this.updateDimensions(width, height);
			if (this.win && !this.win.closed) {
				try {
					this.win.resizeTo(width, height);
				} catch (e) {
					// persmission denied resizing window, oh well
				}
			}
		},

		/**
		 * calls the pop method with or without a delay as necessary
		 */
		launch: function() {
			// if there is no delay then don't even bother with the settimeout, makes unit testing easier
			if (this.options.delay && this.options.delay > 0) {
				// delay the popping of the popup as desired in the options
				setTimeout($j.proxy(this.pop, this), this.options.delay);
			} else {
				this.pop();
			}
		},

		/**
		 * pop's the popup
		 */
		pop: function() {
			// In chrome, we need to make sure the scrollbars is set to 0
			if (($j.browser.webkit !== undefined) && navigator.appVersion.toLowerCase().match("chrome")) {
				this.options.params.scrollbars = 0;
			}

			// In IE9, the window cannot be resizable or else it will always be a tab
			if (($j.browser.msie !== undefined) && (parseInt($j.browser.version, 10) >= 9)) {
				this.options.params.resizable = 0;
			}

			this.win = window.open(
				'about:blank',
				this.options.name ? this.options.name : 'ExternalPopup_' + (+new Date()).toString(36),
				$j.param(this.options.params).replace(/&/g, ',')
			);

			// window didn't pop, probably due to popup-blocker
			if (!this.win) {
				return;
			}

			// With FF4+, we need to open another window with the popup to allow the window.focus
			// to work
			if (($j.browser.mozilla !== undefined) && (parseInt($j.browser.version, 10) >= 2)) {
				var tmp_window = this.win.open('about:blank');
				if (tmp_window) {
					tmp_window.close();
				}
			}

			// position the window under the parent if needed
			if (this.options.location == ExternalPopup.LOCATIONS.UNDER) {
				this.win.blur();
				window.focus();
			}

			// place the popup to it's final destination
			var x = this.options.posX;
			var y = this.options.posY;
			switch(this.options.placement) {
			case ExternalPopup.PLACEMENTS.TOPRIGHT:
				x = screen.width;
				y = 0;
				break;
			case ExternalPopup.PLACEMENTS.BOTTOMRIGHT:
				x = screen.width;
				y = screen.height;
				break;
			case ExternalPopup.PLACEMENTS.TOPLEFT:
				x = 0;
				y = 0;
				break;
			case ExternalPopup.PLACEMENTS.BOTTOMLEFT:
				x = 0;
				y = screen.height;
				break;
			case ExternalPopup.PLACEMENTS.CENTER:
				x = (screen.width - this.options.width) / 2;
				y = (screen.height - this.options.height) / 2;
				break;
			case ExternalPopup.PLACEMENTS.PARENT:
				x = window.screenLeft ? window.screenLeft : window.screenX;
				y = window.screenTop ? window.screenTop : window.screenY;
				break;
			case ExternalPopup.PLACEMENTS.CUSTOM:
				// keep the initialized values for x,y (from the posX and posY options)
				break;
			default:
				break;
			}

			this.win.moveTo(x,y);
			this.setUrl(this.options.url);
			this.isPopped = true;
			$j(this).trigger('ExternalPopup:popped');
		},

		/**
		 * Sets up the window to launch the popup when an external link or form is click/submitted
		 */
		popOnExit: function() {
			// find all links/forms on the page and attach a trigger to those that make us leave the page
			$j('form[action!=],a[target!=_blank][href!=#][href!=]').each($j.proxy(function(index, element) {
				var link = $j(element);

				// skip this link/form if the noPopOnExit class is present
				if (link.hasClass('noPopOnExit')) {
					return true;
				}

				// what is the url/event of the item we want to bind to
				var event = '';
				var url = '';
				if (link.is('a')) {
					url = element.href;
					event = 'click';
				} else {
					url = element.action;
					event = 'submit';
				}

				// ensure the href is not relative (starts with a '/')
				// if we have a full href then check to see if the domains match the current host.
				if (url.indexOf('/') !== 0 && url.indexOf(window.location.host) == -1) {
					link.bind(event, $j.proxy(function() {
						// you can't delay the popping on exit or they will never fire
						this.launch();
					}, this));
				}

				return true;
			}, this));
		},

		/**
		 * Sets up the window to launch the popup upon the first click on the page
		 * (excludes clicking on elements with a class of 'noFirstClick')
		 */
		firstClick: function() {
			var self = this;
			// only launch if it hasn't aready been launched and the cookie doesn't exist
			if (!this.isPopped && !BookingBuddy.getCookie(this.options.cookieName)) {
				$j(document).bind('click.first', function(e) {
					// only launch if the user clicks on an element w/o the class noFirstClick
					// will only launch the firstclick popup if the url isn't null
					if (!$j(e.target).hasClass('noFirstClick') && !self.firstClickPaused) {
						// unbind the click event so it wont get triggered anymore
						$j(document).unbind('click.first');

						// Add in event to set cookie before trying to launch
						$j(self).bind('ExternalPopup:popped', function() {
							// set the popup's cookie only after it has popped (triggered due to possible use of delay)
							if (self.options.cookieName) {
								BookingBuddy.createCookie(self.options.cookieName, 'true', 60 * 24);
							}
						});

						// launch the popup
						self.launch();
					}
				});
			}
		},

		/**
		 * Sets up the window to launch the popup whenever the trigger element is clicked
		 */
		everyClick: function() {
			var self = this;
			if (this.options.triggerElement) {
				$elm = $j(this.options.triggerElement);
				$elm.click(function(e) {
					e.stopPropagation();
					e.preventDefault();

					self.launch();
				});
			}
		}
	}
});

/**
 * @deprecated Use BookingBuddy.Tracking.trackBBSearch
 */
function omnitureTrackBBSearch(link_name, ad_id, mode, repeated) {
	if (typeof repeated == 'undefined') {
		repeated = false;
	}
	s=s_gi(s_account);
	// default to including the "event5" tag on search
	// however, if it's just a non-paid dropdown, add event6
	var bb_event_flag = '';
	if (repeated) {
		bb_event_flag = 'event30,';
	} else {
		bb_event_flag = 'event5,';
	}
	
	if (link_name.match(/dropdown(?!_premium)/)) {
		if (repeated) {
			bb_event_flag = 'event31,';
		}
		else {
			bb_event_flag = 'event6,';
		}
	} else {

		if (!repeated) {
			switch(BookingBuddy.searchMode) {
				case "air":
					bb_event_flag += "event33,";
					break;
				case "hotel":
					bb_event_flag += "event34,";
					break;
				case "car":
					bb_event_flag += "event35,";
					break;
				case "vacation":
					bb_event_flag += "event36,";
					break;
				case "cruise":
					bb_event_flag += "event37,";
					break;
				case "vacation_rental":
					bb_event_flag += "event38,";
					break;
				default:
					break;
			}
		}
		
		//Add event40 for the first FS in each individual search mode
		var v40_cookie = BookingBuddy.getCookie("v40mode");
		var v40_array = (v40_cookie === null) ? [] : JSON.parse(v40_cookie);
		
		var set_v40 = ($j.inArray(BookingBuddy.searchMode, v40_array) == -1);
		
		if (set_v40) {			
			bb_event_flag += "event40,";
			v40_array.push(BookingBuddy.searchMode);
			BookingBuddy.createCookie("v40mode", JSON.stringify(v40_array));
		}
	}
	
	bb_event_flag += 'event7,';

	// bb includes 'i' as part of the ad_id sometimes
	if ((typeof ad_id === "string") && (0 === ad_id.indexOf('i'))) {
		ad_id = ad_id.substr(1);
	}
	
	if ((typeof is_promo_page === 'undefined') || (is_promo_page === false)) {
		bb_product_id = 'bb_' + ad_id;
	}
	else {
		bb_product_id = 'bb_lp_' + ad_id;
	}
	// Set the currency code for the Ad, if available
	var ad = BBAd.singleton(ad_id);
	var ad_currency_code = ad.getMetaData('currency_code');
	s.currencyCode = ad_currency_code ? ad_currency_code : "USD";
	
	s.linkTrackVars = 'products,events';
	if (!repeated) {
		//Don't send purchase event for repeat searches
		bb_event_flag += 'purchase';
	}
	s.linkTrackEvents = bb_event_flag;
	
	// Don't track the popup blocker on BBUK
	var blocker_alert = BookingBuddy.getCookie('blocker_alert');
	if (blocker_alert == 1 && BookingBuddy.domain !== 'bookingbuddy.co.uk') {
		s.eVar8 = 'blocker_search';
		s.linkTrackVars += ',eVar8';
		BookingBuddy.createCookie('blocker_alert', 0);
	}
	
	if (typeof test_affiliate == 'string') {
		s.eVar18 = test_affiliate;
		s.linkTrackVars += ',eVar18';
	}
		
	//Test to see if we need to note the entry time in eVar11
	//then update the cookie
	if (BookingBuddy.getCookie('entry_time') != 'time') {
		s.eVar11 = BookingBuddy.getESTHour();		
		s.linkTrackVars += ',eVar11';
	}
	BookingBuddy.createCookie('entry_time', 'time');

	s.products = ';' + bb_product_id + ';1;' + ad.getMetaData('tracking_hash');	
	s.events = bb_event_flag;

	var arr_city = null;
	// add route data to omniture for BookingBuddy.co.uk
	if ((BookingBuddy.domain == 'bookingbuddy.co.uk' || BookingBuddy.domain == 'bookingbuddy.in') && BookingBuddy.searchMode == 'air'){
		var dep_city = $j('#air_departure_city').val();
		arr_city = $j('#air_arrival_city').val();

		var dep_code = dep_city;
		var arr_code = arr_city;

		var parenIndex = arr_city.indexOf('(');			
		if ( parenIndex != -1 ) {
			arr_code  = arr_city.substr(parenIndex+1, 3);
		}

		parenIndex = dep_city.indexOf('(');
		if ( parenIndex != -1 ){
			dep_code = dep_city.substr(parenIndex+1, 3);
		}
		if ( dep_code && arr_code ){
			s.linkTrackVars += ',eVar21';
			s.eVar21 = 'Air | DEP - ' + dep_code + ' | ARR - ' + arr_code;
		}
	}
	
	if (BookingBuddy.domain == 'bookingbuddy.co.uk' && BookingBuddy.searchMode == 'hotel') {
		arr_city = $j('#hotel_arrival_city').val();
		
		if (arr_city) {
			s.linkTrackVars += ',eVar21';
			s.eVar21 = 'Hotel | ' + arr_city;
		}
	}
	
	// write some debug info about the user's environment & the ad
	if (BookingBuddy.domain == 'bookingbuddy.co.uk') {
		var browserInfo = getBrowserInfo();
		var browserName = browserInfo.name;
		var browserVers = browserInfo.version;
		var checkboxCur = 0;
		var checkboxTot = 0;
		var placementType = '';
		
		if (link_name.match(/\bdropdown\b/)) {
			placementType = 'dropdown';
		} 
		else if (link_name.match(/\btargeted_priced\b/)) {
			placementType = 'pricedad';
		} 
		else if (link_name.match(/\btargeted\b/)) {
			var checkboxInfo = BookingBuddy.UK.checkboxTrackingInfo;
			
			if (checkboxInfo.from_lightbox) {
				placementType = 'lightbox';
			} else {
				placementType = 'checkbox';
			}
			
			checkboxCur = checkboxInfo.current;
			checkboxTot = checkboxInfo.total;
		}
		
		var info = browserName + browserVers + '|' + placementType + '|' + checkboxCur + ' of ' + checkboxTot + '|' + bb_product_id;
		s.linkTrackVars += ',eVar33';
		s.eVar33 = info;
	}

	// add a unique id, with a random 6-digit # and timestamp, to beat serialization
	var now = new Date();
	s.purchaseID = Math.floor(Math.random() * 999999 + 1) + '' + now.getTime();

	if (s.pageName) {
		s.eVar25 = s.pageName;
		s.linkTrackVars += ',eVar25';
	}
	if (s.channel) {
		s.eVar24 = s.channel;
		s.linkTrackVars += ',eVar24';
	}
	s.tl(true,'o',link_name);
	s.linkTrackVars = "None";
	s.linkTrackEvents = "None";
	
}

// returns the user's browser name & version
// function found at http://www.javascripter.net/faq/browsern.htm & modified slightly
function getBrowserInfo() {
	var nAgt = navigator.userAgent;
	var browserName  = navigator.appName;
	var browserVersion  = '' + parseFloat(navigator.appVersion); 
	var nameOffset, verOffset, ix;

	// In MSIE, the true version is after "MSIE" in userAgent
	if ((verOffset = nAgt.indexOf("MSIE")) != -1) {
		browserName = "Microsoft Internet Explorer";
		browserVersion = nAgt.substring(verOffset + 5);
	}
	// In Opera, the true version is after "Opera" 
	else if ((verOffset = nAgt.indexOf("Opera")) != -1) {
		browserName = "Opera";
		browserVersion = nAgt.substring(verOffset + 6);
	}
	// In Chrome, the true version is after "Chrome" 
	else if ((verOffset = nAgt.indexOf("Chrome")) != -1) {
		browserName = "Chrome";
		browserVersion = nAgt.substring(verOffset + 7);
	}
	// In Safari, the true version is after "Safari" 
	else if ((verOffset = nAgt.indexOf("Safari")) != -1) {
		browserName = "Safari";
		browserVersion = nAgt.substring(verOffset + 7);
	}
	// In Firefox, the true version is after "Firefox" 
	else if ((verOffset = nAgt.indexOf("Firefox")) != -1) {
		browserName = "Firefox";
		browserVersion = nAgt.substring(verOffset + 8);
	}
	// In most other browsers, "name/version" is at the end of userAgent 
	else if ((nameOffset = nAgt.lastIndexOf(' ') + 1) < (verOffset = nAgt.lastIndexOf('/'))) {
		browserName = nAgt.substring(nameOffset, verOffset);
		browserVersion = nAgt.substring(verOffset + 1);
		if (browserName.toLowerCase() == browserName.toUpperCase()) {
			browserName = navigator.appName;
		}
	}
	
	// trim the browserVersion string to just the semi-major version (i.e., 3.5 instead of 3.5.3)
	browserVersion = browserVersion.match(/^\d+\.\d+/);
	
	var result = {name: browserName, version: browserVersion};
	return result;
}

// called to track the event of a recent search being called up
// taken straight from bookingbuddy.lib
function omnitureTrackBBRecentSearches(mode) {	
	s=s_gi(s_account);

	s.eVar13 = 'Recent Search - ' + mode.toUpperCase();
	s.linkTrackVars = "eVar13";
	s.linkTrackEvents = "None";
	
	s.tl(true,"o", "A recent search has been clicked.");

	s.linkTrackVars = "None";
	s.linkTrackEvents = "None";
	s.eVar13 = "";
}


//Called specifically by bb search when a popup is blocked
function blockedPopUp() {
	trackPopUpBlocker("search");
}

function trackPopUpBlocker(mode) {
	var blocker_alert = BookingBuddy.getCookie('blocker_alert');
	if (blocker_alert == 1) {
		return;
	}

	s=s_gi(s_account);
	
	s.linkTrackVars = 'eVar8';
	s.linkTrackEvents = 'None';
	
	s.eVar8 = (mode == 'deal') ? 'deal_blocker_alert' : 'blocker_alert';
	
	s.tl(true,'o', (mode == 'deal') ? 'Deal click blocked by popup blocker' : 'Search blocked by popup blocker');
	
	s.linkTrackVars = "None";
	s.linkTrackEvents = "None";
	s.eVar8 = '';
	
	BookingBuddy.createCookie('blocker_alert', 1);
}


/**
 * @deprecated Use BookingBuddy.Tracking.trackVendorLink
 */
function trackVendorLink(link, title) {
	s=s_gi(s_account);	

	// add a unique id, with a random 6-digit # and timestamp, to beat serialization
	var now = new Date();
	s.purchaseID = Math.floor(Math.random() * 999999 + 1) + '' + now.getTime();

	s.linkTrackVars="products,events";
	s.linkTrackEvents="purchase,event27";

	s.products = title;
	s.events = "purchase,";
	
	if (typeof test_affiliate == 'string') {
		s.eVar18 = test_affiliate;
		s.linkTrackVars += ',eVar18';
	}
	
	if (title.indexOf('afs') != -1) {
		s.events += "event27";
		s.linkTrackEvents += ",event27";
	} else {
		s.events += "event22";
		s.linkTrackEvents += ",event22";
	}

	// Make sure cc is always USD
	s.currencyCode = "USD";
	if (s.channel) {
		s.eVar24 = s.channel;
		s.linkTrackVars += ',eVar24';
	}
	if (s.pageName) {
		s.eVar25 = s.pageName;
		s.linkTrackVars += ',eVar25';
	}
	s.tl(true, "o", "Vendor Click");
}

/**
 * @deprecated Use BookingBuddy.Tracking.trackAFSClick
 */
function omnitureAFSClick(link) {
	switch (BookingBuddy.domain) {
		case 'bookingbuddy.com':
			title = ";bbafs_1;1;bbafs_1";
			break;

		case 'bookingbuddy.co.uk':
			title = ";bbukafs_1;1;bbukafs_1";
			break;

		default:
			title = ";afs_1;1;afs_1";
			break;
	}
	trackVendorLink(link, title);
	trackLinkShareClick('afs');
}


/**
 * Track Dart ad calls (temporary)
 * BBS-1230
 */
function omnitureDartClick(link) {
	var title = '';
	var product = '';
	
	product = 'bb_disp' + BookingBuddy.searchMode + '_1';

	title = ';' + product + ';1;' + product; 
	trackVendorLink(link, title);
}


/**
 * Track a ta check rates submit click
 * BBS-1068 TA check rates AB test
 *
 * @deprecated use BookingBuddy.Tracking.trackTACheckRates
 */
function omnitureTACheckRatesClick(link) {
	var title = '';
	var product = '';
	var tracking_hash = null;
	
	// extrack the tracking hash from the ad ID if it's there'
	var adData = link.split('_');

	if (adData.length == 4) {
		tracking_hash = adData[3];
	}

	switch (BookingBuddy.domain) {
		case 'bookingbuddy.co.uk':
			product = "bbuk_taad_1";
			break;
		default:
			product = "bb_taad_1";
			break;
	}
	if ($j.isNullOrUndefined(tracking_hash)) {
		tracking_hash = product;
	}

	title = ';' + product + ';1;' + tracking_hash;

	trackVendorLink(link, title);
	trackLinkShareClick('tacheckrates');
}

/**
 * Tracks a click from AIM thumbnails
 */
function trackAIMThumbnailClick() {
	var title = '';
	
	switch (BookingBuddy.domain) {
		case 'bookingbuddy.com':
			title = ";bb_taaim_1;1;bb_taaim_1";
			break;
	
		case 'bookingbuddy.co.uk':
			title = ";bbuk_taaim_1;1;bbuk_taaim_1";
			break;
			
		default:
			title = ";taaim_1;1;taaim_1";
			break;
	}
	
	trackVendorLink('', title);
}

/**
 * Tracks a click from the Holiday Priced modules
 */
function trackHolidayPriceClick() {
	s = s_gi(s_account);

	// add a unique id, with a random 6-digit # and timestamp, to beat serialization
	var now = new Date();
	s.purchaseID = Math.floor(Math.random() * 999999 + 1) + '' + now.getTime();

	s.linkTrackVars		= "products,events";
	s.linkTrackEvents	= "purchase,event22";
	s.events 			= "purchase,event22";
	s.products 			= ";bbuk_prhol_1;1;bbuk_prhol_1";

	// CC should be GBP for these clicks
	s.currencyCode = "GBP";
	
	if (s.channel) {
		s.eVar24 = s.channel;
		s.linkTrackVars += ',eVar24';
	}
	
	if (s.pageName) {
		s.eVar25 = s.pageName;
		s.linkTrackVars += ',eVar25';
	}
	
	s.tl(true, "o", "Vendor Click");
}

/**
 * Tracks a click from the Travel Insurance checkbox on BBUK flights Step2
 */
function trackTravelInsuranceClick() {
	s = s_gi(s_account);	

	// add a unique id, with a random 6-digit # and timestamp, to beat serialization
	var now = new Date();
	s.purchaseID = Math.floor(Math.random() * 999999 + 1) + '' + now.getTime();

	s.linkTrackVars		= "products,events";
	s.linkTrackEvents	= "purchase,event22";
	s.events 			= "purchase,event22";
	s.products 			= ";bbuk_ins_1;1;bbuk_ins_1";

	// CC should be GBP for these clicks
	s.currencyCode = "GBP";
	
	if (s.channel) {
		s.eVar24 = s.channel;
		s.linkTrackVars += ',eVar24';
	}
	
	if (s.pageName) {
		s.eVar25 = s.pageName;
		s.linkTrackVars += ',eVar25';
	}
	
	s.tl(true, "o", "Vendor Click");
}


// NOTE: This function is being replaced with omnitureDealClick().
// Function duplicated with new argument to prevent downtime between
// when static and bb are released. However, this needs to stay around
// for now since affiliate white labels will still be using it.
function omnitureBBDDealClick(link, omniture_product_id, page_title) {
	s=s_gi(s_account);	

	s.linkTrackVars="products,events,server";
	s.linkTrackEvents="purchase,event16,event24";

	var blocker_alert = BookingBuddy.getCookie("blocker_alert");
	if (blocker_alert == 1) {
		s.eVar8 = "blocker_deal";
		s.linkTrackVars += ",eVar8";
		BookingBuddy.createCookie('blocker_alert', 0);
	}

	if (BookingBuddy.getCookie("entry_time") != "time") {
		s.eVar11 = BookingBuddy.getESTHour();
		s.linkTrackVars += ",eVar11";
	}

	if (typeof test_affiliate == 'string') {
		s.eVar18 = test_affiliate;
		s.linkTrackVars += ',eVar18';
	}
	
	BookingBuddy.createCookie("entry_time", "time");
	if (s.channel) {
		s.eVar24 = s.channel;
		s.linkTrackVars += ',eVar24';
	}
	if (s.pageName) {
		s.eVar25 = s.pageName;
		s.linkTrackVars += ',eVar25';
	}
	s.products=";" + omniture_product_id + ";1;" + omniture_product_id;
	s.events="purchase,event16,event24";

	// add a unique id, with a random 6-digit # and timestamp, to beat serialization
    var now = new Date();
    s.purchaseID = Math.floor(Math.random() * 999999 + 1) + '' + now.getTime();

	s.lnk=s_co(link);
	s.tl(true, "o", "BBDN Deal Click");
}

function omnitureDealClick(link, omniture_product_id, page_title, code) { 
	s=s_gi(s_account);	

	s.linkTrackVars="products,events,server";
	s.linkTrackEvents="purchase,event16,event24";

	var blocker_alert = BookingBuddy.getCookie("blocker_alert");
	if (blocker_alert == 1) {
		s.eVar8 = "blocker_deal";
		s.linkTrackVars += ",eVar8";
		BookingBuddy.createCookie('blocker_alert', 0);
	}

	if (BookingBuddy.getCookie("entry_time") != "time") {
		s.eVar11 = BookingBuddy.getESTHour();
		s.linkTrackVars += ",eVar11";
	}

	if (typeof test_affiliate == 'string') {
		s.eVar18 = test_affiliate;
		s.linkTrackVars += ',eVar18';
	}
	
	BookingBuddy.createCookie("entry_time", "time");
	if (s.channel) {
		s.eVar24 = s.channel;
		s.linkTrackVars += ',eVar24';
	}

	if (s.pageName) {
		s.eVar25 = s.pageName;
		s.linkTrackVars += ',eVar25';
	}
	s.products=";" + omniture_product_id + ";1;" + code;
	s.events="purchase,event16,event24";



	// add a unique id, with a random 6-digit # and timestamp, to beat serialization
    var now = new Date();
    s.purchaseID = Math.floor(Math.random() * 999999 + 1) + '' + now.getTime();
	s.tl(true, "o", "BBDN Deal Click");
	trackLinkShareClick('bbdn');
}

// Simple, generic way to send Omniture events
function omnitureSendEvents(events, description) {
	s = s_gi(s_account);
 	s.linkTrackVars   = "events";
 	s.linkTrackEvents = events;
 	s.events          = events;
 	s.tl(true, "o", description);
 	
 	s.linkTrackVars = '';
 	s.linkTrackEvents = '';
 	s.events = '';
}

function omnitureSendEvar(evar, description) {
	s = s_gi(s_account);
	s.linkTrackVars = evar;
	s[evar] = description;
	s.tl(true, "o", description);
	
	s.linkTrackVars = '';
}


/**
 *
 *	 Attempt to open a window, return true on success, false on failure
 *	 On failure, a popup blocker warning is shown (calling page must include markup)
 * @param string - url
 * @param HTML_element - link
 */

function openDealWindow(url, link) {
	// Support for setting styles on visited links
	var anchor = $j(link);
	if (anchor[0]) {
		anchor.addClass('visited');
	}

	// Create a unique window name and try and open it 
	// NOTE: do not set this with the ServerTime.
	var d = new Date();
	var window_name = 'bb_deal_' + d.getTime();
	var winopts = "status=1,toolbar=1,location=1,menubar=1,resizable=1,scrollbars=1,height=700,width=925,top=35,left=0";

	var deal_window = window.open(url, window_name, winopts);
	if (deal_window) {
		return true;
	}
	else {
		trackPopUpBlocker("deal");
		BookingBuddy.Search.showBlockedPopUpMessage('BookingBuddyDealsBlockedPopUpDivID');
		return false;
	}
}

/**
 * open a generic popup window for displaying a chunk
 *
 * chunkname and headline must already be urlencoded
 */
function openInfoPopup(chunkName, headline, windowName, windowOptions) {
	if (!windowOptions) {
		windowOptions = 'scrollbars=1,resizable=1,width=460,height=300';
	}

	var winRef = window.open('/popup.php?id=' + chunkName + '&title=' + headline, windowName, windowOptions);
	if (winRef) {
		winRef.focus();
	}
}

function openLinkInNewWindow(link, trackingTitle) {
	if (!link.href) {
		return true;
	}
	
	if (!$j.emptyString(trackingTitle)) {
		if (typeof(useRefactoredOmni) != 'undefined') {
			BookingBuddy.Tracking.trackVendorLink(trackingTitle);
		} else {
			trackVendorLink(link, trackingTitle);
		}
	} else {
		trackingTitle = "nl_sample";
	}

	if (window.open(link.href, trackingTitle, 'scrollbars=1,resizable=1,width=750,height=800,toolbar=yes')) {
		return false;
	}
	
	return true;
}


function setEngagementCookie() {
	if (!BookingBuddy.getCookie('engage')) {
		BookingBuddy.createCookie('engage','engage');
	}
}

/**
 * @deprecated Use BookingBuddy.Tracking.trackOrganicSource
 */
function trackOrganicSource(request) {
	var ch10 = BookingBuddy.getCookie('BBsprop10');
	if (ch10) {
		s.prop10 = ch10;
	}

	if (BookingBuddy.getQSParam('source')) {
		return;
	}
	
	var ref = document.referrer.split('?');
	if (ref.length === 0) {
		return;
	}
	
	//Uncomment to test various referrers
	//ref = 'http://www.google.com/#hl=en&q=cheap+flights&btnG=Google+Search&aq=f&oq=cheap+flights&fp=Li-R6mbKWrc'

	var m = null;
	var campaign = null;

	if (m = /^http:\/\/www.google.(.+?)\//i.exec(ref[0])) {		
		campaign = 'org_google';
	}
	else if (m = /^http:\/\/(.+?)earch.yahoo.com\/search/i.exec(ref[0])) {
		campaign = 'org_yahoo';
	}
	else if (m = /^http:\/\/www.bing.(.+?)\/search/i.exec(ref[0])) {
		campaign = 'org_bing';
	}
	else if (m = /^http:\/\/.+?aol.(.+?)\/aol\/search/i.exec(ref[0])) {
		campaign = 'org_aol';
	}
	
	if (campaign !== null) {
		request.eVar7 = s.eVar9 = s.campaign = s.prop10 = campaign;
		BookingBuddy.createCookie('BBsprop10', s.prop10);
	}
}

/**
 * @deprecated Use BookingBuddy.Tracking.trackEntryTime
 */
function trackEntryTime(request) {
	if (BookingBuddy.getCookie('entry_time') != 'time') {
		request.eVar11 = BookingBuddy.getESTHour();		
		request.linkTrackVars += ',eVar11';
	}
	BookingBuddy.createCookie('entry_time', 'time');
	
	if (BookingBuddy.getCookie('last_visited_sent') != 1) {
		var time = new Date();
		if (BookingBuddy.getCookie('last_visited')) {
			var ms_diff = time.getTime() - BookingBuddy.getCookie('last_visited');
			var days_diff = Math.floor(ms_diff / 86400000);
			request.eVar28 = days_diff;
		}
		BookingBuddy.createCookie('last_visited', time.getTime(), 525600);
	}
	BookingBuddy.createCookie('last_visited_sent', 1, 30);
}

/**
 * Track on click events for each link on the deals popunder
 */
function trackDealsPopunderClicks() { 
	s=s_gi(s_account);
	s.eVar14 = 'Vacation Cross Promotion';
	s.linkTrackVars = 'eVar14';
	s.linkTrackEvents = 'None';
	s.tl(true, "o", "BB Deal Popunder Click");
}

function trackBBUKProductImpressions(products) {
	var dep_city = $j('#' + BookingBuddy.searchMode + '_departure_city').length > 0 ? $j('#' + BookingBuddy.searchMode + '_departure_city').val() : false;
	var arr_city = $j('#' + BookingBuddy.searchMode + '_arrival_city').length > 0 ? $j('#' + BookingBuddy.searchMode + '_arrival_city').val() : false;
	if ((BookingBuddy.UK.step2displayed || $j('#targetingNoStep2').length > 0) && (products.length > 0) && arr_city && ((dep_city === false) || (dep_city !== ''))) {
		s=s_gi(s_account);
		s.events = 'prodView';
		s.products = ';bb_' + products.join(',;bb_');
		s.linkTrackVars = 'products,events';
		if (s.channel) {
			s.eVar24 = s.channel;
			s.linkTrackVars += ',eVar24';
		}
		if (s.pageName) {
			s.eVar25 = s.pageName;
			s.linkTrackVars += ',eVar25';
		}
		s.linkTrackEvents = 'prodView';
		s.tl(true, "o", 'BBUK tracking');
	}
}

function trackSiteSectionVisit(mode) {
	var v39_cookie = BookingBuddy.getCookie("vss_mode");
	var v39_array = (v39_cookie === null) ? [] : JSON.parse(v39_cookie);

	var set_v39 = ($j.inArray(mode, v39_array) == -1);
	if (set_v39) {
		omnitureSendEvents("event39", "Site Section Visited - " + mode);
		v39_array.push(mode);
		BookingBuddy.createCookie("vss_mode", JSON.stringify(v39_array));
	}
}

/**
 * Write a tracking pixel to the page when a user enters
 * the site from a linkshare source.
 * To avoid potentially writing hundreds of these to the page,
 * this is intended to only write one per type of click.
 */
function trackLinkShareClick(click_type) {
	if (!$j.isNullOrUndefined(BookingBuddy.getCookie('linkshare'))) {
		var data = { 'click_id' : click_type };

		$j.asyncLoad(
			'#linkshare_click_' + click_type,
			'linkshare_tracking',
			function() {},
			data);
    }
}

function getCheckRatesCheckedProviderList(checked_elements) {
	var provider_list = '';
	
	if (!checked_elements || !checked_elements.length) {
		checked_elements = $j('form#hotelcheckrates_form .check_rates_checkbox_input:visible:checked');
	}
	
	// build our list
	if (checked_elements && checked_elements.length) {
		checked_elements.each(function(index, elm) {
			var key = $j(elm).parent().text();
			// trim doesn't seem to work on IE
			key = key.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
			provider_list += (provider_list) ? "," + key : key;
		});	
	}
		
	return provider_list;
}

function trackCheckRatesClickStream(provider_list, form) {

	if (!provider_list) {
		provider_list = getCheckRatesCheckedProviderList();
	}
	
	// try to get our date
	from_date = $j("#hotelcheckrates_date1").val();
	to_date = $j("#hotelcheckrates_date2").val();	
	if (!from_date && !to_date) {
		// some of the UK implementations use this
		from_date = $j("#hotelcheckrates_arrival_date").val();
		to_date = $j("#hotelcheckrates_departure_date").val();
	}
	if (!from_date && !to_date && form) {
		// BBUK hotel listing page uses this
		from_date = $j(form).find(".departureDate").val();
		to_date = $j(form).find(".returnDate").val();
	}

	STMClickstream.trackCheckRatesClick($j('#siteRedirectUrl').text(), $j('#TACheckRatesMCID').text(), provider_list, from_date, to_date, '');
}


/**
 * A collection of code for tracking user interaction on the page. The STMTracking
 * class could be used directly to write tracking information or tracking can be
 * done in an event-driven fashion. This isn't currently used anywhere since it's
 * hard to justify rewriting stuff that works. Just replace stuff from bb-tracking.js
 * (or your coresponding tracking functions) with this functionality as you see fit.
 *
 * @requires JQuery
 * @requires Utility.js
 *
 * Examples!
 *
 * <code>
 * <script>
 * var tracker = STMTracking.singleton();
 * tracker.setEvent('event4').setEvent('event32');
 * tracker.send('A user subscribed to a newsletter');
 * </script>
 * </code>
 *
 * ...or:
 *
 * <code>
 * <a class="afsLink" href="something">Give us money</a>
 * <script>
 * STMTrackingHandlers.myAfsClick = function(e) { *some stuff here* };
 * $j('.afsLink').each(function(index, element) {
 *     $j(element).track('click', 'myAfsClick');
 * });
 * </script>
 * </code>
 *
 * ...or:
 *
 * <code>
 * <a class="mySillyABTest" href="something">Click me</a>
 * <script>
 * $j('.mySillyABTest').each(function(index, element) {
 *     $j(element).track('click', function(e) {
 *         var tracker = STMTracking.singleton();
 *         tracker.setVariable('eVar15', 'AB Test Blah - Recipe B');
 *         tracker.send('A user did something interesting');
 *     });
 * });
 * </script>
 * </code>
 */

/**
 * Class to manage data sent to Omniture. Used by various predefined tracking
 * handlers below and one-off handlers employed by calls to jQuery.track.
 */
var STMTracking = Object.subClass({

	/**
	 * Backing Omniture 's' object, an obfuscated black box.
	 */
	delegate: null,

	/**
	 * Image used for making requests to the tracking server
	 */
	delegate_img: null,

	/**
	 * Hash of events to send.
	 */
	events: null,

	/**
	 * Has the initial on-page request been made yet?
	 */
	init_done: false,

	/**
	 * Hash of variables sent as part of a request that should be tracked.
	 */
	link_tracking: null,

	/**
	 * Hash of key value pairs to send.
	 */
	variables: null,

	/**
	 * Initialize any tracking events or variables and set our backend Omniture
	 * tracking object.
	 *
	 * @param {object} - An Omniture 's' object
	 * @param {string} - Domain to write tracking requests to
	 */

	init: function(tracker, server) {
		this.clear();

		this.delegate = tracker;
		this.setProperty('trackingServer', server);
	},

	/**
	 * Has the given event already been set?
	 *
	 * @param {string}
	 * @return {bool}
	 */
	isEventSet: function(event) {
		return !$j.isNullOrUndefined(this.events[event]);
	},

	/**
	 * Set an event to be sent with the tracking request.
	 *
	 * @param {string} - Full event name (e.g. 'event4' or 'purchase')
	 * @return - This object
	 */
	setEvent: function(event) {
		if ($j.isString(event)) {
			this.events[event] = true;
			this.setTrackVar('events');
		}

		return this;
	},

	/**
	 * Get the value of a variable that has been set, null if it doesn't exist.
	 *
	 * @param {string} - Full variable name (e.g. 'eVar25')
	 * @return {mixed}
	 */
	getVariable: function(key) {
		return $j.isUndefined(this.variables[key]) ? null : this.variables[key];
	},

	/**
	 * Set a variable (eVar) to be sent with the tracking request. You can also
	 * set arbitrary name-value pairs to be sent with a request using this
	 * method. This method should be used instead of .setProperty() when you
	 * only want the variable to be set for a single request. If you want it to
	 * be included on every call, use .setProperty().
	 *
	 * @param {string} - Full variable name (e.g. 'eVar25')
	 * @param {mixed} - The value
	 * @return - This object
	 */	
	setVariable: function(key, value) {
		if ($j.isString(key)) {
			this.variables[key] = value;
			this.setTrackVar(key);
		}

		return this;
	},

	/**
	 * Get a value of a property of the backing Omniture global 's' object, null if
	 * it doesn't exist.
	 *
	 * @param {string} - Name of the Omniture object property
	 * @return {mixed}
	 */
	getProperty: function(property) {
		return $j.isUndefined(this.delegate[property]) ? null : this.delegate[property];
	},

	/**
	 * Set the value of a property of the backing Omniture global 's' object. Values
	 * set here will persist on each call to .send() made on the page.
	 *
	 * @param {string}
	 * @param {mixed}
	 */
	setProperty: function(property, value) {
		if ($j.isString(property)) {
			this.delegate[property] = value;
		}

		return this;
	},

	/**
	 * Set the name of a variable that should be tracked when a request is
	 * made. This is done automatically for events and variables set using
	 * the setVariable method.
	 *
	 * @param {string} - Name of the variable (e.g. 'products' or 'events')
	 */
	setTrackVar: function(track_var) {
		this.link_tracking[track_var] = true;
		return this;
	},

	/**
	 * Set the given, events, variables, and link tracking variables
	 * as the appropriate properties of the delegate object.
	 *
	 * @param {Object} events
	 * @param {Object} variables
	 * @param {Object} tracking
	 */
	_setSendParams: function(events, variables, tracking) {
		var event_str = $j.getKeys(events).join(',');
		this.setProperty('linkTrackEvents', event_str);
		this.setProperty('events', event_str);

		$j.each(variables, $j.proxy(function(key, value) {
			this.setProperty(key, value);
		}, this));

		// make sure all properties of the delegate that need to be tracked are.
		this.setProperty('linkTrackVars', $j.getKeys(tracking).join(','));
		return this;
	},

	/**
	 * Get the image added to the page when an actual tracking call is made so
	 * we can wait for it to load before sending anymore tracking requests. If
	 * the .delegate_img property has been set, it will be returned in lieu of
	 * the image object created by the delegate.
	 *
	 * @return {Object}
	 */
	_getTrackingImage: function() {
		if (null !== this.delegate_img) {
			return this.delegate_img;
		}
		
		var img_name = 's_i_' + this.delegate.fun;
		if (!$j.isUndefined(this.delegate.wd[img_name])) {
			return this.delegate.wd[img_name];
		}

		return null;
	},

	/**
	 * Send the on-page Omniture request that will set up any needed sessions. All calls
	 * to .send() will be deferred until this method has been run and publishes a description
	 * that they are subscribed to. This method should only be run once.
	 */
	setup: function() {
		if (this.init_done) {
			return this;
		}

		this._setSendParams(this.events,this.variables, this.link_tracking);
		this.delegate.t();
		this.reset();

		var img = this._getTrackingImage();
		var setup_callback = $j.proxy(function() {
			this.init_done = true;
			$j.publish('/stmtracking/init');
		}, this);

		// Only wait for the load event of the tracking image if we got
		// an image object back.
		if (img) {
			$j(img).one('load', setup_callback);
		}

		return this.clear();
	},

	/**
	 * Send any events and variables that have been set so far with the given
	 * title and reset them afterwards.
	 *
	 * @param {String} title
	 */
	send: function(title) {
		// Get a copy of each object we'll be using to track events and
		// such since they'll get modified by the time this callback runs.
		var events = $j.extend({}, this.events);
		var vars = $j.extend({}, this.variables);
		var track = $j.extend({}, this.link_tracking);

		var send_callback = $j.proxy(function() {
			this._setSendParams(events, vars, track);
			this.delegate.tl(true, 'o', title);
			this.reset();
		}, this);

		// If the on-page call hasn't been run, don't make this call. Defer
		// it until we can be sure the on-page call has run and set up any
		// needed Omniture sessions or whatnot.
		if (!this.init_done) {
			$j.subscribe('/stmtracking/init', send_callback);
		} else {
			send_callback();
		}

		return this.clear();
	},

	/**
	 * Clear any events or variables that have been set on this tracker object.
	 */
	clear: function() {
		this.variables = {};
		this.events = {};
		this.link_tracking = {};
		return this;
	},

	/**
	 * Reset properties of the Omniture object that may have been set.
	 */
	reset: function() {
		this.setProperty('linkTrackVars', '');
		this.setProperty('linkTrackEvents', '');
		this.setProperty('events', '');
		return this;
	},

	/**
	 * Immediately send the given array of events with the given description. This 
	 * will also send any previously set events or variables and clear them afterwards.
	 *
	 * @param {Array} events
	 * @param {String} description
	 */
	sendEvents: function(events, description) {
		if ($j.isString(events)) {
			events = events.split(',');
		}
		
		$j.each(events, $j.proxy(function(index, elm) {
			this.setEvent(elm);
		}, this));
		
		this.send(description);
		return this;
		
	},

	/**
	 * Immediately send the given variable and use the value as the description
	 * for the tracking call. This will also send any previously set events or
	 * or variables and clear them afterwards.
	 *
	 * @param {String} variable
	 * @param {String} description
	 */
	sendVariable: function(variable, description) {
		this.setVariable(variable, description);
		this.send(description);
		return this;
	}
});


/**
 * Only instance of this tracking class that should be used on the page.
 */
STMTracking.instance = null;


/**
 * Only keep one instance of this class around at a time. The 's' object
 * and server params are only required on the first call to this which
 * should be done by initialization code. They are ignored on all 
 * subsequent calls.
 *
 * @param {object} - An Omniture 's' object
 * @param {string} - Tracking domain to use
 */
STMTracking.singleton = function(tracker, server) {
	if ($j.isNullOrUndefined(STMTracking.instance)) {
		if ($j.isUndefined(tracker) || $j.isUndefined(server)) {
			throw 'STMTracking: cannot initialize: missing tracker object and/or server params';
		}

		STMTracking.instance = new STMTracking(tracker, server);
	}

	return STMTracking.instance;
};


/**
 * Hook up a tracking event handler to a DOM element.
 */
(function($) {

	$.extend($.fn, {

		track: function(action, handler) {
			if (!this.length) {
				return this;
			}

			var method = (typeof handler === 'string') ? STMTrackingHandlers[handler] : handler;
			if ($.isUndefined(method)) {
				return this;
			}

			$(this[0]).bind(action, function(e) {
				try {
					method(e);
				} catch (err) {
					throw 'STMTracking: failed to run tracking handler for event ' + action + ': ' + err;
				}
			});

			return this;
		}
	});

})(jQuery);


/**
 * Static event handlers responsible for using the STMTracking class to send
 * Omniture information related to whatever event they've been bound to. Your
 * crazy business logic goes here or in some other file that you include to
 * extend this hash.
 */
var STMTrackingHandlers = {};



/**
 * @requires JQuery
 */
var STMClickstream = {

	/** 
	 * Tracks a Hotel Check Rates search
	 *  rdurl: The current site's rd url, with or without qstring
	 *  mcid: The current site's TA MCID
	 *  provider_list: CSV of provider strings (must match TA CheckRates eCPC Collection)
	 *  from_date: MM/DD/YYYY but not enforced
	 *  to_date: MM/DD/YYYY but not enforced
	 *  window_group_id: A unique identifier used to group a set of windows (use '' if unused)
	 */
	trackCheckRatesClick: function(rdurl, mcid, provider_list, from_date, to_date, window_group_id) {
		var rdUrlObj = this.parseRDUrl(rdurl, ['rd_tags']);
		var qsParams = rdUrlObj.qsParams;

		rdurl = rdUrlObj.baseUrl;

		// Can't pass the params as an object because it breaks
		// JS in IE6
		var url = rdurl + "/ajax/?" +
			(qsParams !== null ? $j.param(qsParams) : "") +
			"&service=Log.write" +
			"&parse_type=hotel-check-rates" +
			"&crmcid=" + mcid +
			"&provider_list=" + provider_list +
			"&from_date=" + from_date +
			"&to_date=" + to_date +
			"&window_group_id=" + window_group_id +
			"&page=" + encodeURIComponent(this.getReferringUrl()) +
			"&jsoncallback=?";
		$j.getJSON(url);
	},

	/**
	 * Tracks a TA AIM click
	 *  rdurl: The current site's rd url, with or without a qstring
	 *  mcid: The current site's MCID
	 */
	trackTAAIMClick: function(rdurl, mcid) { 
		rdurl = this.parseRDUrl(rdurl).baseUrl;

		// We have to build it as a single URL because passing an object as the 2nd
		// param breaks all JS in IE6
		var url = rdurl + "/ajax/?" +
			"service=Log.write" +
			"&parse_type=ta-aim" +
			"&aimmcid=" + mcid +
			"&page=" + encodeURIComponent(this.getReferringUrl()) + 
			"&jsoncallback=?";
		$j.getJSON(url);
	},

	/**
	 * Tracks an AFS click
	 * rdurl: The current site's rd url, with or without a qstring
	 */
	trackAFSClick: function(rdurl) { 
		
		rdurl = this.parseRDUrl(rdurl).baseUrl;

		// We have to build it as a single URL because passing an object as the 2nd
		// param breaks all JS in IE6
		var url = rdurl + "/ajax/?" +
			"service=Log.write" +
			"&parse_type=afs-click" +
			"&page=" + encodeURIComponent(this.getReferringUrl()) + 
			"&jsoncallback=?";
		$j.getJSON(url);
	},	

	/**
	 * getNewUUID is taken from the following. It's Math.uuidFast in his version. 
	 * Used here for getting a windowGroupID.
	 * Math.uuid.js (v1.4)
	 * http://www.broofa.com
	 * mailto:robert@broofa.com
	 * 	
	 * Copyright (c) 2010 Robert Kieffer
	 * Dual licensed under the MIT and GPL licenses.
	 */
	 getNewUUID: function() {
	 	
		var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
		var chars = CHARS, uuid = new Array(36), rnd=0, r;
		for (var i = 0; i < 36; i++) {
			if (i==8 || i==13 ||  i==18 || i==23) {
				uuid[i] = '-';
			} else if (i==14) {
				uuid[i] = '4';
			} else {
				if (rnd <= 0x02) {
					rnd = 0x2000000 + (Math.random()*0x1000000)|0;
				}
				r = rnd & 0xf;
				rnd = rnd >> 4;
				uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
			}
		}
		return uuid.join('');
	  },

	/**
	 * Chop up the RD url so we can reconstruct it the way we need for an ajax request.
	 * Discards any non-whitelisted qs params along the way.
	 */
	parseRDUrl: function (rdurl, allowedQSParams) {
		var rdUrlObj = {
			baseUrl : null,
			qsParams : null
		};

		var qmark = rdurl.indexOf('?');
		if (qmark !== -1) {

			//Strip off QS to get base URL. Also strip off any trailing / because
			// we're going to add it right back
			var baseUrl = rdurl.substring(0,qmark);
			if (baseUrl.substring(baseUrl.length-1) == '/') {
				baseUrl = baseUrl.substring(0, baseUrl.length-1);	
			}	
			rdUrlObj.baseUrl = baseUrl;

			//Parse the query string into an array format.
			if(allowedQSParams && allowedQSParams.length > 0){
				var params = {};

				qs = rdurl.substring(qmark + 1);
				paramPairs = qs.split('&');

				for(var i = 0; i < paramPairs.length; i++){
					param = paramPairs[i].split('=');

					if(param.length == 2 && $j.inArray(param[0], allowedQSParams) !== -1){
						params[param[0]] = param[1];
					}
				}

				rdUrlObj.qsParams = params;
			}

		}else{

			//Just strip any trailing /'s
			if (rdurl.substring(rdurl.length-1) == '/') {
				rdurl = rdurl.substring(0, rdurl.length-1);	
			}	
			rdUrlObj.baseUrl = rdurl;

		}

		return rdUrlObj;
	},

	/**
	 * Strips any trailing # off the referring URL. 
	 * (Trailing # causes badness down the line.)
	 */
	getReferringUrl: function () {
		var refurl = window.location.href;
		if (!refurl) {
			return '';
		}
		
		// strip off trailing #
		if (refurl.substring(refurl.length-1) == '#') {
			refurl = refurl.substring(0, refurl.length-1);	
		}	
		
		return refurl;
	}
	

};


BookingBuddy.Tracking = ({
	tracker: null,

	/**
	 * Setup omniture tracking
	 */
	init: function () {
		if (typeof(useRefactoredOmni) != 'undefined'){
			$j('document').ready($j.proxy(this.loadOmniture, this));
		}else{
			//Old model has some race conditions so need to load omni immediately in that case.
			this.loadOmniture();
		}
		return this;
	},

	loadOmniture: function(){
		var i;
		if (BookingBuddy.OmnitureConfig){
			//Set up the Omni and STMTracking objects.
			BookingBuddy.Tracking.setupOmniture(BookingBuddy.OmnitureConfig.account);
			this.tracker = STMTracking.singleton(s, BookingBuddy.OmnitureConfig.trackingServer);

			//Set necessary omni data then issue a page view call.
			this.tracker.setProperty('pageName', BookingBuddy.OmnitureConfig.pageName);
			this.tracker.setVariable('eVar25', BookingBuddy.OmnitureConfig.pageName);

			if (BookingBuddy.OmnitureConfig.evars) {
				var evars = BookingBuddy.OmnitureConfig.evars,
					max = evars.length,
					element = null,
					name = null,
					value = null;

				for (i = max; i--;) {
					element = evars[i];
					name = element['name'];
					name = name.replace('s_','');
					value = element['value'];
					this.tracker.setVariable(name, value);
				}
			}

			if (BookingBuddy.OmnitureConfig.events) {
				var events = BookingBuddy.OmnitureConfig.events,
					event = null;

				for (i = events.length; i--;) {
					event = events[i];
					this.tracker.setEvent(event);
				}
			}

			var channel = 'none';
			if (BookingBuddy.OmnitureConfig.channel){
				channel = BookingBuddy.OmnitureConfig.channel;
			}
			this.tracker.setProperty('channel', channel);
			this.tracker.setVariable('eVar24', channel);

			if (BookingBuddy.OmnitureConfig.substate){
				this.tracker.setProperty('prop9', BookingBuddy.OmnitureConfig.substate);
			}

			this.tracker.setEvent('event15');
			this.tracker.setVariable('eVar40', BookingBuddy.OmnitureConfig.expediaSource);

			this.trackEntryTime();

			// BookingBuddy.affiliateName is not set yet, so check against the account name --
			// Don't need to set event39 for BBUK
			if (BookingBuddy.OmnitureConfig.account.indexOf('slbbuk') === -1) {
				this.deriveSiteSectionVisitEvent(BookingBuddy.OmnitureConfig.searchMode, this.tracker);
			}

			this.tracker.setup();

			$j.publish('/bbtracking/init');
		} else {
			//Old-style init.  Phasing this out.
			this.tracker = STMTracking.singleton(s_gi(s_account), s_trackingServer);
		}
	},

	deriveSiteSectionVisitEvent: function(search_mode, tracker){
		var	v39_cookie 	= BookingBuddy.getCookie("vss_mode"),
		 	v39_array 	= (v39_cookie === null) ? [] : JSON.parse(v39_cookie),
			set_v39 	= ($j.inArray(search_mode, v39_array) == -1);

		if (set_v39) {
			this.tracker.setEvent('event39');
			v39_array.push(search_mode);
			BookingBuddy.createCookie("vss_mode", JSON.stringify(v39_array));
		}
	},

	/**
	 * Track change of search modes
	 * when a user clicks on a radio button
	 * @param {string}
	 */
	trackSiteSectionVisit: function (search_mode) {
		var track_callback = $j.proxy(function() {
			BookingBuddy.Tracking.deriveSiteSectionVisitEvent(search_mode, this.tracker);
			this.tracker.send('Site Section Visited - ' + search_mode);
		}, this);

		if(this.tracker){
			track_callback();
		}else{
			$j.subscribe('/bbtracking/init', track_callback);
		}
	},

	/**
	 * Track ad Searches
	 * @param {array} - ad_ids:
	 */
	trackBBSearch: function (ad_ids) {
		var tracker 				= this.tracker,
			event_timeout			= 2000,
			timeout_offset			= 200,
			ad 						= {},
			search_mode 			= BookingBuddy.searchMode || 'invalid_search_mode',
			domain 					= BookingBuddy.domain || 'invalid_domain',
			bb_product_id 			= null,
			blocker_alert 			= BookingBuddy.getCookie('blocker_alert'),
			entry_time 				= BookingBuddy.getCookie('entry_time'),
			mode_event 				= {
				air: 'event33',
				hotel: 'event34',
				car: 'event35',
				vacation: 'event36',
				cruise: 'event37',
				vacation_rental: 'event38'
			};

		$j.each(ad_ids, function(index, adID) {
			setTimeout(function() {
				var ad_id 				= /\d+/.exec(adID),
					ad 					= BBAd.singleton(ad_id),
					bb_product_id 		= 'bb_' + ad_id,
					// tracking_name contains the ad tier, but is being phased out; tier
					// should be accessed directly when you don't need a unique name
					adTier	 			= ad.getMetaData('tier') || ad.getMetaData('tracking_name') || '',
					ad_currency_code 	= ad.getMetaData('currency_code') || 'USD',
					ad_tracking_hash 	= ad.getMetaData('tracking_hash'),
					purchase_id 		= 0;


				// Non Featured Searches
				if (adTier.match(/dropdown(?!_premium)/)) {
					tracker.setEvent('event6');
				} else {
					// Featured Searches
					tracker.setEvent('event5');

					// These events should *not* get added to ST or AWD
					if (domain.indexOf('bookingbuddy') !== -1) {
						if (mode_event[search_mode]) {
							tracker.setEvent(mode_event[search_mode]);
						}

						//Add event40 for the first FS *only* in each individual search mode
						var v40_cookie = BookingBuddy.getCookie("v40mode"),
						v40_array = (v40_cookie === null) ? [] : JSON.parse(v40_cookie),
						set_v40 = ($j.inArray(search_mode, v40_array) == -1);

						if (set_v40) {
							tracker.setEvent('event40');
							v40_array.push(search_mode);
							BookingBuddy.createCookie("v40mode", JSON.stringify(v40_array));
						}
					}
				}

				tracker.setEvent('event7');
				tracker.setEvent('purchase');

				tracker.setProperty('currencyCode', ad_currency_code).setTrackVar('currencyCode');

				if (domain == 'bookingbuddy.co.uk') {
					BookingBuddy.Tracking.trackUKSearchSettings(ad);
				} else {
					// Don't track the popup blocker on BBUK
					if (blocker_alert == 1) {
						tracker.setVariable('eVar8', 'blocker_search');
						BookingBuddy.createCookie('blocker_alert', 0);
					}
				}

				BookingBuddy.Tracking.trackEntryTime();
				// Test to see if we need to note the entry
				// time in eVar11 then update the cookie
				if (entry_time  != 'time') {
					tracker.setVariable('eVar11', BookingBuddy.getESTHour());
				}
				BookingBuddy.createCookie('entry_time', 'time');

				tracker.setProperty('products', ';' + bb_product_id + ';1;' + ad_tracking_hash).setTrackVar('products');

				// add a unique id, with a random 6-digit # and timestamp, to beat serialization
				purchase_id = Math.floor(Math.random() * 999999 + 1) + '' + Number(new Date());
				tracker.setProperty('purchaseID', purchase_id).setTrackVar('purchaseID');

				if (s.channel) {
					tracker.setVariable('eVar24', s.channel);
				}

				if (s.pageName) {
					tracker.setVariable('eVar25', s.pageName);
				}

				tracker.send('BB Search');
			}, event_timeout); // setTimeout

			event_timeout += timeout_offset;
		});
	},


	/**
	 * Extra settings for a UK search
	 *
	 */
	trackUKSearchSettings: function (ad) {
		var tracker 		= this.tracker,
		 	browserInfo 	= getBrowserInfo(),
			browserName 	= browserInfo.name,
			browserVers 	= browserInfo.version,
			checkboxCur 	= 0,
			checkboxTot 	= 0,
			placementType 	= '',
			checkboxInfo 	= {},
			info 			= '',
			link_name 		= ad.getMetaData('tracking_name'),
			search_mode 	= BookingBuddy.searchMode,
			bb_product_id 	= ad.getID();

		if (search_mode == 'air') {
			var dep_city = $j('#air_departure_city').val(),
				arr_city = $j('#air_arrival_city').val(),
				dep_code = dep_city,
				arr_code = arr_city,
				parenIndex = arr_city.indexOf('(');

			if ( parenIndex != -1 ) { arr_code  = arr_city.substr(parenIndex+1, 3); }

			parenIndex = dep_city.indexOf('(');
			if ( parenIndex != -1 ) {
				dep_code = dep_city.substr(parenIndex+1, 3);
			}

			if ( dep_code && arr_code ){
				tracker.setVariable('eVar21',  'Air | DEP - ' + dep_code + ' | ARR - ' + arr_code);
			}

		} else if (search_mode == 'hotel') {
			arr_city = $j('#hotel_arrival_city').val();
			if (arr_city) {
				tracker.setVariable('eVar21', 'Hotel | ' + arr_city);
			}
		}

		// write some debug info about the user's environment & the ad
		if (link_name.match(/\bdropdown\b/)) {
			placementType = 'dropdown';

		} else if (link_name.match(/\btargeted_priced\b/)) {
			placementType = 'pricedad';

		} else if (link_name.match(/\btargeted\b/)) {
			placementType = 'checkbox';
		}

		info = browserName + browserVers + '|' + placementType + '|' + checkboxCur + ' of ' + checkboxTot + '|' + bb_product_id;
		tracker.setVariable('eVar33', info);
	},

	/**
	 * Send a tracking call when targeted ads are displayed on BBUK.
	 *
	 * @param {array} ad_ids
	 * @return void
	 */
	trackUKProductImpression: function(ad_ids) {
		var tracker = this.tracker,
			search_data = BBSavedSearchData.singleton(),
			dep_city = search_data.getCurrentSearchData('departure_city', BookingBuddy.searchMode),
			arr_city_field = (BookingBuddy.searchMode == 'car') ? 'pickup_city' : 'arrival_city',
			arr_city = search_data.getCurrentSearchData(arr_city_field, BookingBuddy.searchMode);

		if ((BookingBuddy.UK.step2displayed || $j('#targetingNoStep2').length > 0) && (ad_ids.length > 0) && arr_city && ((dep_city === null) || (dep_city !== ''))) {

			tracker.setEvent('prodView');
			tracker.setVariable('products', ';bb_' + ad_ids.join(',;bb_'));

			if (s.channel) {
				tracker.setVariable('eVar24', s.channel);
			}

			if (s.pageName) {
				tracker.setVariable('eVar25', s.pageName);
			}

			tracker.send('BBUK Tracking');
		}
	},

	/**
	 * Track ta Checkrates
	 * @param {array} ad_ids
	 */
	trackTACheckRates: function(ad_ids, $search_form) {
		var provider_list 	= [],
			from_date 		= $search_form[0].departure_date.value,
			to_date 		= $search_form[0].arrival_date.value,
			mcid 			= BookingBuddy.taCheckRatesMCID;

		$j.each(ad_ids, function(index, adID) {
			var title		= '',
			product 		= '',
			suppressClick   = false,
			taAd 			= TACheckRates.singleton(adID),
			adData 			= adID.split('_'), // extract tracking hash
			tracking_hash 	= adData.length == 3 ? adData[2] : false,
			adAuthor        = taAd.getMetaData('author_name'),
			adProviderKey   = taAd.getMetaData('provider_key');

			switch (BookingBuddy.domain) {
			case 'bookingbuddy.co.uk':

				//For BBUK, we suppress repeat clicks by the same user for the same providers.
				var searchedProviders = BookingBuddy.getCookie('searched_providers');
				if(searchedProviders === null){
					searchedProviders = { providers : [] };
				}else{
					searchedProviders = JSON.parse(searchedProviders);
				}

				if($j.inArray(adProviderKey, searchedProviders.providers) !== -1){
					suppressClick = true;
				}else{
					searchedProviders.providers.push(adProviderKey);
				}

				BookingBuddy.createCookie('searched_providers', JSON.stringify(searchedProviders));

				product = "bbuk_taad_1";
				break;
			default:
				product = "bb_taad_1";
				break;
			}

			// Create a provider list for click stream tracking
			// NOTE: This needs to be the author_name, not the vendor/display_name
			if(!suppressClick){
				provider_list.push(adAuthor);

				if (!tracking_hash) {
					tracking_hash = product;
				}

				title = ';' + product + ';1;' + tracking_hash;

				BookingBuddy.Tracking.trackVendorLink(title);
				BookingBuddy.Tracking.trackLinkShareClick('tacheckrates');
			}
		});

		// Click Stream Tracking
		if(provider_list.length > 0){
			STMClickstream.trackCheckRatesClick(BookingBuddy.redirectUrl, mcid, provider_list, from_date, to_date, '');
		}
	},

	/**
	 * Track TA Hotel Reviews Click
	 */
	trackTAHotelReview: function(hotel_id) {
		var mcid 	= BookingBuddy.taReviewsMCID,
			rdURL 	= BookingBuddy.redirectUrl,
			title 	= '';

		// Clean up the rd URL
		// Strip off any qstring
		var qmark = rdURL.indexOf('?');
		if (qmark !== -1) {
			rdURL = rdURL.substring(0, qmark);
		}
		// Also strip off any trailing / because
		// we're going to add it right back
		if (rdURL.substring(rdURL.length - 1) == '/') {
			rdURL = rdURL.substring(0, rdURL.length - 1);
		}

		// Grab the referring URL and clean it up
		var refURL = window.location.href ? window.location.href : '';
		// strip off trailing #
		if (refURL && refURL.substring(refURL.length - 1) == '#') {
			refURL = refURL.substring(0, refURL.length - 1);
		}

		var ajaxURL = rdURL + "/ajax/?" +
			"service=Log.write" +
			"&parse_type=ta-hotel-review" +
			"&revmcid=" + mcid +
			"&hotel_id=" + hotel_id +
			"&page=" + encodeURIComponent(refURL) +
			"&jsoncallback=?";
		$j.getJSON(ajaxURL);

		// Omniture tracking
		switch (BookingBuddy.domain) {
			case 'bookingbuddy.com':
				title = ";bb_tareviews_1;1;bb_tareviews_1";
				break;
			case 'bookingbuddy.co.uk':
				title = ";bbuk_tareviews_1;1;bbuk_tareviews_1";
				break;
			default:
				title = ";tareviews_1;1;tareviews_1";
				break;
		}
		BookingBuddy.Tracking.trackVendorLink(title);
	},

	/**
	 * Track Afs Click
	 */
	trackAFSClick: function() {
		var title = '';

		switch (BookingBuddy.domain) {
			case 'bookingbuddy.com':
				title = ";bbafs_1;1;bbafs_1";
				break;

			case 'bookingbuddy.co.uk':
				title = ";bbukafs_1;1;bbukafs_1";
				break;

			default:
				title = ";afs_1;1;afs_1";
				break;
		}

		BookingBuddy.Tracking.trackVendorLink(title);
		BookingBuddy.Tracking.trackLinkShareClick('afs');

		STMClickstream.trackAFSClick(BookingBuddy.redirectUrl);
	},

	/**
	 * Track AIM Thumbnail click
	 */
	trackAIMThumbnailClick: function() {
		var mcid = BookingBuddy.taHotelPhotosMCID,
			title = '';

		switch (BookingBuddy.domain) {
			case 'bookingbuddy.com':
				title = ";bb_taaim_1;1;bb_taaim_1";
				break;

			case 'bookingbuddy.co.uk':
				title = ";bbuk_taaim_1;1;bbuk_taaim_1";
				break;

			default:
				title = ";taaim_1;1;taaim_1";
				break;
		}

		BookingBuddy.Tracking.trackVendorLink(title);

		// Click Stream Tracking
		STMClickstream.trackTAAIMClick(BookingBuddy.redirectUrl, mcid);
	},

	/**
	 * Track vendor link
	 * @param {string} title
	 */
	trackVendorLink: function(title) {
		var tracker = this.tracker;

		// add a unique id, with a random 6-digit # and timestamp, to beat serialization
		purchase_id = Math.floor(Math.random() * 999999 + 1) + '' + Number(new Date());
		tracker.setProperty('purchaseID', purchase_id).setTrackVar('purchaseID');

		// Make sure cc is always USD
		tracker.setProperty('currencyCode', 'USD').setTrackVar('currencyCode');
		tracker.setProperty('products', title).setTrackVar('products');
		tracker.setEvent('purchase');

		if (typeof test_affiliate == 'string') {
			tracker.setVariable('eVar18', test_affiliate);
		}

		if (title.indexOf('afs') != -1) {
			tracker.setEvent('event27');
		} else {
			tracker.setEvent('event22');
		}

		if (s.channel) {
			tracker.setVariable('eVar24', s.channel);
		}

		if (s.pageName) {
			tracker.setVariable('eVar25', s.pageName);
		}

		tracker.send('Vendor Click');
	},

	/**
	 * Write a tracking pixel to the page when a user enters
	 * the site from a linkshare source.
	 * To avoid potentially writing hundreds of these to the page,
	 * this is intended to only write one per type of click.
	 */
	trackLinkShareClick: function(clickType) {
		if (!$j.isNullOrUndefined(BookingBuddy.getCookie('linkshare'))) {
			var data = { 'click_id' : clickType };
			$j.asyncLoad( '#linkshare_click_' + clickType, 'linkshare_tracking', function() {}, data);
		}
	},

	/**
	 * Takes an array of ad ids and sends ad related info to omniture
	 * @param {array} - ad_ids: ad ids
	 */
	trackRevSearches: function (ad_ids) {
		var ad 					= null,
			ad_tier 			= '',
			ad_tracking_hash 	= '',
			dropdown 			= '',
			dropdown_products 	= ';',
			featured 			= '',
			featured_products 	= ';',
			products 			= '',
			track_search 		= true,
			track_description 	= '',
			tracker 			= this.tracker;


		// add a unique id, with a random 6-digit # and timestamp, to beat serialization
		purchase_id = Math.floor(Math.random() * 999999 + 1) + '' + Number(new Date());
		tracker.setProperty('purchaseID', purchase_id).setTrackVar('purchaseID');

		$j.each(ad_ids, function(index, ad_id){

			ad					= BBAd.singleton(ad_id);
			// BBS-2441: TODO: the "ad_tier.name" is only for old tab browsing code
			// That last fallback can be removed when the old tab browsing code goes away
			ad_tier				= ad.getMetaData('tier') || ad.getMetaData('tracking_name') || ad.getMetaData('ad_tier.name');
			ad_tracking_hash	= ad.getMetaData('tracking_hash');

			if (!ad_tier || ad_tracking_hash) { return; }// skip iteration

			if (ad_tier.indexOf('dropdown ') != -1) {
				// Dropdown Search
				dropdown = true;
				dropdown_products +=  'bb_' + ad_id + ';1;' + ad_tracking_hash + ';event29=1,;';
			} else {
				// Featured Search
				featured = true;
				featured_products +=  'bb_' + ad_id + ';1;' + ad_tracking_hash + ';event28=1,;';
			}
		});


		if (featured && dropdown) {
			// send omniture call - featured and non featured
			products = featured_products.slice(0,-1) + dropdown_products;
			products = products.slice(0,-2);
			tracker.setProperty('products', products).setTrackvar('products');

			tracker.setVariable('eVar33', ad_ids.length);
			tracker.setEvent('event28');
			tracker.setEvent('event29');

			track_description = 'Featured and Dropdown Checked Search - BB REV';

		} else if (featured) {
			// send omniture call - featured
			products = featured_products.slice(0,-2);
			tracker.setProperty('products', products).setTrackvar('products');

			tracker.setVariable('eVar33', ad_ids.length);
			tracker.setEvent('event28');
			tracker.setEvent('event29');

			track_description = 'Featured Checked Search - BB REV';

		} else if (!featured && dropdown) {
			// send omniture call - non featured
			products = dropdown_products.slice(0, -2);
			tracker.setProperty('products', products).setTrackvar('products');

			tracker.setEvent('event29');

			track_description = 'Featured Dropdown Checked Search - BB REV';

		} else {
			track_search = false;
		}


		if (track_search){
			// Capture the test affiliate name if set
			if (typeof test_affiliate == 'string') {
				tracker.setVariable('eVar18', test_affiliate);
			}

			tracker.send(track_description);
		}
	},

	trackDealClick: function(link, omniture_product_id, page_title, code) {
		var tracker = this.tracker;

		tracker.setEvent('purchase');
		tracker.setEvent('event16');
		tracker.setEvent('event24');
		
		purchase_id = Math.floor(Math.random() * 999999 + 1) + '' + Number(new Date());
		tracker.setProperty('purchaseID', purchase_id).setTrackVar('purchaseID');

		var blocker_alert = BookingBuddy.getCookie("blocker_alert");
		if (blocker_alert == 1) {
			tracker.setVariable('eVar8', 'blocker_deal');
			BookingBuddy.createCookie('blocker_alert', 0);
		}

		if (typeof test_affiliate == 'string') {
			tracker.setVariable('eVar18', test_affiliate);
		}

		//Set necessary omni data then issue a page view call.
		this.tracker.setVariable('eVar25', BookingBuddy.OmnitureConfig.pageName);

		var channel = 'none';
		if (BookingBuddy.OmnitureConfig.channel){
			channel = BookingBuddy.OmnitureConfig.channel;
		}
		this.tracker.setVariable('eVar24', channel);

		this.trackEntryTime();

		tracker.setProperty('products', ';' + omniture_product_id + ';1;' + code);
		tracker.setTrackVar('products');

		tracker.send("BBDN Deal Click");

		this.trackLinkShareClick('bbdn');
		
		// Unset the purchaseID so that it doesn't get retained while clicking
		// any "See All Deals" links.
		tracker.setProperty('purchaseID', '');
	},

	trackPopunderPopped: function() {
		var channel;
		// Appears unnecessary, but this aids JS compression and is thus a Good Thing:
		var tracker = this.tracker;
		if (BookingBuddy.OmnitureConfig) {
			channel = BookingBuddy.OmnitureConfig.channel;
		} else {
			channel = tracker.getProperty('channel');
		}
		tracker.setVariable('eVar35', 'Popunder opened from - ' + channel);

		tracker.send("Popunder popped");
	},

	trackPopunderClick: function() {
		var tracker = this.tracker;

		tracker.setVariable('eVar14', 'Vacation Cross Promotion');

		var channel = 'none';
		if (BookingBuddy.OmnitureConfig.channel) {
			channel = BookingBuddy.OmnitureConfig.channel;
		}

		tracker.send("BB Deal Popunder Click");
	},

	/**
	 * Set a users entry time and the number of days since their last
	 * visit (if any). Only sets the variables to be tracked with the next
	 * call, doesn't actually make a call.
	 *
	 * @return void
	 */
	trackEntryTime: function() {
		var tracker = this.tracker;

		if (BookingBuddy.getCookie('entry_time') != 'time') {
			tracker.setVariable('eVar11', BookingBuddy.getESTHour());
			tracker.setTrackVar('eVar11');
		}

		BookingBuddy.createCookie('entry_time', 'time');

		if (BookingBuddy.getCookie('last_visited_sent') != 1) {
			var time = new Date();

			if (BookingBuddy.getCookie('last_visited')) {
				var ms_diff = time.getTime() - BookingBuddy.getCookie('last_visited');
				var days_diff = Math.floor(ms_diff / 86400000);
				tracker.setVariable('eVar28', days_diff);
			}

			BookingBuddy.createCookie('last_visited', time.getTime(), 525600);
		}

		BookingBuddy.createCookie('last_visited_sent', 1, 30);
	},

	setupOmniture: function(account){
		//Global scope
		s=s_gi(account);

		domainName = document.domain.match(/\.([a-z0-9-]+\.[a-z0-9-]+)$/i);
		if (domainName && (domainName[1].length < 8)) {
			domainName = document.domain.match(/\.([a-z0-9-]+\.[a-z0-9-]+\.[a-z0-9-]+)$/i);
		}
		domainName = domainName ? domainName[1] : 'bookingbuddy.com';

		/************************** CONFIG SECTION **************************/
		/* You may add or alter any code config here. */
		/* Link Tracking Config */
		s.trackDownloadLinks=false;
		s.trackExternalLinks=true;
		s.trackInlineStats=true;
		s.linkDownloadFileTypes="exe,zip,wav,mp3,mov,mpg,avi,wmv,doc,pdf,xls";
		s.linkInternalFilters="javascript:,slimg.com," + domainName;
		s.linkLeaveQueryString=false;
		s.linkTrackVars="None";
		s.linkTrackEvents="None";

		/* WARNING: Changing any of the below variables will cause drastic
           changes to how your visitor data is collected.  Changes should only be
           made when instructed to do so by your account manager.
        */
		s.dc=112;
		s.vmk="485017AC";

		/* E-commerce Config */
		s.currencyCode="USD";
		s.eVarCFG="";

		/* Set number of dots in host, so that cookies are set correctly */
		s.cookieDomainPeriods="2";
		s.fpCookieDomainPeriods="2";
		if(domainName.indexOf('.co.uk')>-1) {
			s.cookieDomainPeriods="3";
			s.fpCookieDomainPeriods="3";
		}

		/* Plugin Config */
		s.usePlugins=true;
		//Global scope
		s_doPlugins = function(s) {
			/* Add calls to plugins here */

			var evar = '';

			// Write the value of pageName to eVar22 only once per session
			evar = s.getValOnce('eVar22', 'e_Var22',0);
			if (evar) {
				s.eVar22 = s.pageName;
			}

			// Attempt to get the source from the cookie
			var source = BookingBuddy.User.getSource('source');
			// Fall back on the query param, stripping any invalid chars
			if (!source) {
				source = s.getQueryParam('source').replace(/[^\w\-\.]/, '');
			}

			s.prop10 = source;

			evar = s.getValOnce(source, 's_p1_s_campaign');
			if (evar) {
				s.campaign = evar;
			}

			evar = s.getValOnce(source, 's_p1_s_eVar7');
			if (evar) {
				s.eVar7 = evar;
			}

			evar = s.getValOnce(source, 's_p1_s_eVar9');
			if (evar) {
				s.eVar9 = evar;
			}

			evar = s.getValOnce(s.getQueryParam('taparam').replace(/[^\w\-\.]/, ''), 's_p1_s_eVar29');
			if (evar) {
				s.eVar29 = evar;
			}

			evar = s.getValOnce(s.getQueryParam('traqparam'), 's_p1_s_eVar31');
			if (evar) {
				s.eVar31 = evar;
			}

			// pubid param (should be  a publisher id value passed in  by BBDN)
			evar = s.getValOnce(s.getQueryParam('pubid'), 's_p1_s_eVar39');
			if (evar) {
				s.eVar39 = evar;
			}

			evar = s.getValOnce(s.getQueryParam('supmt'), 's_p1_s_eVar36');
			if (evar) {
				s.eVar36 = evar;
			}
		};
		s.doPlugins=s_doPlugins;

		/* Wrapper for G code event tracking.  If the BB Demo is ever updated
           to H code, this can go away. */
		sendAnalyticsEvent = function() {
			s.pageName = s_pageName ? s_pageName : null;
			s.eVar10 = s_eVar10 ? s_eVar10 : null;
			if (s_pageName || s_eVar10) {
				s.t();
			}
		};

		/************************** PLUGINS SECTION *************************/
		/* You may insert any plugins you wish to use here.                 */
		/*
         * Plugin: getValOnce 0.2 - get a value once per session or number of days
         */
		s.getValOnce=new Function("v","c","e",""
								  +"var s=this,k=s.c_r(c),a=new Date;e=e?e:0;"
								  +"if(v){"
								  +"a.setTime(a.getTime()+e*86400000);"
								  +"s.c_w(c,v,e?a:0);"
								  +"}"
								  +"return v==k?'':v"
								 );

		/*
         * Plugin: getQueryParam 2.1 - return query string parameter(s)
         */
		/*********************************************************************
        * Function getQueryParam(p,d,u): Returns the query string parameter
        *                 values for the parameters specified in p. If p is a
        *                 list of names and multiple values are found, d
        *                 separates the values found. If multiple values are
        *                 found they are returned in the order in which they
        *                 are specified in p.
        *
        *     p = comma delimited list of case insensitive query string
        *         parameter names
        *     d = (optional) delimiter used to separate query string parameter
        *         values if multiple values are found. If omitted and multiple
        *         parameters from p are found, the strings are appended to
        *         eachother without a delimiter.
        *     u = (optional) URL to take query string from. If omitted,
        *         pageURL or window.location is used. If 'f', the
        *         top-most frameset URL is used (in case you want to use the
        *         URL from the address bar and the code is inside a frame).
        *
        * Returns:
        *     - The query string parameter specified
        *     - 'True' if the query string parameter exists without a value
        *     - A delimited list of values if multiple parameters are found
        *********************************************************************/
		s.getQueryParam=new Function("p","d","u",""
									 // object, return value, temp variables
									 +"var s=this,v='',i,t;"
									 +"d=d?d:'';"

									 // u is the URL passed, the current URL or the top frameset URL
									 +"u=u?u:(s.pageURL?s.pageURL:s.wd.location);"
									 +"if(u=='f')u=s.gtfs().location;"

									 // for each value in p
									 +"while(p){"
									 // get the index of the comma
									 +"i=p.indexOf(',');"
									 +"i=i<0?p.length:i;"

									 // grab the parameter value associated with the first name
									 +"t=s.p_gpv(p.substring(0,i),u+'');"

									 // if p value was found, add to running v with delimiter
									 // only add the delimiter if v has a value
									 +"if(t)v+=v?d+t:t;"

									 // take the first parameter off the list
									 +"p=p.substring(i==p.length?i:i+1)"

									 +"}"

									 +"return v"
									);

		/*********************************************************************
        * Function p_gpv(k): Get Parameter Value returns the value of the
        *                 query string parameter, k, 'True' or ''.
        *
        *     k = query string parameter name (case insensitive)
        *     u = URL to take query string from
        *
        * Returns:
        *     - The query string parameter specified
        *     - 'True' if the query string parameter exists without a value
        *     - empty string
        *********************************************************************/
		s.p_gpv=new Function("k","u",""
							 // value from key/value pair
							 +"var s=this,v='',i=u.indexOf('?'),q;"

							 // if k (key/value pair) and a query string exists in the URL
							 +"if(k&&i>-1){"

							 // q is query string without the question mark
							 +"q=u.substring(i+1);"

							 // s.pt with p_gvf finds value associated with k
							 +"v=s.pt(q,'&','p_gvf',k)"

							 +"}"

							 // return value
							 +"return v"
							);

		/*********************************************************************
        * Function p_gvf(t,k): Get Parameter Value Function returns the value
        *                 of the query string parameter, k, 'True' or ''.
        *
        *     t = query string token (one of the name value pairs)
        *     k = query string parameter name (case insensitive)
        *
        * Returns:
        *     - The query string parameter specified
        *     - 'True' if the query string parameter exists without a value
        *     - empty string
        *********************************************************************/
		s.p_gvf=new Function("t","k",""

							 // check for a name=value pair (in case of && in the query string)
							 +"if(t){"

							 // p = parameter name, v = parameter value or 'True' if no value exists
							 +"var s=this,"
							 +"i=t.indexOf('='),"
							 +"p=i<0?t:t.substring(0,i),"
							 +"v=i<0?'True':t.substring(i+1);"

		// if the p does not equal k, set v to ''
							 +"if(p.toLowerCase()==k.toLowerCase())"
							 +"return s.epa(v)"
							 +"}"

							 // return the URL decoded value
							 +"return ''"
							);
	}
}).init();

/************* DO NOT ALTER ANYTHING BELOW THIS LINE ! **************/
var s_objectID;function s_c2fe(f){var x='',s=0,e,a,b,c;while(1){e=
f.indexOf('"',s);b=f.indexOf('\\',s);c=f.indexOf("\n",s);if(e<0||(b>=
0&&b<e))e=b;if(e<0||(c>=0&&c<e))e=c;if(e>=0){x+=(e>s?f.substring(s,e):
'')+(e==c?'\\n':'\\'+f.substring(e,e+1));s=e+1}else return x
+f.substring(s)}return f}function s_c2fa(f){var s=f.indexOf('(')+1,e=
f.indexOf(')'),a='',c;while(s>=0&&s<e){c=f.substring(s,s+1);if(c==',')
a+='","';else if(("\n\r\t ").indexOf(c)<0)a+=c;s++}return a?'"'+a+'"':
a}function s_c2f(cc){cc=''+cc;var fc='var f=new Function(',s=
cc.indexOf(';',cc.indexOf('{')),e=cc.lastIndexOf('}'),o,a,d,q,c,f,h,x
fc+=s_c2fa(cc)+',"var s=new Object;';c=cc.substring(s+1,e);s=
c.indexOf('function');while(s>=0){d=1;q='';x=0;f=c.substring(s);a=
s_c2fa(f);e=o=c.indexOf('{',s);e++;while(d>0){h=c.substring(e,e+1);if(
q){if(h==q&&!x)q='';if(h=='\\')x=x?0:1;else x=0}else{if(h=='"'||h=="'"
)q=h;if(h=='{')d++;if(h=='}')d--}if(d>0)e++}c=c.substring(0,s)
+'new Function('+(a?a+',':'')+'"'+s_c2fe(c.substring(o+1,e))+'")'
+c.substring(e+1);s=c.indexOf('function')}fc+=s_c2fe(c)+';return s");'
eval(fc);return f}function s_gi(un,pg,ss){var c="function s_c(un,pg,s"
+"s){var s=this;s.wd=window;if(!s.wd.s_c_in){s.wd.s_c_il=new Array;s."
+"wd.s_c_in=0;}s._il=s.wd.s_c_il;s._in=s.wd.s_c_in;s._il[s._in]=s;s.w"
+"d.s_c_in++;s.m=function(m){return (''+m).indexOf('{')<0};s.fl=funct"
+"ion(x,l){return x?(''+x).substring(0,l):x};s.co=function(o){if(!o)r"
+"eturn o;var n=new Object,x;for(x in o)if(x.indexOf('select')<0&&x.i"
+"ndexOf('filter')<0)n[x]=o[x];return n};s.num=function(x){x=''+x;for"
+"(var p=0;p<x.length;p++)if(('0123456789').indexOf(x.substring(p,p+1"
+"))<0)return 0;return 1};s.rep=function(x,o,n){var i=x.indexOf(o);wh"
+"ile(x&&i>=0){x=x.substring(0,i)+n+x.substring(i+o.length);i=x.index"
+"Of(o,i+n.length)}return x};s.ape=function(x){var s=this,h='01234567"
+"89ABCDEF',i,c=s.charSet,n,l,e,y='';c=c?c.toUpperCase():'';if(x){x='"
+"'+x;if(c=='AUTO'&&('').charCodeAt){for(i=0;i<x.length;i++){c=x.subs"
+"tring(i,i+1);n=x.charCodeAt(i);if(n>127){l=0;e='';while(n||l<4){e=h"
+".substring(n%16,n%16+1)+e;n=parseInt(n/16);l++}y+='%u'+e}else if(c="
+"='+')y+='%2B';else y+=escape(c)}x=y}else{x=x?s.rep(escape(''+x),'+'"
+",'%2B'):x;if(x&&c&&s.em==1&&x.indexOf('%u')<0&&x.indexOf('%U')<0){i"
+"=x.indexOf('%');while(i>=0){i++;if(h.substring(8).indexOf(x.substri"
+"ng(i,i+1).toUpperCase())>=0)return x.substring(0,i)+'u00'+x.substri"
+"ng(i);i=x.indexOf('%',i)}}}}return x};s.epa=function(x){var s=this;"
+"return x?unescape(s.rep(''+x,'+',' ')):x};s.pt=function(x,d,f,a){va"
+"r s=this,t=x,z=0,y,r;while(t){y=t.indexOf(d);y=y<0?t.length:y;t=t.s"
+"ubstring(0,y);r=s.m(f)?s[f](t,a):f(t,a);if(r)return r;z+=y+d.length"
+";t=x.substring(z,x.length);t=z<x.length?t:''}return ''};s.isf=funct"
+"ion(t,a){var c=a.indexOf(':');if(c>=0)a=a.substring(0,c);if(t.subst"
+"ring(0,2)=='s_')t=t.substring(2);return (t!=''&&t==a)};s.fsf=functi"
+"on(t,a){var s=this;if(s.pt(a,',','isf',t))s.fsg+=(s.fsg!=''?',':'')"
+"+t;return 0};s.fs=function(x,f){var s=this;s.fsg='';s.pt(x,',','fsf"
+"',f);return s.fsg};s.c_d='';s.c_gdf=function(t,a){var s=this;if(!s."
+"num(t))return 1;return 0};s.c_gd=function(){var s=this,d=s.wd.locat"
+"ion.hostname,n=s.fpCookieDomainPeriods,p;if(!n)n=s.cookieDomainPeri"
+"ods;if(d&&!s.c_d){n=n?parseInt(n):2;n=n>2?n:2;p=d.lastIndexOf('.');"
+"if(p>=0){while(p>=0&&n>1){p=d.lastIndexOf('.',p-1);n--}s.c_d=p>0&&s"
+".pt(d,'.','c_gdf',0)?d.substring(p):d}}return s.c_d};s.c_r=function"
+"(k){var s=this;k=s.ape(k);var c=' '+s.d.cookie,i=c.indexOf(' '+k+'="
+"'),e=i<0?i:c.indexOf(';',i),v=i<0?'':s.epa(c.substring(i+2+k.length"
+",e<0?c.length:e));return v!='[[B]]'?v:''};s.c_w=function(k,v,e){var"
+" s=this,d=s.c_gd(),l=s.cookieLifetime,t;v=''+v;l=l?(''+l).toUpperCa"
+"se():'';if(e&&l!='SESSION'&&l!='NONE'){t=(v!=''?parseInt(l?l:0):-60"
+");if(t){e=new Date;e.setTime(e.getTime()+(t*1000))}}if(k&&l!='NONE'"
+"){s.d.cookie=k+'='+s.ape(v!=''?v:'[[B]]')+'; path=/;'+(e&&l!='SESSI"
+"ON'?' expires='+e.toGMTString()+';':'')+(d?' domain='+d+';':'');ret"
+"urn s.c_r(k)==v}return 0};s.eh=function(o,e,r,f){var s=this,b='s_'+"
+"e+'_'+s._in,n=-1,l,i,x;if(!s.ehl)s.ehl=new Array;l=s.ehl;for(i=0;i<"
+"l.length&&n<0;i++){if(l[i].o==o&&l[i].e==e)n=i}if(n<0){n=i;l[n]=new"
+" Object}x=l[n];x.o=o;x.e=e;f=r?x.b:f;if(r||f){x.b=r?0:o[e];x.o[e]=f"
+"}if(x.b){x.o[b]=x.b;return b}return 0};s.cet=function(f,a,t,o,b){va"
+"r s=this,r;if(s.apv>=5&&(!s.isopera||s.apv>=7))eval('try{r=s.m(f)?s"
+"[f](a):f(a)}catch(e){r=s.m(t)?s[t](e):t(e)}');else{if(s.ismac&&s.u."
+"indexOf('MSIE 4')>=0)r=s.m(b)?s[b](a):b(a);else{s.eh(s.wd,'onerror'"
+",0,o);r=s.m(f)?s[f](a):f(a);s.eh(s.wd,'onerror',1)}}return r};s.gtf"
+"set=function(e){var s=this;return s.tfs};s.gtfsoe=new Function('e',"
+"'var s=s_c_il['+s._in+'];s.eh(window,\"onerror\",1);s.etfs=1;var c="
+"s.t();if(c)s.d.write(c);s.etfs=0;return true');s.gtfsfb=function(a)"
+"{return window};s.gtfsf=function(w){var s=this,p=w.parent,l=w.locat"
+"ion;s.tfs=w;if(p&&p.location!=l&&p.location.host==l.host){s.tfs=p;r"
+"eturn s.gtfsf(s.tfs)}return s.tfs};s.gtfs=function(){var s=this;if("
+"!s.tfs){s.tfs=s.wd;if(!s.etfs)s.tfs=s.cet('gtfsf',s.tfs,'gtfset',s."
+"gtfsoe,'gtfsfb')}return s.tfs};s.mr=function(sess,q,ta){var s=this,"
+"dc=s.dc,t1=s.trackingServer,t2=s.trackingServerSecure,ns=s.visitorN"
+"amespace,unc=s.rep(s.fun,'_','-'),imn='s_i_'+s.fun,im,b,e,rs='http'"
+"+(s.ssl?'s':'')+'://'+(t1?(s.ssl&&t2?t2:t1):((ns?ns:(s.ssl?'102':un"
+"c))+'.'+(s.dc?s.dc:112)+'.2o7.net'))+'/b/ss/'+s.un+'/1/H.10-Pdvu-2/"
+"'+sess+'?[AQB]&ndh=1'+(q?q:'')+(s.q?s.q:'')+'&[AQE]';if(s.isie&&!s."
+"ismac){if(s.apv>5.5)rs=s.fl(rs,4095);else rs=s.fl(rs,2047)}if(s.d.i"
+"mages&&s.apv>=3&&(!s.isopera||s.apv>=7)&&(s.ns6<0||s.apv>=6.1)){"
+"im=s.wd[imn]=new Image;im.src=rs;if(rs.indexOf('&p"
+"e=')>=0&&(!ta||ta=='_self'||ta=='_top'||(s.wd.name&&ta==s.wd.name))"
+"){b=e=new Date;while(e.getTime()-b.getTime()<500)e=new Date}return "
+"''}return '<im'+'g sr'+'c=\"'+rs+'\" width=1 height=1 border=0 alt="
+"\"\">'};s.gg=function(v){var s=this;return s.wd['s_'+v]};s.glf=func"
+"tion(t,a){if(t.substring(0,2)=='s_')t=t.substring(2);var s=this,v=s"
+".gg(t);if(v)s[t]=v};s.gl=function(v){var s=this;if(s.pg)s.pt(v,',',"
+"'glf',0)};s.gv=function(v){var s=this;return s['vpm_'+v]?s['vpv_'+v"
+"]:(s[v]?s[v]:'')};s.havf=function(t,a){var s=this,b=t.substring(0,4"
+"),x=t.substring(4),n=parseInt(x),k='g_'+t,m='vpm_'+t,q=t,v=s.linkTr"
+"ackVars,e=s.linkTrackEvents;s[k]=s.gv(t);if(s.lnk||s.eo){v=v?v+','+"
+"s.vl_l:'';if(v&&!s.pt(v,',','isf',t))s[k]='';if(t=='events'&&e)s[k]"
+"=s.fs(s[k],e)}s[m]=0;if(t=='visitorID')q='vid';else if(t=='pageURL'"
+"){q='g';s[k]=s.fl(s[k],255)}else if(t=='referrer'){q='r';s[k]=s.fl("
+"s[k],255)}else if(t=='vmk')q='vmt';else if(t=='charSet'){q='ce';if("
+"s[k]&&s[k].toUpperCase()=='AUTO')s[k]='ISO8859-1';else if(s[k]&&s.e"
+"m==2)s[k]='UTF-8'}else if(t=='visitorNamespace')q='ns';else if(t=='"
+"cookieDomainPeriods')q='cdp';else if(t=='cookieLifetime')q='cl';els"
+"e if(t=='variableProvider')q='vvp';else if(t=='currencyCode')q='cc'"
+";else if(t=='channel')q='ch';else if(t=='transactionID')q='xact';el"
+"se if(t=='campaign')q='v0';else if(s.num(x)){if(b=='prop')q='c'+n;e"
+"lse if(b=='eVar')q='v'+n;else if(b=='hier'){q='h'+n;s[k]=s.fl(s[k],"
+"255)}}if(s[k]&&t!='linkName'&&t!='linkType')s.qav+='&'+q+'='+s.ape("
+"s[k]);return ''};s.hav=function(){var s=this;s.qav='';s.pt(s.vl_t,'"
+",','havf',0);return s.qav};s.lnf=function(t,h){t=t?t.toLowerCase():"
+"'';h=h?h.toLowerCase():'';var te=t.indexOf('=');if(t&&te>0&&h.index"
+"Of(t.substring(te+1))>=0)return t.substring(0,te);return ''};s.ln=f"
+"unction(h){var s=this,n=s.linkNames;if(n)return s.pt(n,',','lnf',h)"
+";return ''};s.ltdf=function(t,h){t=t?t.toLowerCase():'';h=h?h.toLow"
+"erCase():'';var qi=h.indexOf('?');h=qi>=0?h.substring(0,qi):h;if(t&"
+"&h.substring(h.length-(t.length+1))=='.'+t)return 1;return 0};s.lte"
+"f=function(t,h){t=t?t.toLowerCase():'';h=h?h.toLowerCase():'';if(t&"
+"&h.indexOf(t)>=0)return 1;return 0};s.lt=function(h){var s=this,lft"
+"=s.linkDownloadFileTypes,lef=s.linkExternalFilters,lif=s.linkIntern"
+"alFilters;lif=lif?lif:s.wd.location.hostname;h=h.toLowerCase();if(s"
+".trackDownloadLinks&&lft&&s.pt(lft,',','ltdf',h))return 'd';if(s.tr"
+"ackExternalLinks&&(lef||lif)&&(!lef||s.pt(lef,',','ltef',h))&&(!lif"
+"||!s.pt(lif,',','ltef',h)))return 'e';return ''};s.lc=new Function("
+"'e','var s=s_c_il['+s._in+'],b=s.eh(this,\"onclick\");s.lnk=s.co(th"
+"is);s.t();s.lnk=0;if(b)return this[b](e);return true');s.bc=new Fun"
+"ction('e','var s=s_c_il['+s._in+'],f;if(s.d&&s.d.all&&s.d.all.cppXY"
+"ctnr)return;s.eo=e.srcElement?e.srcElement:e.target;eval(\"try{if(s"
+".eo&&(s.eo.tagName||s.eo.parentElement||s.eo.parentNode))s.t()}catc"
+"h(f){}\");s.eo=0');s.ot=function(o){var a=o.type,b=o.tagName;return"
+" (a&&a.toUpperCase?a:b&&b.toUpperCase?b:o.href?'A':'').toUpperCase("
+")};s.oid=function(o){var s=this,t=s.ot(o),p=o.protocol,c=o.onclick,"
+"n='',x=0;if(!o.s_oid){if(o.href&&(t=='A'||t=='AREA')&&(!c||!p||p.to"
+"LowerCase().indexOf('javascript')<0))n=o.href;else if(c){n=s.rep(s."
+"rep(s.rep(s.rep(''+c,\"\\r\",''),\"\\n\",''),\"\\t\",''),' ','');x="
+"2}else if(o.value&&(t=='INPUT'||t=='SUBMIT')){n=o.value;x=3}else if"
+"(o.src&&t=='IMAGE')n=o.src;if(n){o.s_oid=s.fl(n,100);o.s_oidt=x}}re"
+"turn o.s_oid};s.rqf=function(t,un){var s=this,e=t.indexOf('='),u=e>"
+"=0?','+t.substring(0,e)+',':'';return u&&u.indexOf(','+un+',')>=0?s"
+".epa(t.substring(e+1)):''};s.rq=function(un){var s=this,c=un.indexO"
+"f(','),v=s.c_r('s_sq'),q='';if(c<0)return s.pt(v,'&','rqf',un);retu"
+"rn s.pt(un,',','rq',0)};s.sqp=function(t,a){var s=this,e=t.indexOf("
+"'='),q=e<0?'':s.epa(t.substring(e+1));s.sqq[q]='';if(e>=0)s.pt(t.su"
+"bstring(0,e),',','sqs',q);return 0};s.sqs=function(un,q){var s=this"
+";s.squ[un]=q;return 0};s.sq=function(q){var s=this,k='s_sq',v=s.c_r"
+"(k),x,c=0;s.sqq=new Object;s.squ=new Object;s.sqq[q]='';s.pt(v,'&',"
+"'sqp',0);s.pt(s.un,',','sqs',q);v='';for(x in s.squ)s.sqq[s.squ[x]]"
+"+=(s.sqq[s.squ[x]]?',':'')+x;for(x in s.sqq)if(x&&s.sqq[x]&&(x==q||"
+"c<2)){v+=(v?'&':'')+s.sqq[x]+'='+s.ape(x);c++}return s.c_w(k,v,0)};"
+"s.wdl=new Function('e','var s=s_c_il['+s._in+'],r=true,b=s.eh(s.wd,"
+"\"onload\"),i,o,oc;if(b)r=this[b](e);for(i=0;i<s.d.links.length;i++"
+"){o=s.d.links[i];oc=o.onclick?\"\"+o.onclick:\"\";if((oc.indexOf(\""
+"s_gs(\")<0||oc.indexOf(\".s_oc(\")>=0)&&oc.indexOf(\".tl(\")<0)s.eh"
+"(o,\"onclick\",0,s.lc);}return r');s.wds=function(){var s=this;if(s"
+".apv>3&&(!s.isie||!s.ismac||s.apv>=5)){if(s.b&&s.b.attachEvent)s.b."
+"attachEvent('onclick',s.bc);else if(s.b&&s.b.addEventListener)s.b.a"
+"ddEventListener('click',s.bc,false);else s.eh(s.wd,'onload',0,s.wdl"
+")}};s.vs=function(x){var s=this,v=s.visitorSampling,g=s.visitorSamp"
+"lingGroup,k='s_vsn_'+s.un+(g?'_'+g:''),n=s.c_r(k),e=new Date,y=e.ge"
+"tYear();e.setYear(y+10+(y<1900?1900:0));if(v){v*=100;if(!n){if(!s.c"
+"_w(k,x,e))return 0;n=x}if(n%10000>v)return 0}return 1};s.dyasmf=fun"
+"ction(t,m){if(t&&m&&m.indexOf(t)>=0)return 1;return 0};s.dyasf=func"
+"tion(t,m){var s=this,i=t?t.indexOf('='):-1,n,x;if(i>=0&&m){var n=t."
+"substring(0,i),x=t.substring(i+1);if(s.pt(x,',','dyasmf',m))return "
+"n}return 0};s.uns=function(){var s=this,x=s.dynamicAccountSelection"
+",l=s.dynamicAccountList,m=s.dynamicAccountMatch,n,i;s.un.toLowerCas"
+"e();if(x&&l){if(!m)m=s.wd.location.host;if(!m.toLowerCase)m=''+m;l="
+"l.toLowerCase();m=m.toLowerCase();n=s.pt(l,';','dyasf',m);if(n)s.un"
+"=n}i=s.un.indexOf(',');s.fun=i<0?s.un:s.un.substring(0,i)};s.sa=fun"
+"ction(un){var s=this;s.un=un;if(!s.oun)s.oun=un;else if((','+s.oun+"
+"',').indexOf(un)<0)s.oun+=','+un;s.uns()};s.t=function(){var s=this"
+",trk=1,tm=new Date,sed=Math&&Math.random?Math.floor(Math.random()*1"
+"0000000000000):tm.getTime(),sess='s'+Math.floor(tm.getTime()/108000"
+"00)%10+sed,yr=tm.getYear(),vt=tm.getDate()+'/'+tm.getMonth()+'/'+(y"
+"r<1900?yr+1900:yr)+' '+tm.getHours()+':'+tm.getMinutes()+':'+tm.get"
+"Seconds()+' '+tm.getDay()+' '+tm.getTimezoneOffset(),tfs=s.gtfs(),t"
+"a='',q='',qs='';s.gl(s.vl_g);s.uns();if(!s.q){var tl=tfs.location,a"
+",o,i,x='',c='',v='',p='',bw='',bh='',j='1.0',k=s.c_w('s_cc','true',"
+"0)?'Y':'N',hp='',ct='',pn=0,ps;if(String&&String.prototype){j=\"1.1"
+"\";if(j.match){j=\"1.2\";if(tm.setUTCDate){j=\"1.3\";if(s.isie&&s.i"
+"smac&&s.apv>=5)j=\"1.4\";if(pn.toPrecision){j=\"1.5\";a=new Array;i"
+"f(a.forEach){j=\"1.6\";i=0;o=new Object;eval(\"try{i=new Iterator(o"
+")}catch(e){}\");if(i&&i.next)j=\"1.7\"}}}}}if(s.apv>=4)x=screen.wid"
+"th+'x'+screen.height;if(s.isns||s.isopera){if(s.apv>=3){v=s.n.javaE"
+"nabled()?'Y':'N';if(s.apv>=4){c=screen.pixelDepth;bw=s.wd.innerWidt"
+"h;bh=s.wd.innerHeight;}}s.pl=s.n.plugins}else if(s.isie){if(s.apv>="
+"4){v=s.n.javaEnabled()?'Y':'N';c=screen.colorDepth;if(s.apv>=5){bw="
+"s.d.documentElement.offsetWidth;bh=s.d.documentElement.offsetHeight"
+";if(!s.ismac&&s.b){eval(\"try{s.b.addBehavior('#default#homePage');"
+"hp=s.b.isHomePage(tl)?'Y':'N'}catch(e){}\");eval(\"try{s.b.addBehav"
+"ior('#default#clientCaps');ct=s.b.connectionType}catch(e){}\")}}}el"
+"se r=''}if(s.pl)while(pn<s.pl.length&&pn<30){ps=s.fl(s.pl[pn].name,"
+"100)+';';if(p.indexOf(ps)<0)p+=ps;pn++}s.q=(x?'&s='+s.ape(x):'')+(c"
+"?'&c='+s.ape(c):'')+(j?'&j='+j:'')+(v?'&v='+v:'')+(k?'&k='+k:'')+(b"
+"w?'&bw='+bw:'')+(bh?'&bh='+bh:'')+(ct?'&ct='+s.ape(ct):'')+(hp?'&hp"
+"='+hp:'')+(p?'&p='+s.ape(p):'')}if(s.usePlugins)s.doPlugins(s);var "
+"l=s.wd.location,r=tfs.document.referrer;if(!s.pageURL)s.pageURL=l;i"
+"f(!s.referrer)s.referrer=r;if(s.lnk||s.eo){var o=s.eo?s.eo:s.lnk;if"
+"(!o)return '';var p=s.gv('pageName'),w=1,t=s.ot(o),n=s.oid(o),x=o.s"
+"_oidt,h,l,i,oc;if(s.eo&&o==s.eo){while(o&&!n&&t!='BODY'){o=o.parent"
+"Element?o.parentElement:o.parentNode;if(!o)return '';t=s.ot(o);n=s."
+"oid(o);x=o.s_oidt}oc=o.onclick?''+o.onclick:'';if((oc.indexOf(\"s_g"
+"s(\")>=0&&oc.indexOf(\".s_oc(\")<0)||oc.indexOf(\".tl(\")>=0)return"
+" ''}ta=n?o.target:1;h=o.href?o.href:'';i=h.indexOf('?');h=s.linkLea"
+"veQueryString||i<0?h:h.substring(0,i);l=s.linkName?s.linkName:s.ln("
+"h);t=s.linkType?s.linkType.toLowerCase():s.lt(h);if(t&&(h||l))q+='&"
+"pe=lnk_'+(t=='d'||t=='e'?s.ape(t):'o')+(h?'&pev1='+s.ape(h):'')+(l?"
+"'&pev2='+s.ape(l):'');else trk=0;if(s.trackInlineStats){if(!p){p=s."
+"gv('pageURL');w=0}t=s.ot(o);i=o.sourceIndex;if(s.gg('objectID')){n="
+"s.gg('objectID');x=1;i=1}if(p&&n&&t)qs='&pid='+s.ape(s.fl(p,255))+("
+"w?'&pidt='+w:'')+'&oid='+s.ape(s.fl(n,100))+(x?'&oidt='+x:'')+'&ot="
+"'+s.ape(t)+(i?'&oi='+i:'')}}if(!trk&&!qs)return '';if(s.p_r)s.p_r()"
+";var code='';if(trk&&s.vs(sed))code=s.mr(sess,(vt?'&t='+s.ape(vt):'"
+"')+s.hav()+q+(qs?qs:s.rq(s.un)),ta);s.sq(trk?'':qs);s.lnk=s.eo=s.li"
+"nkName=s.linkType=s.wd.s_objectID=s.ppu='';if(s.pg)s.wd.s_lnk=s.wd."
+"s_eo=s.wd.s_linkName=s.wd.s_linkType='';return code};s.tl=function("
+"o,t,n){var s=this;s.lnk=s.co(o);s.linkType=t;s.linkName=n;s.t()};s."
+"ssl=(s.wd.location.protocol.toLowerCase().indexOf('https')>=0);s.d="
+"document;s.b=s.d.body;s.n=navigator;s.u=s.n.userAgent;s.ns6=s.u.ind"
+"exOf('Netscape6/');var apn=s.n.appName,v=s.n.appVersion,ie=v.indexO"
+"f('MSIE '),o=s.u.indexOf('Opera '),i;if(v.indexOf('Opera')>=0||o>0)"
+"apn='Opera';s.isie=(apn=='Microsoft Internet Explorer');s.isns=(apn"
+"=='Netscape');s.isopera=(apn=='Opera');s.ismac=(s.u.indexOf('Mac')>"
+"=0);if(o>0)s.apv=parseFloat(s.u.substring(o+6));else if(ie>0){s.apv"
+"=parseInt(i=v.substring(ie+5));if(s.apv>3)s.apv=parseFloat(i)}else "
+"if(s.ns6>0)s.apv=parseFloat(s.u.substring(s.ns6+10));else s.apv=par"
+"seFloat(v);s.em=0;if(String.fromCharCode){i=escape(String.fromCharC"
+"ode(256)).toUpperCase();s.em=(i=='%C4%80'?2:(i=='%U0100'?1:0))}s.sa"
+"(un);s.vl_l='visitorID,vmk,ppu,charSet,visitorNamespace,cookieDomai"
+"nPeriods,cookieLifetime,pageName,pageURL,referrer,currencyCode,purc"
+"haseID';s.vl_t=s.vl_l+',variableProvider,channel,server,pageType,tr"
+"ansactionID,campaign,state,zip,events,products,linkName,linkType';f"
+"or(var n=1;n<51;n++)s.vl_t+=',prop'+n+',eVar'+n+',hier'+n;s.vl_g=s."
+"vl_t+',trackDownloadLinks,trackExternalLinks,trackInlineStats,linkL"
+"eaveQueryString,linkDownloadFileTypes,linkExternalFilters,linkInter"
+"nalFilters,linkNames';s.pg=pg;s.gl(s.vl_g);if(!ss)s.wds()}",
l=window.s_c_il,n=navigator,u=n.userAgent,v=n.appVersion,e=v.indexOf(
'MSIE '),m=u.indexOf('Netscape6/'),a,i,s;if(l)for(i=0;i<l.length;i++){
s=l[i];if(s.oun==un)return s;else if(s.fs(s.oun,un)){s.sa(un);return s
}}if(e>0){a=parseInt(i=v.substring(e+5));if(a>3)a=parseFloat(i)}
else if(m>0)a=parseFloat(u.substring(m+10));else a=parseFloat(v);if(a
>=5&&v.indexOf('Opera')<0&&u.indexOf('Opera')<0){eval(c);return new
s_c(un,pg,ss)}else s=s_c2f(c);return s(un,pg,ss)}function s_co(o){
var s=s_gi("^",1,1);return s.co(o)}function s_gs(un){var s=s_gi(un,1,1
);return s.t()}function s_dc(un){var s=s_gi(un,1);return s.t()}


BookingBuddy.Strings = {
	// This is probably out of date but it'll stay here as a fallback
	// in case a site isn't rendering it as part of page load
	ServerTime: "Tue, 17 Jan 2012 15:16:46 -0500",

	LS: {
		ChooseValidLocation: "Warning! Please choose a valid location.",
		AmbiguousLocation: "Warning! There may be more than one option for the location you entered",
		ViewAirportList: "Or view our airport list",
		NoSuggestions: "No suggestions"
	},

	Validation: {
		SearchTwoHoursAway: "The time you have chosen has passed or is less than 2 hours from now.\nSearch anyway?",
		CorrectFollowing: "Please correct the following:",
		SearchDatePast: "one of your search dates is in the past",
		PickUpAfterDropOff: "Pick-up Date cannot come after Drop-off Date",
		ChooseValidDropOff: "please choose a valid Drop-off Date",
		ChooseValidPickupOff: "please choose a valid Pick-up Date",
		ChoosePickupCity: "please select a Pick-up City",
		CheckInAfterCheckOut: "Check-out Date must come after Check-in Date",
		ChooseDepartureDate: "please choose a valid Departure Date",
		ChooseArrivalDate: "please choose a valid Arrival Date",
		ChooseDestinationCity: "please select a Destination City",
		DepartureDateAfterReturnDate: "Departure Date cannot come after Return Date",
		ChooseReturnDate: "please choose a valid Return Date",
		ChooseDepartureDate: "please choose a valid Departure Date",
		EnterArrivalCity: "please enter an Arrival City or Airport code",
		EnterDepartureCity: "please enter a Departure City or Airport code",
		OneLocationInvalid: "One of your locations is invalid",
		PleaseCorrectFollowing: "Please correct the following:",
		DifferentLocations: "please enter different arrival and departure locations",
		Beyond330Days: "Airline tickets go on sale up to 330 days in advance. Please enter a date before #{days_out} to find a flight."
	},

	LE: {
		GenericError: "Oops, there was a problem processing your request.",
		ChooseLocation: "Please choose a location.",
		ChooseArrivalAndDeparture: "Please choose both a Departure and an Arrival location."
	},

	LT: {
		air: {
			Default: "Going to #{point_1}? Try these searches:",
			Default_From: "Flying from #{point_1}? Try these searches:",
			Default_To: "Flying to #{point_2}? Try these searches:",
			Default_RoundTrip: "Flying from #{point_1} to #{point_2}? Try these searches:"
		},
		hotel: {
			Default: "Visiting #{point_1}? Try these searches:"
		},
		car: {
			Default: "Renting a car in #{point_1}? Try these searches:"
		},
		vacation: {
			Default: "Going to #{point_1}? Try these searches:",
			Default_From: "Vacation from #{point_1}? Try these searches:",
			Default_To: "Vacationing in #{point_2}? Try these searches:"
		},
		vacation_rental: {
			Default: "Visiting #{point_1}? Try these searches:"
		},
		cruise: {
			Default: "Taking a cruise to #{point_1}? Try these searches:"
		},
		ad: {
			Default:
				'<div class="ad_div">' +
				'<div class="BBAdCell BBTargetedAdCell " id="BBAd#{id}" title="#{tracking_name}">' +
				'<input	 id="bb_ad_image_#{id}"  type="button" value="" alt="Search #{display_name}" style="background-image: url(#{logo})" title="#{title}" class="BBInputImage submitTrigger" />' +
				'<input id="bb_ad_button_#{id}" type="submit" value="Search" title="#{title}" class="BBInputButton submitTrigger" />' +
				'</div>' +
				'</div>' ,
			UK_Default:
				'<div class="BBPricedAd">' +
					'<div class="BBAdCell BBTargetedAdCell" id="BBAd#{id}"' +
							'onmouseover="BookingBuddy.UK.toggleTooltip(\'BBAd#{id}\', true);" onmouseout="BookingBuddy.UK.toggleTooltip(\'BBAd#{id}\', false);"> ' +
						'<span class="adId hide">#{id}</span>' +
						'<input type="checkbox" id="check_#{id}" name="#{tracking_name}" class="ad_checkbox BBInputCheckBox adIdCheckBox toggleAdCheck smartElement" #{checked}/> ' +
						'<input id="bb_ad_image_#{id}" type="image" name="nojs_i#{id}" value="Search #{display_name}" alt="Search #{display_name}" src="#{logo}" title="#{title}" class="BBInputImage toggleAdCheck" /> ' +
					'</div>' +
				'</div>',
			UK_Default_Priced:
				'<div class="BBPricedAd">' +
						'<div id="BBAd#{id}_tooltip" class="LTTooltip" style="display: none;"' +
								' onmouseover="BookingBuddy.UK.toggleTooltip(\'BBAd#{id}\', true);" onmouseout="BookingBuddy.UK.toggleTooltip(\'BBAd#{id}\', false);">' +
							'<div class="tooltip_arrow"> </div>' +
							'<div class="tooltip_top"> </div>' +
							'<div class="LTDesc"> ' +
								'<div class="LTDescContent">' +
									'Travel dates:<br />' +
									'Depart: #{depart_date_day}/#{depart_date_month}/#{depart_date_year}<br />' +
									'Return: #{return_date_day}/#{return_date_month}/#{return_date_year}<br />' +
									'<div class="price_found">#{last_updated}<br /></div>' +
									'<a class="LTPricedDeal" onclick="$j.publish(\'/click/ltPricedDeal\', [#{id}, this]);" href="#{redirect_url}r=i#{id}&amp;search_mode=air&amp;departure_date=#{depart_date_day}/#{depart_date_month}/#{depart_date_year}&amp;return_date=#{return_date_day}/#{return_date_month}/#{return_date_year}&amp;search_type=#{search_type}&amp;num_travelers=#{num_travelers}&amp;departure_city=#{air_departure_city}&amp;arrival_city=#{air_arrival_city}&amp;departure_month=#{depart_date_month} #{depart_date_year}&amp;departure_day=#{depart_date_day}&amp;departure_time=#{departure_time}&amp;return_month=#{return_date_month} #{return_date_year}&amp;return_day=#{return_date_day}&amp;return_time=#{return_time}&amp;service_class=#{service_class}" target="_blank"> Show me this deal</a>' +
									'<br />' +
									'<span class="tt_disclaimer"> Note: fares found may no longer be available</span>' +
									'<br />' +
									'<a href="" onclick="$j.publish(\'/click/search/orignalDates\', [#{id}, this]); return false;" target="_blank"> Search my original dates</a>' +
								'</div>' +
							'</div>' +
							'<div class="tooltip_bottom"></div>' +
						'</div> ' +

						'<div class="BBAdCell BBTargeted_pricedAdCell " id="BBAd#{id}" onmouseover="BookingBuddy.UK.toggleTooltip(\'BBAd#{id}\', true);" onmouseout="BookingBuddy.UK.toggleTooltip(\'BBAd#{id}\', false);"> ' +
							'<a target="_blank" class="LTPricedDeal" onclick="$j.publish(\'/click/ltPricedDeal\', [#{id}, this]);" href="#{redirect_url}r=i#{id}&amp;search_mode=air&amp;departure_date=#{depart_date_day}/#{depart_date_month}/#{depart_date_year}&amp;return_date=#{return_date_day}/#{return_date_month}/#{return_date_year}&amp;search_type=#{search_type}&amp;num_travelers=#{num_travelers}&amp;departure_city=#{air_departure_city}&amp;arrival_city=#{air_arrival_city}&amp;departure_month=#{depart_date_month} #{depart_date_year}&amp;departure_day=#{depart_date_day}&amp;departure_time=#{departure_time}&amp;return_month=#{return_date_month} #{return_date_year}&amp;return_day=#{return_date_day}&amp;return_time=#{return_time}&amp;service_class=#{service_class}"><img id="bb_ad_image_#{id}" name="nojs_i#{id}" value="Search #{display_name}" alt="Search #{display_name}" src="#{logo}" title="#{title}" class="BBInputImage" /></a>' +
							'<div class="LTPrice">' +
									'<a class="lt_price" onclick="$j.publish(\'/click/ltPricedDeal\', [#{id}, this]);" href="#{redirect_url}r=i#{id}&amp;search_mode=air&amp;departure_date=#{depart_date_day}/#{depart_date_month}/#{depart_date_year}&amp;return_date=#{return_date_day}/#{return_date_month}/#{return_date_year}&amp;search_type=#{search_type}&amp;num_travelers=#{num_travelers}&amp;departure_city=#{air_departure_city}&amp;arrival_city=#{air_arrival_city}&amp;departure_month=#{depart_date_month} #{depart_date_year}&amp;departure_day=#{depart_date_day}&amp;departure_time=#{departure_time}&amp;return_month=#{return_date_month} #{return_date_year}&amp;return_day=#{return_date_day}&amp;return_time=#{return_time}&amp;service_class=#{service_class}" target="_blank">  #{price}</a>' +
							'</div> ' +
					'</div>' +
			'</div>' ,
                        OMG_Default:
			'<div class="ad_div">' +
			'<div class="BBAdCell BBTargetedAdCell" id="BBAd#{id}">' +
			'<input type="checkbox" class="adIdCheckBox BBAdCheckBox" id="bb_ad_#{id}"/>' +
			'<p class="OMGPartnerName">#{display_name}</p>' +
			'</div>' +
			'</div>'
		}
	},

	RS: {
		air: {
			Default_roundtrip:  "#{c1} to #{c2} (#{d1.month}/#{d1.day}/#{d1.year} - #{d2.month}/#{d2.day}/#{d2.year})",
			Default_oneway:  "#{c1} to #{c2} (#{d1.month}/#{d1.day}/#{d1.year})",
			UK_roundtrip:
				'<li><a onclick="setTimeout(\'omnitureTrackBBRecentSearches()\',4000)" href="javascript:void(0)">' +
				"#{c1} to #{c2}" +
				'</a>&nbsp;&nbsp;' +
				"#{d1.day} #{d1.month} - #{d2.day} #{d2.month}&nbsp;&nbsp;" +
				'<a onclick="setTimeout(\'omnitureTrackBBRecentSearches()\',4000)" href="javascript:void(0)">' +
				"View" +
				'</a>' +
				'</li>',
			UK_oneway:
				'<li><a onclick="setTimeout(\'omnitureTrackBBRecentSearches()\',4000)" href="javascript:void(0)">' +
				"#{c1} to #{c2}" +
				'</a>&nbsp;&nbsp;' +
				"#{d1.month} #{d1.day}&nbsp;&nbsp;" +
				'<a onclick="setTimeout(\'omnitureTrackBBRecentSearches()\',4000)" href="javascript:void(0)">' +
				"View" +
				'</a>' +
				'</li>'
		},
		hotel: {
			Default: "#{c2} (#{d1.month}/#{d1.day}/#{d1.year} - #{d2.month}/#{day_2}/#{d2.year})",
			UK:
				'<li><a onclick="setTimeout(\'omnitureTrackBBRecentSearches()\',4000)" href="javascript:void(0)">' +
				"#{c2}" +
				'</a>&nbsp;&nbsp;' +
				' #{d1.month} #{d1.day} - #{d2.month} #{d2.day}&nbsp;&nbsp;' +
				'<a onclick="setTimeout(\'omnitureTrackBBRecentSearches()\',4000)" href="javascript:void(0)">' +
				"View" +
				'</a>' +
				'</li>'
		},
		car: {
			Default: "#{c2} (#{d1.month}/#{d1.day}/#{d1.year} - #{d2.month}/#{day_2}/#{d2.year})"
		},
		vacation: {
			Default: "#{c1} to #{c2} (#{d1.month}/#{d1.day}/#{d1.year} - #{d2.month}/#{day_2}/#{d2.year})"
		},
		vacation_rental: {
			Default: "#{c2} (#{d1.month}/#{d1.day}/#{d1.year} - #{d2.month}/#{day_2}/#{d2.year})"
		},
		cruise: {
			Default: "#{c3} (#{d3.month}/#{d3.day}/#{d3.year})"
		}
	},

	DHTMLDropdown: {
		PremiumAdTemplate:
			'<div class="premium">' +
			'<div style="background: url(#{logo_url}) no-repeat; display: block; #{css}"></div></div>',
		BasicAdTemplate: '<div class="regular">#{label}</div>',
		PlaceholderAdTemplate: '<div class="regular">------------------------------</div>'
	},

	Deals: {
		DefaultTemplate:
			'<div class="BB-deal #{row_type}">' +
				'<div class="dealType #{deal_type}">&nbsp;</div>' +
				'<div class="price">#{price}</div>' +
				'<div class="destinationName">#{destination_name}</div>' +
				'<div class="title">' +
					'<a href="#{url}" rel="nofolllow" target="_blank"  title="#{title}">#{title}</a>' +
					'<br />' +
					'<span class="source">#{source}</span>' +
				'</div>' +
			'</div>',

		RevSpanTemplate:
			'<div class="BB-deal">' +
				'<span class="price">#{price}</span>' +
				'<span class="destination_name">#{destination_name_folded}</span>' +
				'<span class="span_deal_title">' +
					'<a href="#{url}" rel="nofolllow" target="_blank"  title="#{title}">#{title}</a>' +
					'<br />' +
					'<span class="deal_source">#{source}</span>' +
				'</span>' +
			'</div>',

		RevNarrowTemplate:
			'<div class="BB-deal">' +
				'<span class="price">#{price}</span>' +
				'<span class="destination_name destination_name-narrow">' +
					'<a href="#{url}" rel="nofolllow" target="_blank"  title="#{title}">#{title}</a> - <b>#{destination_name}</b>' +
					'&nbsp;<span class="deal_source">#{source}</span><br />' +
				'</span>' +
			'</div>',

		BottomFeederTemplate:
			'<div class="BB-deal #{row_type}">' +
				'<div class="destinationName">#{destination_name}</div>' +
				'<div class="title"><a href="#{url}" target="_blank"  title="#{title}">#{price} -- #{title}</a></div>' +
				'<div class="source">#{source}</div>' +
				'<div class="rollover" style="display:none">Grab this deal! &gt;&gt;</div>' +
			'</div>',

		SponsoredTemplate:
			'<div class="BB-deal">' +
				'<div class="price">#{price}</div>' +
				'<div class="destination_name">#{destination_name}</div>' +
				'<div class="deal_title"><a href="#{url}" target="_blank" title="#{title}">#{title}</a></div>' +
				'<div class="deal_source">#{source}</div>' +
			'</div>',

		TargetedHotelTemplate:
			'<div class="BB-deal">' +
				'<div class="price">#{price}</div>' +
				'<div class="deal_title"><a href="#{url}" target="_blank" title="#{title}">#{title}</a></div>' +
				'<div class="deal_source">#{source}</div>' +
			'</div>'
	},

	NL: {
		inlineHeadline: "Find Out First When Fares Drop!",
		inlineWrapupHeader: "Thanks for Signing Up",
		inlineWrapupMsg: "Fare Alerts from #{dep_airport} will be on their way to you soon",
		inlineWelcome: "Welcome Back!",
		inlineDepTeaser: 'Alert me when BookingBuddy finds low fares from <span class="nl_location">#{dep_airport}</span>',
		inlineRouteTeaser: 'Alert me when BookingBuddy finds low fares from <span class="nl_location">#{dep_airport} to #{arr_airport}</span>',

		inlineSATeaser: "Invite me to SniqueAway -- free access to top hotels for up to 50% off!",
		inlineSAHeadline: "Save Up to 50% on Top Hotels!",
		inlineSAWrapupHeader: "Your Invite is On the Way!",
		inlineSAWrapupMsg: "Check your inbox to accept the invitation from SniqueAway. You'll become a member of this exclusive site and save up to 50% on top hotels around the world.",

		inlineHotelWatchTeaser: "Send me top #{arrival_city} hotel deals",
		inlineHotelWatchAddTeaser: "Add #{arrival_city} to my Hotel Watch list",
		inlineHotelWatchWrapupHeader: "Hotel savings are on their way!",
		inlineHotelWatchWrapupMsg: "Thanks for signing up!",

		inlineSAPlusHWWrapupHeader: "Hotel savings are on their way!",
		inlineSAPlusHWWrapupMsg: "Thanks for signing up!",
		inlineSAPlusHWWrapupMsgSubText: "The freshest #{arrival_city} hotel deals plus your SniqueAway invitation are on their way to your inbox. Get ready to save big on your next hotel stay!"
	},

	CheckRates: {
		DefaultTemplate:
			'<div class="checkrates_provider check_rates_checkbox_ad ta_checkrates">' +
				'<label>' +
					'<input id="#{name}" class="BBInputCheckBox adIdCheckBox taCheckRates" type="checkbox" name="#{name}" value="on" #{checked} /> #{vendor}' +
				'</label>' +
			'</div>'
    }
};


(function($){

BookingBuddy.bbSubmitHandlers = {

	/**
	 * Creates and returns an object of redirect urls for check rates
	 * 
	 * @param {jquery object}	- form
	 * @param {array}	- contains ad ids 
	 * @returns {object}  ad_id: redirect_url
	 */
	getCheckRatesURL: function($searchForm, checked_ads_ids) {
		var	redirect_urls 	= {},
			ad 				= null,
			redirect 		= '',
			ar_date 		= $searchForm.attr('arrival_date').value.split('/'),
			dp_date 		= $searchForm.attr('departure_date').value.split('/'),
			num_travelers 	= $searchForm.attr('num_travelers').value,
			params 			= {};
		
		if (ar_date.length == 3) {
			params.inMonth = ar_date[0] + ' ' + ar_date[2];
			params.inDay = ar_date[1];
		}

		if (dp_date.length == 3) {
			params.outMonth = dp_date[0] + ' ' + dp_date[2];
			params.outDay = dp_date[1];
		}
		
		if (num_travelers) {
			params.adults = num_travelers;
		}
		
		params = '&' + $.toQueryString(params);
		
		$.each(checked_ads_ids, function(index, ad_id){
			ad 			= TACheckRates.singleton(ad_id);
			redirect 	= decodeURIComponent(ad.getMetaData('external_url')) + params;
			redirect_urls[ad_id] = redirect;
		});
		
		return redirect_urls;
	},

	/**
	 * Returns a form type based on elements on the page
	 * @param {jqueryObject} - $form
	 * @returns {string} form type 
	 */
	getFormType: function($search_form) {

		// Order here is important, since 
		// BookingBuddy.TabbedWindowHandler.tabbed is set based on browser
		if ($search_form.find(".adIdCheckBox").length === 0 ) {
			return 'one_search';

		} else if ($search_form.find(".check_rates_providers .ta_checkrates:visible").length > 0 ) {
			return 'ta_checkrates';	

		} else if (BookingBuddy.TabbedWindowHandler && BookingBuddy.TabbedWindowHandler.tabbed) {
			return 'tabbed_browsing';

		} else {
			return 'mulitple_checked_searches';	
		}

		return 'default';
	},


	/**
	 * Forks off to the correct submission function based on form type
	 * @param {jqueryObject} $search_form
	 * @param {HTMLCollection}  checked
	 */
	submitFormByType: function ($search_form, checked) {
		var BSH 		= BookingBuddy.submitHandlers,
			BBSH 		= BookingBuddy.bbSubmitHandlers,
			form_type 	= BBSH.getFormType($search_form),
			ad 			= {},
			ad_id 		= 0,
			ad_count 	= checked.length,
			id_list   	= [],
			ta_data		= {},
			smart_form  = {};
			
		// if user "created" more prepopwindows than
		// the quantity of searches, remove the window 
		// from the array and close it
		while (BSH.prepopWindows.length > ad_count) {
			// close window object popped from the array
			BSH.prepopWindows.pop().win_obj.close();			
		}

		switch (form_type) {
			case 'ta_checkrates':
				checked.each( function() {
					var adId = $(this).attr('name');
					if (adId) {
						id_list.push(adId);
					}
				});

				// Ta check rates submission
				BBSH.searchCheckRates($search_form, id_list);			
			break;

			case 'one_search':
				smart_form  = $.data($search_form[0], 'smartForm');
				ad_id 		= /\d+/.exec(smart_form.submitTrigger.id);
				id_list.push(ad_id);

				if (BSH.setFormRedirectUrl($search_form)) {
					BBSH.searchAd($search_form, id_list);
				}	
			break;

			case 'tabbed_browsing':
			case 'mulitple_checked_searches':
				checked.each( function() {
					ad_id = /\d+/.exec($(this)[0].id);
					id_list.push(ad_id);
				});

				if (BSH.setFormRedirectUrl($search_form)) {
					if (form_type == 'tabbed_browsing') {
						BBSH.searchTabBrowsing($search_form, id_list);
					} else {
						BBSH.searchAd($search_form, id_list);
					}
				}
			break;

			default: 
				return false;
			break;
		}

		return true;
	},


	/**
	 * BBS general submit handler
	 * This handler takes care of any search that opens a new window
	 * This includes step2, check rates, affiliates, ie6 and 
	 * anything else that opens one or multiple search windows
	 * @param {string} - form name
	 * @param {string} - form target value
	 * @param {object} - event object
	 */
	submitForm: function(form, target, e) {
		if (typeof e !== 'undefined') {
			e.stopPropagation();
			e.preventDefault();
		}

		// defaults 
		form   = form  || BookingBuddy.searchMode + '_bbsearch';
		target = target || '_blank';

		var	$search_form 	= $(form),
			smart_form 		= $.data( form,"smartForm"),
			checked 		= $search_form.find(".adIdCheckBox:visible:checked"),
			BSH 			= BookingBuddy.submitHandlers,
			BBSH 			= BookingBuddy.bbSubmitHandlers;

		smart_form.submitHandlerData["trigger_id"]  = smart_form.submitTrigger.id;
		smart_form.submitHandlerData["submit_type"] = $(smart_form.submitTrigger).hasClass("default") ? "default" : "runSearch";

		if (smart_form.submitHandlerData['submit_type'] == 'default') {
			// BBS-2087: When jquery is upgraded, this will need to change to:
			// $(form).prop("action", window.location.pathname);
			// Otherwise, the form will submit to / instead of the current page

			// Not actually running a search
			// Make sure the form target is the current page
			$(form).attr("action", "");
			BSH.defaultHandler(form, target, e);

		} else {

			BBSH.submitFormByType($search_form, checked);
		} 
	},


	/**
	 * Should open staggered windows for any BBAd based on ids passed in
	 * @requires {BookingBuddy.submitHandlers.checkedMultiWindowOptions}
	 * @param {jquery object} $search_form	- search form
	 * @param {array} checked_ads_ids: 	contains ad ids 
	 */
	searchAd: function($search_form, checked_ads_ids) {	
		var	BSH 			= BookingBuddy.submitHandlers,
			BBSH 			= BookingBuddy.bbSubmitHandlers,
			blocked_ad_ids 	= [],
			track_ids 		= [],
			ad_id 			= '',
			ad_count 		= checked_ads_ids.length,
			form_type 		= BBSH.getFormType($search_form),
			windows 		= []; 
		
		// get amount of windows to be used based on ads
		// to be searched and account for prepopwindows 
		windows = BSH.prepopWindows.length > 0 ? BSH.openMultiWindow(ad_count, BSH.prepopWindows) : BSH.openMultiWindow(ad_count);

		// We reverse the array to open windows in a certain order
		// The first selected ad should open in the window which is 
		// focused and farthest down the page
		windows.reverse();
		
		$.each(windows, function() {
			ad_id = checked_ads_ids.shift();
			
			if (this.win_obj && this.win_name) {

				// if this window is a "prepop" window
				// it'll now get submitted and become a search window
				if (this.win_name.indexOf('prepop') !== -1) {
					BSH.prepopWindows.pop();
				}

				// alter window name
				this.win_name = this.win_obj.name = 'bb_search_' + Number(new Date()) + ad_id;

				BSH.searchWindows.push({
					win_obj: this.win_obj, 
					win_name: this.win_name
				});

				$search_form.attr('r').value = 'i' + ad_id;
				$search_form[0].target = this.win_name;
				$search_form[0].submit();

				track_ids.push(ad_id);
			} else {
				blocked_ad_ids.push(ad_id);
			}
		});

		// Any ad that didnt have a window assigned to it is blocked
		blocked_ad_ids = blocked_ad_ids.concat(checked_ads_ids.reverse());
		
		// show popup blocker if there are blocked IDs.
		if (blocked_ad_ids.length) {
			BBSH.loadBlockedPopup($search_form, blocked_ad_ids, track_ids);
		}

		// Publish form submission
		if (form_type == 'one_search') {
			$.publish('/submit/oneSearchForm', [track_ids, $search_form]);

		} else if (form_type == 'mulitple_checked_searches'){
			$.publish('/submit/multipleSearchesForm', [track_ids, $search_form]);
		}
	},
	
	/**
	 * Check box submission for the Tabbed Browsing Page
	 * Will open a tabbed searches page with a url 
	 * containing all the checked ids 
	 * @param {jqueryObject}	- search_form
	 * @param {array}	- contains ad ids 
	 */
	searchTabBrowsing: function($search_form, checked_ads_ids) {
		// get all checked/enabled form inputs except 
		// for the checkboxes and create an object
		var form_params 	= $search_form.find(':input').not('.BBInputCheckBox').serializeArray(),
			max 			= form_params.length,
			query_string 	= '';

		// ad ids to the r param 
		for (var i=0; i < max ; i++) {
			if (form_params[i].name == "r") {
				form_params[i].value = checked_ads_ids.join();	
			}
		}

		query_string = $.param(form_params);
		BookingBuddy.TabbedWindowHandler.openTabbedWindow(query_string);
	},

	/**
	 * Should open staggered windows for check rates based on ids passed in
	 * @requires {BookingBuddy.submitHandlers.checkedMultiWindowOptions}
	 * @param {jquery object}	- form
	 * @param {array}	- contains ad ids 
	 */
	searchCheckRates: function($search_form, checked_ads_ids) {			
		var BSH 			= BookingBuddy.submitHandlers,
			BBSH 			= BookingBuddy.bbSubmitHandlers,
		 	blocked_ad_ids 	= [],
			track_ids 		= [],
			ad_id 			= '',
			ad_count 		= checked_ads_ids.length,
			redirect_urls 	= BBSH.getCheckRatesURL($search_form, checked_ads_ids),
			windows 		= [];

		// get amount of windows to be used based on ads to be searched
		// and account for prepopwindows 
		windows = BSH.prepopWindows.length > 0 ? BSH.openMultiWindow(ad_count, BSH.prepopWindows) : BSH.openMultiWindow(ad_count);
		
		// reverse the array to open windows in a certain order
		windows.reverse();
				
		$.each(windows, function() {
			ad_id = checked_ads_ids.shift();
			
			if (this.win_obj && this.win_name) {

				// if this window is a "prepop" window
				// it'll now get submitted and become a search window
				if (this.win_name.indexOf('prepop') !== -1) {
					BSH.prepopWindows.pop();
				}

				// alter window name
				this.win_name = this.win_obj.name = 'bb_search_' + Number(new Date()) + ad_id;

				BSH.searchWindows.push({
					win_obj: this.win_obj, 
					win_name: this.win_name
				});

				$search_form.attr('r').value = 'i' + ad_id;
				this.win_obj.location.href = redirect_urls[ad_id];	

				track_ids.push(ad_id);
			} else {
				blocked_ad_ids.push(ad_id);
				
			}
		});
		
		//Any ad that didnt have a window assigned to it is blocked
		blocked_ad_ids = blocked_ad_ids.concat(checked_ads_ids.reverse());
		
		// show popup blocker if there are blocked IDs.
		if (blocked_ad_ids.length) {
			BBSH.loadBlockedPopupCheckRates($search_form, blocked_ad_ids, track_ids);	
		}

		BBSH.resetCheckBoxes($search_form);

		$.publish('/submit/checkRatesForm', [track_ids, $search_form]);
	},

	/**
	 * Gather info for popup blocker popup
	 * @requires {BookingBuddy.submitHandlers.checkedMultiWindowOptions}
	 * @param {jquery object}	- form
	 * @param {array}	- contains ad ids 
	 * @param {array}	- contains ad ids for windows that the user has opened
	 */
	loadBlockedPopupCheckRates: function ($search_form, ad_ids, visited_ids) {
		var ads_info 	= [],
			ad 			= '',
			logo_class 	= 'check_rates';

		//Be very careful when changing ads_info check out the showBlockedPopup function.
		$.each(visited_ids, function(index, ad_id) {
			ad = TACheckRates.singleton(ad_id);	
			ads_info.push({
				ad_id: 			ad_id, 
				logo_class: 	logo_class, 
				display_name: 	ad.getMetaData('display_name'), 
				visited: 		'visited'
			});
		});
		
		$.each(ad_ids, function(index, ad_id) {
			ad = TACheckRates.singleton(ad_id);	
			ads_info.push({
				ad_id: 			ad_id,  
				logo_class: 	logo_class, 
				display_name: 	ad.getMetaData('display_name'), 
				visited: 		''
			});
		});

		BookingBuddy.bbSubmitHandlers.showBlockedPopup($search_form, ads_info);
	},

	
	/**
	 * Gather info for popup blocker popup
	 * @requires {BookingBuddy.submitHandlers.checkedMultiWindowOptions}
	 * @param {jquery object}	- form
	 * @param {array}	- contains ad ids 
	 * @param {array}	- contains ad ids for windows that the user has opened
	 */
	loadBlockedPopup: function ($search_form, ad_ids, visited_ids) {
		var ads_info 	= [],
			ad 			= '';

		$.each(visited_ids, function(index, ad_id) {
			ad = BBAd.singleton(ad_id);	
			ads_info.push({
				ad_id: 			ad_id, 
				logo_class: 	'', 
				logo_url: 		ad.getMetaData('logo'), 
				display_name: 	ad.getMetaData('display_name'), 
				visited: 		'visited' 
			});
		});
		
		$.each(ad_ids, function(index, ad_id) {
			ad = BBAd.singleton(ad_id);
			ads_info.push({
				ad_id: 			ad_id, 
				logo_class: 	'', 
				logo_url: 		ad.getMetaData('logo'), 
				display_name: 	ad.getMetaData('display_name'), 
				visited: 		'' 
			});
		});

		BookingBuddy.bbSubmitHandlers.showBlockedPopup($search_form, ads_info);
	},
	
	/**
	 * PopupBlocker link template
	 */
	linkTemplate: '<div class="popup_logo #{logo_class}" id="logo_#{ad_id}"></div>' +
					'<div class="popup_name #{visited}" id="name_#{ad_id}">#{display_name}</div>',
	
	/**
	 * PopupBlocker output template
	 */
	outputTemplate: '<div class="">' + 
						'<div class="popup_checked_img">' + 
							'<div class="popup_checked_chkmrk #{visited}" id="chkmrk_#{ad_id}"></div>' +
						'</div>' +
					'</div>',

	/**
	 * Ajusts the position of the popupblocker 
	 * based on the latest search window opened
	 * @param {jqueryObject}
	 */
	adjustPopupBlockerPosition: function($dhtml_checkboxes) {
		var BSH 				= BookingBuddy.submitHandlers,
			current_win_left 	= (typeof window.screenLeft  !== 'undefined') ? window.screenLeft : window.screenX,  // current window position
			left 			 	= BSH.checkedMultiWindowOptions.winLeft - current_win_left - $dhtml_checkboxes.width(),
			min_left 			= 6,
			// default top position of the blocker
			top 				= BSH.checkedMultiWindowOptions.winTop;
			
		if (left < min_left) { 
			left = min_left; 
		}

		$dhtml_checkboxes.css( {
			left: left,
			// top is set based on where we 
			// are "scrolling wise" on the window
			top: top + $(window).scrollTop()
		});
	},

	/**
	 * If a search window is already open
	 * @param {number} ad_id: 
	 * @returns boolean  true: if a search window was focused
	 */
	focusedSearchWindow: function(ad_id) {
		var	search_windows	= BookingBuddy.submitHandlers.searchWindows,
			window_count 	= search_windows.length,
			index			= 0;

		for (index = 0; index < window_count; index++) {
			if (search_windows[index].win_name.indexOf(ad_id) !== -1 && !search_windows[index].win_obj.closed) {
				search_windows[index].win_obj.focus();		
				return true;
			}		
		}
	
		return false;
	},

	
	/**
	 * Displays the search blocked popup for check rates and bbad searches
	 * @param {jquery object}	search_form:  - form
	 * @param {array}	ads_info:  - contains objects with data about ads
	 */
	showBlockedPopup: function($search_form, ads_info) {		
		var BSH  		= BookingBuddy.submitHandlers,
			BBSH 		= BookingBuddy.bbSubmitHandlers,
			link_tmpl 	= BBSH.linkTemplate,
			output_tmpl	= BBSH.outputTemplate;

		$.asyncLoad('#dhtml_checkboxes', 'popup_blocked_msg', function(elm_selector) {
			var ad_count 			= ads_info.length,
				$popup_on_logos 	= $('#popup_on_logos'),
				$dhtml_checkboxes 	= $('#dhtml_checkboxes'),
				$checkboxes_success = $('#checkboxes_success'),
				popup 				= new DHTMLPopup($(elm_selector).attr('id')),
				num_visited 		= 0, 
				output_text 		= '',
				logo_url 			= '',
				success 			= 0;

			$popup_on_logos.html('');
			popup.setPageMask('page_mask');

			// Display the popupblocker (centered)
			popup.show();

			BBSH.adjustPopupBlockerPosition($dhtml_checkboxes);
			
			$.each(ads_info, function(index, ad) {			
				if (ad.visited == 'visited') {
			    	num_visited++;
				}

				var ad_id 		= ad.ad_id,
					logo_url 	= ad.logo_url || '',
					$check_mark	= $(),
					$name 		= $(),
					link_text 	= $.tmpl(link_tmpl, ad),
					output_text = $.tmpl(output_tmpl, ad),
					// create submit link on popup blocker div
					link = $('<a />', {
						html: link_text,
						click: function (event) {
							event.preventDefault();
							event.stopPropagation();

							// if the window is already opened 
							// just focus it and return
							if (BBSH.focusedSearchWindow(ad_id)) {
								return;	
							}

							form_type = BBSH.getFormType($search_form);			
							if (form_type === 'ta_checkrates') {
								BBSH.searchCheckRates($search_form, [ad_id]);	
							} else {
								BBSH.searchAd($search_form, [ad_id]);	
							}

							BBSH.adjustPopupBlockerPosition($dhtml_checkboxes);

							$check_mark = $(this).next();
							$name 		= $(this).find('.popup_name');

							// makes sure that the counter does not display 
							// something innacurate (i.e 10 out of 7 sites)
							if (Number($checkboxes_success.html()) < ad_count && $check_mark.is(':hidden')) {
								success = Number($checkboxes_success.html()) + 1;
								$checkboxes_success.html(success + ' ');
							}
					
							$name.addClass('visited');
							$check_mark.addClass('visited');
						}
					});	
				
				output_text = $(output_text);
				$('.popup_checked_img', output_text).prepend(link);

				$popup_on_logos.append(output_text);	
				
				if (logo_url) {
					$('#dhtml_checkboxes #logo_' + ad_id).css('background-image', 'url(' + logo_url + ')');
				}

			});	
			
			$checkboxes_success.html(num_visited + ' ');
			$('#checkboxes_total').html(ad_count +  ' ');

		}, {popup_class: 'bbus'});
	},

	/**
	 * Opens a tabbed search window and proceeds to use the default hander
	 * @param {string} - form name
	 * @param {string} - form target value
	 * @param {object} - event object
	 */
	skipStep2: function(form, target, e) {
		var BBTH 	= BookingBuddy.TabbedWindowHandler,
			BSH 	= BookingBuddy.submitHandlers,
			opened 	= false;

		if (BBTH.tabbed) {
			opened = BBTH.openTabbedWindow($(form).serialize());
			if (opened) {
				opened.focus();
			}
		}

		return BSH.defaultHandler(form, target, e);
	},
	
	/**
	 * Opens a tabbed search window and keeps the page the same
	 *
	 * @param {string} - form name
	 * @param {string} - form target value
	 * @param {object} - event object
	 */
	bbsToTabbed: function(form, target, e) {

		var BBTH 	= BookingBuddy.TabbedWindowHandler,
			BSH 	= BookingBuddy.submitHandlers,
			opened 	= false;

		if (BBTH.tabbed) {
			opened = BBTH.openTabbedWindow($(form).serialize());
			if (opened) {
				opened.focus();
			}
		}

		
		return false;
	},

	/**
	 * Handles opening of the prepop search windows for IE
	 * @returns window  Object or false
	 */
	launchPrepop: function() {
		var BSH  			= BookingBuddy.submitHandlers,
			window_count 	= BSH.prepopWindows.length,
			prepopWindow 	= [],
			win_obj 		= null;

		if (window_count < 2) {

			// open one window on the correct staggered location
			// and load the prepop message into it
			prepopWindow			= BSH.openMultiWindow(1);
			win_obj 				= prepopWindow[0].win_obj;
			win_obj.location.href 	= '/popunder/prepop.php';

			win_obj.blur();

			// adjust the actual window name and the stored window name
			prepopWindow[0].win_name = win_obj.name = 'bb_prepop' + Number(new Date());

			BSH.prepopWindows.push(prepopWindow[0]);

			return win_obj;
		}

		return false;
	},
	
	/**
	 * Handles closing of any leftover prepop windows
	 */
	closePrepops: function() {
		var BSH	 = BookingBuddy.submitHandlers,
			max  = BSH.prepopWindows.length;
			
		for (var i = 0; i < max; i++) {
			BSH.prepopWindows[i].win_obj.close();	
		}

		// reset preopWindows
		BSH.prepopWindows = [];
	},

	/**
	 * Uncheck ad checkboxes for the form passed in
	 * @param {jqueryObject} $search_form
	 */
	resetCheckBoxes: function($search_form) {
		$('.adIdCheckBox', $search_form).attr('checked', false);
	}	

};

})(jQuery);


/**
 * TabBrowsing Splash Page addon 
 * 
 * Allows for a fully customizable DHTML Splash environment to be loaded on top of tabBrowsing.
 * 
 * This feature consists of four objects
 * BBTraqLander
 *   Acts as a controller for all actions relative to the splash screen
 * BBTraqLanderItem
 *   Base splash item class, used for placing divs on the splash srceen
 * BBTraqLanderItem_Title
 *   Splash Item subclass which can modify the document's title
 * BBTraqLanderItem_Factory
 *   Factory object which will build the proper BBTraqLanderItem* object
 *
 * <code>
 * var splash_page = new BBTraqLander({
 *   params: this.options.splashParams,
 *   splash_container: 'splash_container',
 *   page_mask: 'page_mask',
 *   fade (optional): true (if not set defaults to true)
 * });
 * splash_page.display();
 * $j.subscribe(/hide/TraqLander, $.proxy(function () {
 *   // do custom things after the dhtml splash layer has closed
 * }, this));
 * </code>
 * 
 */
var BBTraqLander = function (options) {
	this.elements = [];
	this.options = $j.extend({}, BBTraqLander.defaults, options);
	this.dhtml = DHTMLPopup_Factory.create({type: 'layer', id: this.options.splash_container});
	if (this.options.page_mask) {
		this.dhtml.setPageMask(this.options.page_mask);
		this.dhtml.maskClick(this.options.page_mask);
	}
	this.processParams();
	this.dhtml.base.bind('dhtmlpopup:hide', $j.proxy(this.hide, this));
};

/**
 * Regex pattern to pull out all sequences in the splash params
 * i.e. turn A1B1C1 into A1,B1,C1
 */
BBTraqLander.REGEX_PARAMS = /([A-Z]\d)+?/g;

/**
 * Rexex pattern to pull out the individual elements of a splash param
 * i.e. turn A1 into A,1
 */
BBTraqLander.REGEX_PARAM = /([A-Z])(\d)/;

/**
 * Nested lookup array used to decode splash parameters.
 * param A1 translates into a popup, that is shown and
 * has an id of 'center_popup'.
 *
 * This was put in place so that we could keep the query
 * string short yet have it be very flexible. Say we wanted
 * to have different color side_popup's.  All you would have
 * to do would be to create a new css class for a green one
 * then add something like
 *
 * <code>
 * 	B: {
 *		type: 'popup',
 *		styles: {	
 *			1: {
 *				display: true,
 *				id: 'side_popup',
 *				css_class: 'pink'
 *			},
 *			2: {
 *				display: true,
 *				id: 'side_popup',
 *				css_class: 'green'
 *			}	 
 *		}
 *	},
 * </code>
 */
BBTraqLander.ITEM_OPTIONS = {
	A: {
		type: 'popup',
		styles: {
			1: {
				display: true,
				id: 'CTM_A1'
			},
			2: {
				display: true,
				id: 'CTM_A2'
			},
			3: {
				display: true,
				id: 'CTM_A3'
			},
			4: {
				display: true,
				id: 'CTM_A4'
			}
		}
	},
	B: {
		type: 'popup',
		styles: {
			1: {
				display: true,
				id: 'side_popup',
				css_class: 'pink'
			}
		}
	},
	C: {
		type: 'title',
		styles: {
			1: {
				display: true,
				id: 'page_title',
				enable_scrolling: false
			},
			2: {
				display: true,
				id: 'page_title',
				enable_scrolling: true,
				scroll_delay: 150
			}
		}
	}
};

$j.extend(BBTraqLander, {
	defaults: {
		/**
		 * Defines the construction of the splash screen
		 * in the form of <alpha><numeric><alpha><numeric>... etc
		 * (i.e. A1B1C0 )
		 * This will load the corresponding items form the BBTraqLander.ITEM_OPTIONS arrays.
		 * thus this example will load a BBTraqLanderItem, or sub class, with the following options:
		 *   BBTraqLander.ITEM_OPTIONS.A.styles.1
		 *   BBTraqLander.ITEM_OPTIONS.B.styles.1
		 *   BBTraqLander.ITEM_OPTIONS.C.styles.0
		 * @param {string}
		 */
		params: '',
		
		/**
		 * HTML container id which contains the main dhtml popup content.
		 * all DHTMLPopup events will be bound to this element.
		 *
		 * @param {string} 
		 */
		splash_container: '',

		/**
		 * ID of the div responsible for the modal affect while the splash page is present
		 * (the blacking out of the page)
		 * 
		 * @param {string} <div>
		 */
		page_mask: '',
		
		/**
		 * Causes all the popups to fadein in reverse order (B1 will appear before A1)
		 * 
		 * @param {boolean}
		 */
		fade: true
	},

	prototype: {
		/**
		 * Goes through the splash parameters and builds/adds the
		 * proper BBTraqLanderItems to the class.
		 */
		processParams: function () {
			var matches = this.options.params.match(BBTraqLander.REGEX_PARAMS),
				max = matches ? matches.length : 0,
				i = 0,
				options = {},
				item = null;
			
			for (i = 0; i < max; i++) {
				map = matches[i].match(BBTraqLander.REGEX_PARAM);
				options = {
					param_key: map[1],
					param_code: map[2]
				};
				item = BBTraqLanderItem_Factory.create(options);
				this.addItem(item);
			}
		},

		/**
		 * Adds a, or sublass of a, BBTraqLanderItem object to the elements array.
		 * Popup elements will automatically be added to the dhtml layer and added
		 * to the mask click (clicking on them will close the dhtml layer).
		 *
		 * @param {TabBrowsingSplashItem}
		 */
		addItem: function (item) {
			if (item.options.type == 'popup' && item.options.id && item.options.display) {
				this.dhtml.addElement(item.options.id);
				this.dhtml.maskClick(item.options.id);
			}
			this.elements.push(item);
		},
		
		/**
		 * Display the splash lander.
		 * Prior to showing the dhtml layer each element in the
		 * elements array will have their display() function
		 * called.  This is useful if that element has special things
		 * that need to be done prior to display.
		 *
		 * publishes /show/TraqLander
		 */		
		display: function () {
			$j.each(this.elements, function(index, element) {
				// apply any special display functionality
				element.display();
			});
			// show the dhtml layer use a custom function if available
			this.dhtml.show(this.options.fade);
			
			$j.publish('/show/TraqLander');
		},

		/**
		 * Hide the splash lander.
		 * Each element in the elements array will have their hide()
		 * function called.  This is useful if that element has special
		 * clean up opperations.
		 *
		 * publishes /hide/TraqLander
		 */		
		hide: function () {
			$j.each(this.elements, function(index, element) {
				// apply any special hide functionality
				element.hide();
			});
			$j.publish('/hide/TraqLander');
		}
	}
});

var BBTraqLanderItem = Object.subClass({

	init: function (options) {
		this.options = $j.extend({}, this.defaults, options);
		this.element = this.options.id ? $j('#' + this.options.id) : null;
	},

	defaults: {
		/**
		 * id of the object to be displayed (most likely a
		 * hidden div)
		 *
		 * @param {string}		 
		 */
		id: '',

		/**
		 * Specifies the css classes you want applied to
		 * the html element linked to id.  Pass multiple
		 * classes seperated by a space.
		 * (i.e. css_class: 'big blue whale')
		 *
		 * @param {string}
		 */		
		css_class: '',

		/**
		 * Alpha Key for the BBTraqLander.ITEM_OPTIONS array
		 * What element are we dealing with?
		 * (i.e. param_key: 'A')
		 *
		 * @param {string}
		 */
		param_key: '',

		/**
		 * Numeric Key for the BBTraqLander.ITEM_OPTIONS array
		 * How should that elment be dealt with?
		 * (i.e. param_code: 1)
		 *
		 * @param {integer}
		 */			
		param_code: 0,

		/**
		 * should this element be displayed?
		 * This can be useful when you want to include elements
		 * in the parameters but don't wish to display them
		 *
		 * @param {boolean} default (false)
		 */
		display: true
	},

	display: function () {
		// add any passed css classes to the element
		if (this.element && this.options.css_class) {
			this.element.addClass(this.options.css_class);
		}
	},

	hide: function () {
	
	}

});


BBTraqLanderItem_Title = BBTraqLanderItem.subClass({
	init: function (options) {
		this._super(options);

		// if there is an included html element then load in the new page_title
		// text from it.
		if (this.element) {
			this.options.page_title = this.element.html();			
		}	
	},

	defaults: {
		
		/**
		 * id of the hidden div to retrieve the page title text from.
		 * if no id is passed then you can manually set the page_title
		 * text field as an input parameter
		 *
		 * @param {string}
		 */
		id: '',

        /**
         * What should the new page title text be?
         * You can either set this or the id field. If provided the ID
         * field will be used over this one.
         *
         * @param {string}
         */
        page_title: '',		

		/**
		 * Alpha Key for the BBTraqLander.ITEM_OPTIONS array
		 * What element are we dealing with?
		 *
		 * @param {string}
		 */
		param_key: '',

		/**
		 * Numeric Key for the BBTraqLander.ITEM_OPTIONS array
		 * How should that elment be dealt with?
		 *
		 * @param {integer}
		 */			
		param_code: 0,

		/**
		 * should this element be displayed?
		 *
		 * @param {boolean} default (false)
		 */
		display: false,

		/**
		 * Should the updated title scroll marque style?
		 *
		 * @param {bool}
		 */
		enable_scrolling: false,

		/**
		 * String appended to the end of the title when scrolling is enabled.
		 * Allows for a more seemless scroll action and makes the beginning 
		 * and the end of the page title more apparent.
		 *
		 * @param {string}
		 */
		scrolling_delimiter: " ___ ",

		/**
		 * How often should the scrolling title update?
		 * (i.e. after how many miliseconds should the next character be rolled over?)
		 *
		 * @param {int}
		 */
		scroll_delay: 150

	},

	/**
	 * Sets the page title with what is currenty set in this.options.page_title.
	 * If scrolling is enabled then this function will be called recursivly
	 * shifting the starting index in the string. Otherwise it will just
	 * update the title and return.  Setting this.options.enable_scrolling
	 * to false will stop the scrolling animation and print the full page
	 * title to the document in the correct order.
	 *
	 * @param {int} starting_index - what index in the page_title should be the
	 *   first charater in the new page title. Most Likely you'll be calling
	 *   this with 0 as the input (displaying the string from the begining).
	 */
	updateTitle: function (starting_index) {

		// Handle the case where we want to stop the scrolling animation,
		// or we don't want any animation at all.
		if (!this.options.enable_scrolling) {
			document.title = this.options.page_title;
			return;
		}
		
		var self = this;
		var delimed_title = this.options.page_title + this.options.scrolling_delimiter;
		var length = delimed_title.length;
		
		// update the document with the shifted title
		document.title = delimed_title.substring(starting_index, length) + delimed_title.substring(0, starting_index);

		// recursivly call scrollTitle with a new starting index to emulate scrolling
		setTimeout(function() {
			self.updateTitle(starting_index == length ? 0 : starting_index + 1);
		}, self.options.scroll_delay);
	},

	/**
	 * Overridden display function
	 * Sets the page title to what's stored in the options
	 */
	display: function () {
		if (this.options.display && this.options.page_title) {
			this.updateTitle(0);
		}
	},

	/**
	 * Overridden hide function:
	 * disable scrolling animation
	 */	
	hide: function () {
		this.options.enable_scrolling = false;
	}	
	
});

BBTraqLanderItem_Factory = {
	types: {
		popup: BBTraqLanderItem,
		title: BBTraqLanderItem_Title
	},

	create: function (options) {
		var item = null;
		this.options = {};
		// nested objects don't get compressed. Use local vars and make shit faster
		var BBS_options = BBTraqLander.ITEM_OPTIONS;
		
		// if the supplied item type is available then load those particular settings
		if (options.param_key in BBS_options) {
			// attempt to load the particular style options into the object
			if (options.param_code in BBS_options[options.param_key].styles) {
				this.options = $j.extend({}, options, BBS_options[options.param_key].styles[options.param_code]);
			}
			// attempt to load the type of the object into the options
			if ('type' in BBS_options[options.param_key]) {
				this.options.type = BBS_options[options.param_key].type;
			}			
		}

		// build the correct object based on it's type
		if (this.options.type) {
			var type = this.types[this.options.type];
			if ($j.isNullOrUndefined(type)) {
				type = this.types.popup;
			}
			item = new type(this.options);
		}
		
		return item;
	}
};

/**
 * TabBrowsing Plugin
 *
 * Plugin should be called on a container div, where the <ul> and <li> will be placed:
 *$(document).ready( function() {
 *	$('#tabs_container').tabBrowsing({
 *		tabIds:							BookingBuddy.tabIds,
 *		adIds:							BookingBuddy.adIds,
 *		dropdownElem:				$('#BBAdDropdown'),
 *		shouldLoadFirstTab:	<?php echo $show_instructions ? 'false' : 'true';?>
 *	});
 *
 *});
 *
 * This plugin consists of two objects
 * TabBrowsing - Acts as a controller for actions relative to all tabs
 * BBTab			 - Is Responsible for each Tab
 *
 * These objects can accessed by grabbing the $.data from the <div> on which the plugin was called on
 */
(function ($){

	// Start: BBS-2664
	var EyelinerAd = (function() {
		/**
		 * @const {string}	The element storage key for whether or not a tab has an Eyeliner Ad.
		 */
		var HAS_EYELINER_AD = 'eyeliner-ad-has-eyeliner-ad',

		/**
		 * @const {string}	The element storage key for whether or not the eyeliner ad impression
		 *		has been sent to Omniture.
		 */
		HAS_RECORDED_IMPRESSION = 'eyeliner-ad-has-recorded-impression',

		/**
		 * @const {string}	The element storage key for the tab that contains the Eyeliner Ad.
		 */
		TAB = 'eyeliner-ad-tab',

		/**
		 * @const {string}	The element storage key for the timeout function that is used to
		 *		delay showing the Eyeliner Ad until the tab is visible.
		 */
		TIMEOUT_ID = 'eyeliner-ad-timeout-id',

		/**
		 * @type {jQuery}	The eyeliner ad container.
		 */
		$container = $(
			'<div class="eyeliner-ad">' +
				'<div class="arrow" />' +
				'<div class="wrapper">' +
					'<div class="left" />' +
					'<div class="right" />' +
					'<div class="content" />' +
				'</div>' +
			'</div>'
		).hide(),

		/**
		 * @type {jQuery}	The arrow for the eyeliner ad.
		 */
		$arrow = $container.find('.arrow'),

		/**
		 * @type {jQuery}	The wrapper of the text for the eyeliner ad.
		 */
		$textWrapper = $container.find('.wrapper'),

		/**
		 * @type {jQuery}	The content area for the eyeliner ad.
		 */
		$contentArea = $textWrapper.find('.content');

		// -------------------------------------------------------------------------------------- //

		/**
		 * Fade in the Eyeliner Ad.
		 *
		 * @return {EyelinerAd}
		 */
		var fadeIn = function() {
			/**
			 * @type {jQuery}	The tab associated with the currently displayed eyeliner ad.
			 */
			var tab = $container.data(TAB),

			/**
			 * @type {{left: int, top: int}}	The position of the tab.
			 */
			tabPosition = tab.offset();

			// Delay execution until the tab is visible
			if (!tab.is(':visible')) {
				$container.data(TIMEOUT_ID, window.setTimeout(fadeIn, 250));
				return EyelinerAd;
			}

			// If the ad is "visible" but its position is outside of the viewable range, hide the ad
			if ((tabPosition.left < 0) || (tabPosition.top < 0)) {
				return fadeOut();
			}

			trackImpression();
			updateContent();

			$arrow.css({ left: tabPosition.left + 40 });

			$container.show();

			$textWrapper.css({
				left: Math.max(tabPosition.left + 40 - ($contentArea.width() / 2) + 3, 20)
			}).hide().fadeIn();

			return EyelinerAd;
		},

		/**
		 * Fade out the Eyeliner Ad.
		 *
		 * @return {EyelinerAd}
		 */
		fadeOut = function() {
			$container.fadeOut();
			return EyelinerAd;
		},

		/**
		 * Event handler for when the eyeliner ad is clicked.
		 *
		 * @param {jQuery.Event}	event	The event that was triggered.
		 * @return {EyelinerAd}
		 */
		onClick = function(event) {
			event.preventDefault();

			var tab = $container.data(TAB);
			if (tab) {
				tab.trigger('click');
			}

			return EyelinerAd;
		},

		/**
		 * Run the code to show/hide the eyeliner ad.
		 *
		 * @param {jQuery}	panel	The tab panel.
		 * @param {jQuery}	tabs	The tabs.
		 * @return {EyelinerAd}
		 */
		run = function(panel, tabs) {
			/**
			 * @type {jQuery}	The tab of the eyeliner ad.
			 */
			var tab,

			/**
			 * @ty[e {jQuery}	The currently active tab.
			 */
			activeTab = tabs.filter('li.active');

			// Always prepend to make sure the eyeliner ad is at the top of the panel
			$container.prependTo(panel);

			// If there is an active tab, nothing should be done
			if (activeTab.length) {
				return EyelinerAd;
			}

			// Search for the eyeliner ad within the list of tabs; Note that the tab must be within
			// the first 4 to be considered
			tabs.each(function(index) {
				if (index >= 4) { return false; }

				var element = $(this);
				if (element.data(HAS_EYELINER_AD)) {
					tab = element;
					return false;
				}

				return true;
			});

			// Clear any timeout handlers and store the tab information into the Eyeliner Ad
			window.clearTimeout($container.data(TIMEOUT_ID));
			$container.data(TAB, tab);

			// Depending on the state of the Eyeliner Ad and whether or not we got a tab:
			// 1. Eyeliner Ad is hidden and tab was not found:
			//    - Nothing to do
			// 2. Eyeliner Ad is hidden and tab was found:
			//    - Fade in the Eyeliner Ad after the tab is visible
			// 3. Eyeliner Ad is visible and tab was not found:
			//    - Fade out the Eyeliner Ad
			// 4. Eyeliner Ad is visible and tab was found:
			//    - Slide the Eyeliner Ad into the right position
			if ($container.is(':visible')) {
				if (tab) { slide(); }
				else { fadeOut(); }
			} else if (tab) { $container.data(TIMEOUT_ID, window.setTimeout(fadeIn, 250)); }

			return EyelinerAd;
		},

		/**
		 * Slide the Eyeliner Ad into the right position.
		 *
		 * @return {EyelinerAd}
		 */
		slide = function() {
			/**
			 * @type {jQuery}	The tab associated with the currently displayed eyeliner ad.
			 */
			var tab = $container.data(TAB),

			/**
			 * @type {{left: int, top: int}}	The position of the tab.
			 */
			tabPosition = tab.offset();

			// If the ad is no longer viewable, hide the ad
			if ((tabPosition.left < 0) || (tabPosition.top < 0)) {
				return fadeOut();
			}

			updateContent();

			$arrow.animate({ left: tabPosition.left + 40 });

			$textWrapper.animate({
				left: Math.max(tabPosition.left + 40 - ($contentArea.width() / 2) + 3, 20)
			});

			return EyelinerAd;
		},

		/**
		 * Track the impression for the ad.
		 *
		 * @return {EyelinerAd}
		 */
		trackImpression = function() {
			if (!$container.data(HAS_RECORDED_IMPRESSION)) {
				var tab = $container.data(TAB).data('tabObj'),
					tracker = STMTracking.singleton();

				tracker.setEvent('event52')
					.setEvent('purchase')
					.setTrackVar('products')
					.setTrackVar('purchaseID');

				tracker.setProperty('products', ';el_' + tab.tab.ad_id + ';1;283ccd1c20')
					.setProperty('purchaseID',
						Math.floor(Math.random() * 999999 + 1) + '' + Number(new Date())
					);

				tracker.send('Eyeliner Ad Impression');
				$container.data(HAS_RECORDED_IMPRESSION, true).bind('click', onClick);
			}

			return EyelinerAd;
		},

		/**
		 * Update the content of the eyeliner ad from the provided tab.
		 *
		 * @return {EyelinerAd}
		 */
		updateContent = function() {
			var message = 'Book a Room #{city} on Marriott.com to Truly Get our Lowest Price ' +
						  'Anywhere - Guaranteed!';

			$contentArea.html($.tmpl('<a href="#">' + message + '</a>', {
				city: (BookingBuddy && BookingBuddy.queryString && BookingBuddy.queryString.arrival_city) ?
						'in ' + BookingBuddy.queryString.arrival_city : ''
			}));

			return EyelinerAd;
		};

		// -------------------------------------------------------------------------------------- //

		// Public API
		return {
			HAS_EYELINER_AD: HAS_EYELINER_AD,
			container: $container,

			/**
			 * @type {array}	The list of Eyeliner Ads.
			 */
			adsList: [9005961],

			run: run,

			/**
			 * See if the test is active.
			 *
			 * @return {bool}
			 */
			isActive: function() {
				return !!BookingBuddy.abTests.bbs2664;
			}
		};
	})();
	// End: BBS-2664

	// Start: BBS-2741
	var AlertOnClose = (function() {
		// Private API

		/**
		 * @type {object}	The template of the message to display to the user when the page
		 *		unloads.
		 */
		var messages = {
			a: 'You have unviewed #{vertical} results that may have a lower price for your ' +
				'#{geo} trip. Please click the tabs to view these results.',
			b: 'You are about to abandon your #{geo} #{vertical} results. Please click the tabs ' +
				'to view these results.',
			c: 'You have #{geo} #{vertical} results that have not been viewed. Closing this ' +
				'window may cause the prices to no longer be available. Please click the tabs to ' +
				'view your results.'
		},

		/**
		 * @type {int}	The number of searches so far.
		 */
		searchCount = 0,

		/**
		 * Get the message to display.
		 *
		 * @return {string}
		 */
		getMessage = function() {
			/**
			 * @type {string}	The translated name for the vertical the user is in.
			 */
			var vertical;
			switch (BookingBuddy.queryString.search_mode) {
				case 'air': vertical = 'flight'; break;
				case 'vacation': vertical = 'vacation package'; break;
				case 'vacation_rental': vertical = 'vacation rental'; break;
				default: vertical = BookingBuddy.queryString.search_mode;
			}

			/**
			 * @type {string}	The location the user is traveling from and to.
			 */
			var geo = '';
			if (BookingBuddy.queryString.departure_city) {
				geo = BookingBuddy.queryString.departure_city;
			}

			if (BookingBuddy.queryString.arrival_city) {
				if (geo) {
					geo += ' to ';
				}

				geo += BookingBuddy.queryString.arrival_city;
			}

			if (BookingBuddy.queryString.pickup_city) {
				geo = BookingBuddy.queryString.pickup_city;
			}

			if (BookingBuddy.queryString.destination) {
				geo = BookingBuddy.queryString.destination;
			}

			return $.tmpl(messages[BookingBuddy.abTests.bbs2741], { vertical: vertical, geo: geo });
		},

		/**
		 * Event handler for when the page unloads.
		 *
		 * @return {mixed}
		 */
		onBeforeUnload = function() {
			if (!searchCount) {
				return getMessage();
			}
		},

		/**
		 * Event handler for when a tab is displayed.
		 *
		 * @return {AlertOnClose}
		 */
		onTabDisplay = function() {
			++searchCount;
			return AlertOnClose;
		},

		/**
		 * Initialize the test.
		 *
		 * @param {jQuery}	tabBrowsing		The tab browsing container.
		 * @return {AlertOnClose}
		 */
		run = function(tabBrowsing) {
			tabBrowsing.bind('tabBrowsing:tabDisplayed', onTabDisplay);

			return runOnce();
		},

		/**
		 * The part of the initialization that should only be run once.
		 *
		 * @return {ALertOnClose}
		 */
		runOnce = function() {
			window.onbeforeunload = onBeforeUnload;

			// Hijack the method
			runOnce = function() { return AlertOnClose; };
			return AlertOnClose;
		};

		// -------------------------------------------------------------------------------------- //

		// Public API
		return {
			// Inherit from private API
			run: run,

			// Only in public
			isActive: function() {
				return !!BookingBuddy.abTests.bbs2741;
			}
		};
	})();
	// End: BBS-2741

	/**
	 * ----------------------------------------------------------------------------------------
	 * @constructor TabBrowsing
	 * @param object htmlelement on which the plugin was called on
	 * @param object TabBrowsing overriding options
	 * ----------------------------------------------------------------------------------------
	 */
	function TabBrowsing(element, options) {
		if (!BookingBuddy.queryString || BookingBuddy.queryString.length === '') {
			return this;
		}

		var self = this;

		// override default options
		this.options = $.extend({}, TabBrowsing.options, options );

		// reference to the element on which this plugin got called on
		this.options.container = $(element);

		// set the panel container object based on the selector input
		this.options.panel = $(this.options.panelSelector);

		if(this.options.initialActive){
			this.options.panel.show();
			this.options.container.show();
		}

		// Contains the 'search data' part of the query string (no ad ids)
		var q_string = $.extend({}, BookingBuddy.queryString);
		delete q_string['r'];
		this.options.searchQueryString = $.toQueryString(q_string);

		this.options.siteRedirectUrl = $('#siteRedirectUrl').text();
		this.options.siteRedirectUrl += this.options.siteRedirectUrl.indexOf('?') != -1 ? '&' : '?';

		// create each tab and append it to the container
		this.options.container_ul = $(document.createElement('ul'));

		// Run once to make sure everything is right for the current window size
		// This needs to be run prior to adding the tabs to ensure that the fauxTabs
		// option is properly set prior the BBTab object setting it's shouldPopNew Window member
		this.windowResizeHandler();

		// bind window resizing event
		$(window).bind('resize', $.proxy(this.windowResizeHandler, this));


		// add tabs to container
		$.each(this.options.tabIds, function(index, ad_id){
			self.addTab(ad_id);
		});

		// hide the delete buttons if there is only one tab!
		if (this.options.tabIds.length == 1) {
			self.toggleDeleteButtons('hide');
		}

		this.options.container.append(this.options.container_ul);

		// Setup tab event handler add/delete
		this.loadTabEventHandler();

		// displayInstructions if needed
		if (!this.options.shouldLoadFirstTab) {
			if (!this.options.supressInstructionsHtml) {
				this.showInstructionsHtml();
			}
		} else {
			this.loadFirstTab();
		}

		// click handler for logos
		this.loadLogoHandler();

		// Fade each tab in for some extra *HAWTNESS*
		// Note: fading in of the tabs should happen after the instructions are show
		// to allow the updating of the count in the instructions to opperate smoothly.
		this.fadeInTabs(this.options.fade_in_speed, this.options.animate_counter);

		// run this once to initialize the page, enabling the left/right Arrows if we're displaying more tabs than maxTabDisplay
		this.toggleArrows();

		// bind the toggleing of arrows to the add/remove tabs to the container events, this will add/remove the left/right arrows as needed
		this.options.container.bind('tabBrowsing:tabMovedToDropdown tabBrowsing:tabAddedToContainer', $.proxy(this.toggleArrows, this));

		// if you guessed "load the dropdown" - you win
		this.loadDropdown();

		if(this.options.initialActive){
			this.options.dropdownElem.show();
		}

		this.options.container.trigger('tabBrowsing:loaded');

		// Start: BBS-2741
		if (AlertOnClose.isActive()) {
			AlertOnClose.run(this.options.container);
		}
		// End: BBS-2741

		return this;
	}


	/*
	 * TabBrowsing methods
	 */
	$.extend(TabBrowsing, {
		/**
		 * @param {object} holds all settings for tabbed browsing
		 */
		options: {

			/**
			 * Should the html instructions be supressed
			 */
			supressInstructionsHtml: false,

			/**
			 * should_be passed in
			 * @param {boolean}
			 */
			shouldLoadFirstTab: false,

			/**
			 * @param {jqueryObject} <ul> object that holds all the tabs
			 */
			container_ul: $(),

			/**
			 * Tab ad Ids
			 * should_be passed in
			 * @param {array}
			 */
			tabIds: [],

			/**
			 * List of tab objects indexed by ad id
			 * @param {object}
			 */
			tabList: {},

			/**
			 * Dropdown ad Ids
			 * should_be passed in
			 * @param {array}
			 */
			dropdownIds: [],

			/**
			 * All ad ids
			 * should_be passed in
			 * @param {array}
			 */
			adIds: [],

			/**
			 * Max amount of tabs to be shown
			 * @param {number}
			 */
			maxTabDisplay: 6,

			/**
			 * tab movement increment
			 * @param {number} (pixels)
			 */
			tabIncrement: 102,

			/**
			 * should_be passed in
			 * @param {jqueryObject} - points to dropdown
			 */
			dropdownElem: $(),

			/**
			 * Faux tabs are a workaround for permission issues for IE. In faux tab mode
			 * one "control" window is opened to show the tabs and search instructions
			 * (just like regular tabbed search) but then every search is opened in a
			 * new window, blacklisted or not, and positioned to look like it was opened
			 * as a tab of the main window.
			 *
			 * This also depends on if whether or not a user is sent down the tabbed
			 * searches path to begin with. They (IE6 users) may just get the multi-window
			 * version of search which makes this flag moot.
			 *
			 * Currently we are also including safari to be shown as faux tabs. It is critcal that
			 * we do not rope in chrome along with safari. The current way to do this is by checking
			 * the appversion. Chrome's has the word Chrome in it, Safari's does not.
			 *
			 * @param {boolean} - default (false) - gets set in this.setWindowOptions()
			 */
			fauxTabs: false,

			/**
			 * Selector used to get the panel element
			 * @param {string}
			 */
			panelSelector: '#panel',

			/**
			 * reference to panel element
			 * @param {jqueryObject}
			 */
			panel: $(),

			/**
			 * Holds a reference to the window object used to open a popup
			 * @param {object}
			 */
			popupWindowNames: {},

			/**
			 * Holds window opening options for this tab
			 * @param {string}
			 */
			window_opts: null,

			/**
			 * Speed at which the tabs should 'fade in' when the screen is drawn
			 * @param {integer} - default (500)
			 */
			fade_in_speed: 500,

			/**
			 * Should the html instructions count up the number of found sites as the tabs are fadded in?
			 * @param {boolean} - default (true)
			 */
			animate_counter: true

		},

		/**
		 * List of bb_affiliates that have the bookingbuddy logo in the upper right
		 * hand corner of the page.
		 *
		 * This is a temporary fix until we offload some of these options as inputs
		 * in Admin.  The whole concept of checking the affiliate name is used all
		 * over BB and will eventually be tackled!
		 *
		 * @var {Array}
		 */
		AFFILIATES_WITH_BB_LOGO: ['onetravel', 'cheapoair_com'],

		prototype: {

			/**
			 * Handles clicks on the page's logos for all affilates
			 */
			loadLogoHandler: function () {
				var affiliate = BookingBuddy.affiliateName;

				if (affiliate) {
					var bbLogo = $.inArray(affiliate, TabBrowsing.AFFILIATES_WITH_BB_LOGO) > -1,
						affiliateSelector =  bbLogo ? '#panel .logo_link' : '.logo_link';

					// If we are on an affiliate page w/ a bb logo then set the click
					// event of the top right logo link to shoot to bb.com
					if (bbLogo) {
						$("#tab_header_right .logo_link").live('click', function() {
							window.location = "http://rd.bookingbuddy.com/?r=tabbed_bookingbuddy_com";
						});
					}

					// Set up the affiliate links to go to their sites.
					$(affiliateSelector).live('click', function() {
						window.location = "http://rd.bookingbuddy.com/?r=tabbed_" + affiliate;
					});

				}
			},

			/**
			 *
			 * Fades in all tabs one-by-one
			 * @param {number}		speed :
			 * @param {boolean}		animate_counter: if true will show a counter animation
			 */
			fadeInTabs: function (speed, animate_counter) {
				var $adCount = $('#tl_instructions_content h3 span');
				$adCount.html("1");

				var fadeTab = function(tab) {
					if (tab[0]) {
						// animate counter
						if (animate_counter) {
							$adCount.html((tab.index() + 1));
						}

						tab.fadeIn(speed, function() {
							// animation completed
							tab.removeClass('hidden');
							if (tab.next()[0]) {
								fadeTab(tab.next());
							}
						});
					}
				};

				var q_string = $j.extend({}, BookingBuddy.queryString),
					tab_list = this.options.container_ul.find('li'),
					first_tab = this.options.container_ul.find('li:first');
				// Dont do tab animation if we have TraqLander popups
				if (!q_string.ctm ||
					(q_string.ctm.indexOf('A') == -1 && q_string.ctm.indexOf('B') == -1)) {
					fadeTab(first_tab);
				} else {
					// default the counter to the full number of ads
					$adCount.html(tab_list.length);
					// All tabs should appear on the page
					tab_list.each(function() {
						$j(this).show();
					});
					// Fade each tab when the Traq Lander disappears
					$j.subscribe('/hide/TraqLander', $j.proxy(function() {
						// Hide the tabs since we cant animate on visible tabs
						tab_list.each(function() {
							$j(this).hide();
						});
						fadeTab(first_tab);
					}, this));
				}
			},

			/**
			 * Load and display first tab
			 */
			loadFirstTab: function () {
				var first_tab = this.options.container_ul.find('li:first').data('tabObj').tab;
				first_tab.display();
			},

			/**
			 *
			 */
			setWindowOptions: function () {
				if ($.browser.msie || ($.browser.webkit && !navigator.appVersion.toLowerCase().match("chrome"))) {
					this.options.fauxTabs = true;
				}

				// We want to display things in new windows if we are in faux tabs
				// mode or for FF's frame busting searches
				if (this.options.fauxTabs || $.browser.mozilla)  {
					this.options.window_opts = this.getWindowOptions('search');
				} else {
					this.options.window_opts = this.getWindowOptions('standard');
				}
			},

			/**
			 * Returns window.open settings based on option passed in
			 * options: 'cover, fullscreen, search, standard'
			 * @param string option - type of "window.open" setting to return
			 * @returns window.open setting
			 */
			getWindowOptions: function (option) {
				var setting, top, left, height, width;

				// Get the coordinates of the top left of the screen relative to the
				// user's monitor.
				var win_top = !$.isUndefined(window.screenY) ? window.screenY : window.screenTop;
				var win_left = !$.isUndefined(window.screenX) ? window.screenX : window.screenLeft;

				// When tabs open in tabbed search mode, this modifies how
				// far down the page they will open for different browsers
				// FireFox IE and Safari all have unique values and are the
				// only users of this variable
				var faux_tab_placement = $.browser.mozilla ? 55 : ($.browser.msie ? 65 : 90);

				switch (option) {
					case 'cover':
						height = $.browser.mozilla ? (screen.availHeight - win_top) : $(window).height() + 10;
						width  = $(window).width() + 10;

						setting = "status=0,toolbar=0,location=1,menubar=0,resizable=1,scrollbars=0,height=" + height +	",width=" + width +
											",top=" + win_top + ",left=" + win_left;
						break;

					case 'fullscreen':
						setting = "fullscreen=1,status=0,toolbar=0,location=1,menubar=0,resizable=1,scrollbars=0";
						break;

					// IE and Safari: search window offsets are relative to the control window
					case 'search':
						height = this.options.panel.height() - faux_tab_placement;
						width = $(window).width() + 2;
						// window positioning Black Magic
						if (!$.browser.msie) {
							top = $.browser.mozilla ? 82 : 87;
							top += window.outerHeight - $(window).height() + win_top;
						} else {
							//IE9 is shaped differently and requires a different + top
							var ie_adjust = ($.browser.version != 9) ? 93 : 160;
							top = win_top + ie_adjust;
						}

						setting = "status=0,toolbar=0,location=1,menubar=0,resizable=1,scrollbars=1,height=" + height + ",width=" + width +
											",top=" + top + ",left=" + win_left;
						break;

					// regular tabbed browsing for well behaving browsers (not IE!!!)
					case 'standard':
					default:
						top  = win_top + 60;
						left = win_left + 20;
						setting = "status=1,toolbar=1,location=1,menubar=1,resizable=1,scrollbars=1,height=1000,width=1040,top=" + top + ",left=" + left;
						break;
				}

				return setting;
			},

			/**
			 * @returns number of tabs
			 */
			getTabCount: function () {
				return this.options.container.find('ul li').length;
			},

			/**
			 * Creates a BBTab object
			 * Adds it to the <ul>
			 * Does omniture tracking for this tab (attempted search and vendor tracking)
			 * @returns BBTab object
			 */
			addTab: function (ad_id) {
				// create tab and append it to the container
				var tab = new BBTab(ad_id, this.options);
				this.options.tabList[ad_id] = tab;
				this.options.container_ul.append(tab.getElem());
				$.publish('/tabBrowsing/tabAdded', [tab]);
				this.options.container.trigger('tabBrowsing:tabAddedToContainer');

				return tab;
			},

			/**
			 * Acts as a controller for these tab events
			 * - display
			 * - delete
			 */
			loadTabEventHandler: function () {
				var self = this;

				// events for tabs
				this.options.container_ul.delegate('li', 'click', function (event) {
					var tab = $(this).data('tabObj').tab;

					// remove any panel html if needed ie.(Instructions text)
					$('#panelHtml').remove();

					// Delete tab
					if ($(event.target).hasClass('delete')) {
						self.removeTab(tab);

					} else {
						// Display Tab
						if (!tab.isActive()) {
							tab.display();
						}
					}
				});
			},

			/**
			 * Window resize event handler
			 * adjusts the panel to fill the browser window
			 */
			windowResizeHandler: function () {

				//Sets the height for the iframe based on the window.
				var window_width  = $(window).width();
				var window_height = $(window).height();
				var header_height = $('#tab_header_wrapper').height();
				var panel_height = Math.abs(window_height - header_height) - 3;

				this.options.panel.height(panel_height);

				// Causes tabs to fit better onto the screen by changing what percent
				// of the page their width should be
				var tab_width = '';

				if (window_width < 1000) {

					// The minimum length the window can be is 555, below that we dont support formatting.
					var min_window = 555;

					// Every 16 pixels we want to change the width percent by 1.
					var change_width_at = 16;

					// This is the minimum percent with we want.
					var base_width = 45;

					// This is a function that descides the ideal tab width percent
					// It should only be altered if either tab spacing or tab size is changed
					tab_width = (Math.floor((window_width - min_window) / change_width_at ) + base_width) + '%';

				} else {
					tab_width = 725 + 'px';
				}

				this.options.container.css('width', tab_width);

				// set window options  everytime the panel changes in height
				this.setWindowOptions();

				this.options.container.trigger('tabBrowsing:windowResized');
			},

			/**
			 * Loads the dropdown with the correct ad ids
			 * @requires dhtmlDropdown (should be passed in)
			 */
			loadDropdown: function () {
				if (!this.options.dropdownElem[0]) {return;}

				var dropdown_obj = this.options.dropdownElem.data('dhtmlDropdown');
				if (!dropdown_obj) {return;}

				var self = this;

				// dropdownIds contains the intersection between adIds and tabIds
				self.options.dropdownIds = $.grep(self.options.adIds, function(elem, index) {
					if ($.inArray(elem, self.options.tabIds) === -1) {
						return true;
					}
				});

				// remove adds that were loaded using Bookingbuddy.dropdownoptions
				dropdown_obj.reset();

				// add whatever ad that isn't a tab right now
				$.each(self.options.dropdownIds, function(index, id) {
					dropdown_obj.insert(id, true);
				});

				dropdown_obj.redraw();

				this.options.dropdownElem.bind('DHTMLDropdown:select', function() {
					self.addTabFromDropdown(dropdown_obj);
				});
			},

			/**
			 * Adds a tab from the dropdown to the list
			 * @param {object} - dhtmlDropdown object
			 */
			addTabFromDropdown: function (dropdown_obj) {
				var ad_id = dropdown_obj.val();
				if (!ad_id) {
					return;
				}

				// remove unchecked object and dropdown option
				dropdown_obj.remove(ad_id);
				dropdown_obj.val(null);
				dropdown_obj.redraw();

				// Display must be called prior to focus last tab due to the
				// fact that the new tab is hidden until the display is called
				// and focusLastTab does all it's calcuations based on height
				// which are only build on visible elements.
				this.addTab(ad_id).display().focusLastTab();

				this.toggleDeleteButtons('show');

				// add ad to tabIds array
				if ($.inArray(ad_id, this.options.tabIds) === -1) {
					this.options.tabIds.push(ad_id);
				}

				// remove ad_id from dropdownIds array
				var id_index = $.inArray(ad_id, this.options.dropdownIds);
				if (id_index !== -1) {
					this.options.dropdownIds.splice(id_index, 1);
				}

				this.options.container.trigger('tabBrowsing:tabAddedFromDropdown');
			},

			/**
			 * Adds an ad to the dropdown
			 * @param {number} ad id
			 */
			addToDropdown: function (ad_id) {
				var dropdown_obj = this.options.dropdownElem.data('dhtmlDropdown');
				if (!dropdown_obj) {return;}

				dropdown_obj.insert(ad_id, true).redraw();

				// add ad to dropdownIds array
				if ($.inArray(ad_id, this.options.dropdownIds) === -1) {
					this.options.dropdownIds.push(ad_id);
				}
			},

			/**
			 * Displays/Hide deleteButtons
			 * @param {string} - show/hide
			 */
			toggleDeleteButtons: function(display) {
				switch (display) {
					case 'hide':
						this.options.container_ul.find('.delete').hide();
						break;
					case 'show':
					default:
						this.options.container_ul.find('.delete').show();
						break;
				}

				return this;
			},

			/**
			 * Does all necessary things for tab removal
			 * removes element, deletes tab object and activtes next tab
			 * @param {object} BBTab
			 */
			removeTab: function (tab) {
				// hide the tab first thing for speeeeed
				tab.tab_li.hide();

				// if the tab has an open window, close it
				var ad_id = tab.ad_id;
				if (tab.options.popupWindowNames[ad_id] && !tab.options.popupWindowNames[ad_id].closed) {
					tab.options.popupWindowNames[ad_id].close();
				}

				// remove tabId from tabIds array
				var id_index = $.inArray(tab.ad_id, this.options.tabIds);
				if (id_index !== -1) {
					this.options.tabIds.splice(id_index, 1);
				}

				// we only have one *other* tab hide delete button
				if (tab.tab_li.siblings().length == 1) {
					this.toggleDeleteButtons('hide');
				}

				// add ad back into the dropdown
				this.addToDropdown(tab.ad_id);


				// Are any of the tabs active?  if not then we can re-display the
				// instructions later on.
				var activeTabs = tab.tab_li.siblings('.active').length > 0;
				// START: BBS-2200
				// Must also make sure the hotel cross-sell tab isn't active!
				activeTabs |= $('#hotel_container li.active').length > 0;
				// END: BBS-2200

				// remove the tab element and it's iframe
				tab.tab_li.remove();
				tab.tabIframe.remove();

				// If the current active tab gets deleted load the devil horns.
				// we need to show/build the instruction html after removing the
				// tab so that the instructions will have the newest tab count.
				// NOTE: only update the instructions after the tab is deleted to
				// the correct count can be written.
				if (tab.isActive() || !activeTabs) {
					this.showInstructionsHtml();
				}

				this.options.container.trigger('tabBrowsing:tabMovedToDropdown');

				// Start: BBS-2664
				if (EyelinerAd.isActive()) {
					EyelinerAd.run(this.options.panel, $('#tabs_container li'));
				}
				// End: BBS-2664
			},

			/**
			 * Activates the next unseen tab relative
			 * to the tab passed in
			 * @param {object} BBTab
			 */
			activateNextUnseenTab: function (tab) {
				tab.tab_li.siblings().each(function() {
					tab_obj = $(this).data('tabObj').tab;
					// if a tab doesn't have an iframe yet - it hasn't been seen
					if (!tab_obj.tabIframe[0]) {
						tab_obj.display();
						return false; //break
					}
				});

				return this;
			},

			/**
			 * Displays instructions oh how to use the tabs
			 * This should only load if we're not automatically loading the first tab
			 * or if the active tab is closed.
			 * @returns this
			 */
			showInstructionsHtml: function () {

				var tpl_obj = {
					tab_count:  this.getTabCount(),
					// trip_details will be pulled from the itin div outputted on the page
					trip_details: $('#itin')[0]	? '<h5>Trip Details: <span>' + $('#itin').html() + '</span></h5>' : ''
				};

				var instructionsDivId = "tl_instructions",
					panelHtmlCss = "panelHtml",
					tpl = '<div class="left_arrow"></div>' +
										'<div class="tl_instructions_content" id="tl_instructions_content">' +
											'<h3><span>#{tab_count}</span> Sites Found!</h3>' +
											'<span>#{trip_details}</span>'	 +
											'<h4>Click the logos above to compare prices.</h4>' +
											'<div class="logo_link"></div>' +
										'</div>' +
									'<div class="right_arrow"></div>';

				$("<div />", {
						id: instructionsDivId,
						'class': "tl_instructions",
						html: $.tmpl(tpl, tpl_obj)
				}).
				prependTo(this.options.panel).
				wrap('<div class="' + panelHtmlCss + '" id="panelHtml"/>');

				// Start: BBS-2664
				if (EyelinerAd.isActive()) {
					EyelinerAd.run(this.options.panel, $('#tabs_container li'));
				}
				// End: BBS-2664

				return this;
			},

			/**
			 * Toggles this visibility of the left and right arrows based on the current tab count
			 * and the maxTabDisplay var.  The arrows will be displayed if there are more tabs than the max
			 * and will be hidden if there are less.
			 * Will create the arrows if are required and don't exist.
			 */
			toggleArrows: function () {
				var tabCount = this.getTabCount();
				var arrows = $(this.options.container).siblings('#more_right,#more_left');
				var arrowsExist = (arrows.length > 0);
				var arrowsHidden = arrowsExist && arrows.first().css('display') == 'none';

				// Arrows are not needed, hide them
				if (tabCount < this.options.maxTabDisplay) {
					if (arrowsExist && !arrowsHidden) {
						// the arrows exist on the page and they are being shown, hide the arrows
						arrows.hide();
						// and move the container to the zero position so that all tabs are visible
						// animate the movement for extra hotness!!
						this.options.container_ul.animate({left: 0}, 500);
					}
				} else {
					// Arrows are required, create or show them
					if (!arrowsExist) {
						this.createArrows();
					} else if (arrowsHidden) {
						// the arrows exist, show them
						arrows.show();
					}

				}

				this.options.container.trigger('tabBrowsing:toggleArrows');
			},

			/**
			 * Creates the arrow objects and wrapes the contains with them.
			 * on creation the arrows will be visible.  if we're displaying more tabs than
			 * the maxTabDisplay var and enables moving the tabs to the right or left
			 */
			createArrows: function () {
				var blocked = false;
				var self = this;
				var leftArrow;
				var rightArrow;

				// left arrow/event handler
				leftArrow = $('<div />', {
					"class": "more",
					id: "more_left",
					click: function(event) {
						if (blocked) {return;}

						if (self.options.container_ul.position().left === 0) {
							return;
						}
						blocked = true;
						self.options.container_ul.animate({
							left: '+=' + self.options.tabIncrement
						}, 500 , function () {
							blocked = false;

							// Start: BBS-2664
							if (EyelinerAd.isActive()) {
								EyelinerAd.run(self.options.panel, $('#tabs_container li'));
							}
							// End: BBS-2664
						});
					}
				}).append($('<div />',{
					"class": "gradient",
					id: "gradient_left"
				}));

				// right arrow/event handler
				rightArrow = $('<div />', {
					"class": "more",
					id: "more_right",
					click: function(event) {
						if (blocked) {return;}

						// The distance between the last tab and the right arrow
						// should not be greater than a tab width
						var arrow_pos = $(this).position().left;
						var last_tab_pos = self.options.container_ul.find('li:last').offset().left;
						if ((arrow_pos - last_tab_pos) > self.options.tabIncrement + 20) {
							return;
						}

						blocked = true;
						self.options.container_ul.animate({
							left: '-=' + self.options.tabIncrement
						}, 500, function () {
							blocked = false;

							// Start: BBS-2664
							if (EyelinerAd.isActive()) {
								EyelinerAd.run(self.options.panel, $('#tabs_container li'));
							}
							// End: BBS-2664
						});
					}
				}).append($('<div />',{
					"class": "gradient",
					id: "gradient_right"
				}));

				// add arrows to container
				self.options.container.before(leftArrow);
				self.options.container.after(rightArrow);
			}
		}
	});


	/**
	 * ----------------------------------------------------------------------------------------
	 * @class BBTab BBTab object
	 * @param {string} ad_id	- ad id
	 * @param {object} options - TabBrowsing options gets passed down to this object
	 * ----------------------------------------------------------------------------------------
	 */
	function BBTab(ad_id, options) {
		/**
		 * Iframe object linked to this tab
		 * @param {jqueryObject}
		 */
		this.tabIframe = $();

		/**
		 * List Element linked to this tab
		 * @param {jqueryObject}
		 */
		this.tab_li = $();

		/**
		 * Html object to be appended to the panel in case this tab opens a popup window
		 * @param {jqueryObject}
		 */
		this.popup_html = $();

		this.ad = BBAd.singleton(ad_id);
		this.ad_display_name = this.ad.getMetaData('display_name');

		if (!this.ad_display_name || typeof options === 'undefined') {
			return $();
		}

		this.options = options;
		this.ad_id = ad_id;
		this.ad_tracking_hash = this.ad.getMetaData('tracking_hash');
		this.ad_rank = this.ad.getMetaData('rank');
		this.ad_url = this.options.siteRedirectUrl + 'r=i' + this.ad_id + '&ad_rank=' + this.ad_rank + '&' + this.options.searchQueryString;
		this.ad_logo = this.ad.getMetaData('tab_logo');
		this.square_ad_logo = this.ad.getMetaData('logo');

		/**
		 * Should the ad open in a new window when clicked?
		 * Since we only need to define this property once and it's value won't need to change during the life of the BBTab object
		 * it is wrapped in an immediate function. The returned value of the immediate function will become the value of the property.
		 * [the actual algorithm will only be executed once, on initialization of the BBTab object]
		 * @param {boolean}
		 */
		this.shouldPopNewWindow = (function (tab) {
			if (tab.options.fauxTabs) {
				return true;
			}

			if (!$('#searchModeName')[0]) {
				return false;
			}

			// List of ads that are framebusting and therefore will open in a new window instead
			// This is based on ad display names and is case insensitive
			var	blackList = {
				air:				['delta', 'jetblue', 'Hawaiian', 'bing', 'cheaptickets', 'insidetrip',
									 'fly\\s*cheap\\s*abroad', 'fly\\s*there\\s*4\\s*less', 'ati\\s*flights', 'frontier\\s*airlines'],
				hotel:				['delta', 'jetblue', 'Hawaiian', 'choice hotels', 'bing', 'cheaptickets', 'marriott'],
				vacation:			['delta', 'jetblue', 'Hawaiian', 'bing', 'cheaptickets'],
				car:				['delta', 'jetblue', 'Hawaiian', 'bing', 'cheaptickets'],
				cruise:				['delta', 'jetblue', 'Hawaiian', 'bing', 'cheaptickets'],
				vacation_rental:	['delta', 'jetblue', 'Hawaiian', 'bing', 'cheaptickets']
			};

			var regex;
			var list = blackList[BookingBuddy.searchMode];
			for (index in list) {
				if (list.hasOwnProperty(index)) {
					regex = new RegExp(list[index], 'i');
					if (tab.ad_display_name.search(regex) != -1) {
						// Ad should open in a new window
						return true;
					}
				}
			}

			return false;
		}(this));

		return this;
	}

	$.extend(BBTab, {

		prototype: {

			/**
			 * Returns this tabs <li> object
			 * If the object is *not* yet created, it creates a
			 * tab  <li> object and returns it
			 * @returns li object
			 */
			getElem: function () {
				if (this.tab_li[0]) {return this.tab_li;}

				var self = this;

				// shorten the display name on tab if needed and add "..."
				var ad_display_name = this.ad_display_name.length > 12 ? this.ad_display_name.substring(0,9) + '...' : this.ad_display_name;

				// Create tab element
				this.tab_li = $('<li />', {
						title: 'Search ' + self.ad_display_name,
						'class': 'hidden'
						}
					).
					/**
					 * add data for this tab
					 */
					data('tabObj', {
						tab: self
					}).
					/**
					 * add an anchor element with a background Image
					 */
					append( $('<div />', {
						"class": "tab",
						html:	'<span>' + ad_display_name + '</span>'
					})).
					/**
					 * add a "delete button" div
					 */
					append( $('<div />', {
						"class":	'delete',
						title:		'Remove ' + self.ad_display_name
					}));

				// Start: BBS-2664
				if (EyelinerAd.isActive()) {
					$.each(EyelinerAd.adsList, function() {
						if (self.ad_id == this) {
							self.tab_li.data(EyelinerAd.HAS_EYELINER_AD, true);
							return false;
						}

						return true;
					});
				}
				// End: BBS-2664

				// display a generic tab containing only the ad name if the ad image isn't found
				if (self.ad_logo) {
					this.tab_li.find('.tab').css('background-image', "url(' " + self.ad_logo + " ')");
				} else {
					this.tab_li.find('.tab').addClass('generic');
				}

				return this.tab_li;
			},

			/**
			 * Controller for the display functionality of this tab
			 * @returns this
			 */
			display: function () {

				// always load an iframe
				this.makeActive().loadIframe();

				try {
					if (this.shouldPopNewWindow) {
						this.displayInPopUp();
					} else {
						this.displayInIframe();
					}
				} catch (e) {
					// Catch exception thrown by an AD to avoid breaking the rest of the page
				}

				this.options.container.trigger('tabBrowsing:tabDisplayed', this);

				return this;
			},

			/**
			 * Puts the last tab in focus and activates it
			 * @returns this
			 */
			focusLastTab: function () {

				var tab_count = this.options.container_ul.find('li').length;
				if (tab_count < this.options.maxTabDisplay) {return this;}

				var increment_diff = Math.abs(this.options.tabIncrement * (this.options.maxTabDisplay - this.tab_li.index()));
				var move_to = -(increment_diff + this.options.tabIncrement);

				this.options.container_ul.css('left', 0);
				this.options.container_ul.animate({
					left:  move_to
				}, 300);

				this.makeActive();

				return this;
			},

			/**
			 * Returns whether or not a tab is active (determined by class name)
			 * @returns bool
			 */
			isActive: function() {
				return this.tab_li.hasClass('active');
			},

			/**
			 * Sets appropriate class names and actions to make a tab *Active*
			 * @returns this
			 */
			makeActive: function () {
				// reset class names
				this.tab_li.siblings().removeClass('active');
				this.tab_li.addClass('active').removeClass('hidden');
				return this;
			},

			/**
			 * Creates an iframe element if it doesn't exist and returns it
			 * @returns Jquery iframe obj
			 */
			getIframeElem: function () {
				return $('<iframe />', {
						frameborder: "0",
						"class":			"hidden white tabIframe",
						src:					""
					});
			},

			/**
			 * Adds an iFrame object if needed  and activates it
			 * @returns this
			 */
			loadIframe: function () {

				if (!this.tabIframe[0]) {
					// if we don't have an iframe object liked to this tab
					// create one and append it the panel
					this.tabIframe = this.getIframeElem().appendTo(this.options.panel);
				}

				// show this iframe and hide everthing else except for the dart ad
				// which will hide itself if we don't get an ad back
				this.tabIframe.show().siblings().not('.dart_ad').hide();
				return this;
			},

			/**
			 * Displays ad in iframe if not already displayed
			 * @returns this
			 */
			displayInIframe: function () {
				// load tab in an iframe
				if (this.tabIframe.attr('src') === '') {
					this.tabIframe[0].src = this.ad_url;
				}

				return this;
			},

			/**
			 * Creates and Displays popup html for this tab
			 * @returns this
			 */
			showPopupHtml: function () {
				var self = this;

				var panelSelector = '#panelHtml';

				$(panelSelector).remove();

				if (!this.popup_html[0]) {
					this.popup_html = $('<h2 />', {
						"class": "popup_html",
						html: 'Your search results for <span>' + self.ad_display_name + '</span> opened in a new window.'
						});

					this.popup_html.find('span').bind({
						mouseover: function () {
							$(this).css('color', '#F89839');
						},
						mouseout: function () {
							$(this).css('color', '#003366');
						},
						click: function (event) {
							event.preventDefault();
							if (!self.poppedNewWindowAd()) {
								self.showPopupBlockedMsg();
							}
						}
					});
				}

				var panelCss = 'panelHtml';

				this.popup_html.prependTo(this.options.panel).wrap("<div id='panelHtml' class='" + panelCss + "' />");

				return this;
			},

			/**
			 * Display tab in a popup window and show message
			 */
			displayInPopUp: function () {
				// let the gradient show
				this.tabIframe.hide();

				// If there isn't a widow to be focused let's pop one
				// and set the iframe popup msg
				if (!this.focusedPopupAd(this.ad_id)) {
					if (!this.poppedNewWindowAd()) {
						this.showPopupBlockedMsg();
					}
				}

				// get popup msg to be displayed in iframe
				this.showPopupHtml();
			},

			/**
			 * pops a new window for an ad
			 * @returns boolean - true: if a new window got popped succesfully
			 */
			poppedNewWindowAd: function () {

				var popupWindowNames = this.options.popupWindowNames;
				var ad_id = this.ad_id;

				// Only focus window if it's already open
				// else open new window
				if (!this.focusedPopupAd()) {
					var window_name = 'bb_search_' + this.ad_display_name.replace(/([^a-zA-Z])/g, '_');
					popupWindowNames[ad_id] = window.open('', window_name, this.options.window_opts);

					if (popupWindowNames[ad_id]) {
						popupWindowNames[ad_id].location.href = this.ad_url;
						popupWindowNames[ad_id].focus();

						// Publish ad click tracking for IE 7/8.  Other Browsers
						// Will do ad click tracking from templates/pages/search_redirect.tpt.php
						if ($.browser.msie && ($.browser.version == "7.0" || $.browser.version == "8.0")) {
							$.publish('/page/search_redirect/', [{ad_id: ad_id}]);
						}
					} else {
						// got blocked
						return false;
					}
				}
				return true;
			},

			/**
			 * Focuses a popped up ad window
			 * @returns boolean - true: if window got focused
			 */
			focusedPopupAd: function () {
				var ad_id = this.ad_id;
				if (this.options.popupWindowNames[ad_id] && !this.options.popupWindowNames[ad_id].closed) {
					this.options.popupWindowNames[ad_id].focus();
					return true;
				}
				return false;
			},

			/**
			 * show popup Blocker message
			 *
			 */
			showPopupBlockedMsg: function () {
				var self = this;

				$.asyncLoad('#dhtml_checkboxes', 'popup_blocked_msg', function(elm_selector) {

					var popup = new DHTMLPopup($(elm_selector).attr('id'));
					popup.setPageMask('page_mask');
					popup.show();

					var output = $('<div />', {
						css: {
							height: 31,
							width: 110,
							display: 'block',
							cursor: 'pointer'
						},
						click: function () {
							if (!self.poppedNewWindowAd()) {
								// recursion fail
								// We should never get to this point but just in case..
								// let's show the popup blocker (again)
								self.showPopupBlockedMsg();
							}

							// remove popup blocker box
							popup.hide();
						},
						html: '<p>' + self.ad_display_name + '</p>'
					});

					// if we have a tab logo and a regular logo
					if (self.ad_logo) {
						output.css({
							background: "url('" + self.square_ad_logo +  "') 0 -65px no-repeat"
						});
						output.find('p').hide();
					}

					$('#popup_on_logos').html('').append(output);
					$('#popup_on_msg').hide();
					$('#popup_on_text p').html("Please click the ad below to open your search results window.");
				});
			},

			/**
			 * when called it'll track this tab's url as an attemped search
			 * attempted is denoted by the "click_type=c" param
			 * @returns this
			 */
			trackAttemptedSearch: function () {
				var tracking_url = this.ad_url + '&click_type=c';
				this.sendTrackingImage(tracking_url);

				return this;
			},

			/**
			 * Sends specif vendor tracking
			 * @returns this
			 */
			trackVendor: function () {
				var self = this;

				// list or urls to track based on vendor
				var vendorTracking = {
					air: [{
						adName: /travelocity(?!(?:_canada|_ca_))/i,
						url: 'http://travel.travelocity.com/flights/InitialSearch.do?WA1=03010&WA2=223933409&WA3=47706889&WA4=0&WA5=1653085&WA6=72188'
					}],
					hotel: [{
						adName: /travelocity(?!(?:_canada|_ca_))/i,
						url: 'http://travel.travelocity.com/hotel/HotelCobrand.do?WA1=03010&WA2=223933521&WA3=47706965&WA4=0&WA5=1653085&WA6=72188'
					}],
					vacation: [{
						adName: /travelocity(?!(?:_canada|_ca_))/i,
						url: 'http://travel.travelocity.com/trips/ClarifySearchTimekeeper.do?WA1=03010&WA2=223933563&WA3=47706967&WA4=0&WA5=1653085&WA6=72188'
						}],
					car: [{
						adName: /travelocity(?!(?:_canada|_ca_))/i,
						url: 'http://travel.travelocity.com/car/PostFrondDoor.do?WA1=03010&WA2=223933491&WA3=47706961&WA4=0&WA5=1653085&WA6=72188'
					}],
					cruise: [{
						adName: /travelocity(?!(?:_canada|_ca_))/i,
						url:	'http://leisure.travelocity.com/Vacations/Cruise/Finder/Results/1,,TRAVELOCITY,00.html?WA1=03010&WA2=223933513&WA3=' +
									'47706963&WA4=0&WA5=1653085&WA6=72188'
					}],
					vacation_rental: [{}]
				};

				$.each(vendorTracking[BookingBuddy.searchMode], function(index, vendor) {
					if (vendor.adName && vendor.url) {
						if (vendor.adName.test(self.ad_display_name)) {
							setTimeout(function() {
								self.sendTrackingImage(vendor.url);
							}, 1000);
							return false;
						}
						return true;
					}
				});

				return this;
			},

			/**
			 * Creates an image using the passed in url for tracking purposes
			 * @param {string} url
			 */
			sendTrackingImage: function (url) {
				var track = new Image();
				track.src = url;
			}
		}
	});

	/**
	 * --------------------------------------
	 * Plugin setup
	 * --------------------------------------
	 */
	$.fn.tabBrowsing = function (options) {

		// if we don't have a siteRedirectUrl we can't do anything- abort
		if (!$('#siteRedirectUrl')[0]) {
			return this;
		}

		return this.each(function () {
			var element = $(this);
			// Return early if this element already has a plugin instance
			if (element.data('tabBrowsing')) {
				return;
			}

			// Store plugin object in this element's data
			var tabBrowsing = new TabBrowsing(this, options);

			element.data('tabBrowsing', tabBrowsing);
		});
	};

})(jQuery);

/**
 * This module contains settings and functions that control the 
 * various types of "windows" that we might open for tabbed browsing. 
 * This needs to be a separate static class since it gets called from various pages
 */

(function ($){
BookingBuddy.TabbedWindowHandler = {

	/**
	* A reference to a window opened using the openTabbedWindow
	* method and data we need to know about it. Useful if you need 
	* to open a window in the background and then later do something 
	* with it.
	*
	* @param object 
	*/
	win: null,

	/**
	 * object to store the window properties for opening or resizing
	 * @var object
	 */
	winProperties: {},

	/**
	 * string to store/cache the window openning setting
	 *	@param object
	 */
	winSettings: {},

	/**
	 * Flag to load tabbed browsing vs. multiple windows, defaults to true unless broswer is ie6
	 * @param boolean
	 */
	tabbed: (!$.browser.msie || $.browser.version != 6),

	/**
	 * Tab search url
	 * @param string
	 */
	searchUrl: '/tabsearches.php',

	/**
	 * The window mode
	 * @var string
	 */
	mode: null,

	/**
	 * Open the tabbed search window using different settings based browser
	 * height, width and reference to the window opened 
	 * 
	 * @param string url
	 * @return {object} window
	 */
	openTabbedWindow: function(url) {

		if (url.indexOf('?') === -1) {
			url = '?' + url;	
		} 
		url = BookingBuddy.TabbedWindowHandler.searchUrl + url;

		var open_window = BookingBuddy.TabbedWindowHandler.win;

		// the window was opened ahead of time, use it
		if (open_window && !open_window.closed && url) {
			open_window.location.href = url;
			open_window.focus();
			return open_window;
		}
		
		var options = BookingBuddy.TabbedWindowHandler.getWindowOptions(BookingBuddy.TabbedWindowHandler.getMode());

		if ($.isNullOrUndefined(url)) {
			url = '';
		}

		var d = new Date();
		var win = window.open('', 'bb_tabbed_window' + d.getTime(), options);
		if (!win) {
			return null;
		}

		// if there was no URL this window is being opened in advance
		// to make sure it doesn't get caught by a popup blocker
		if (!url) {
			win.blur();
			window.focus();
		}

		win.location.href = url;
		BookingBuddy.TabbedWindowHandler.win = win;

		return win;
	},

	/**
	 * Resize an already opened tabbed browsing window based on width,
	 * height, top and left positions for specific browsers
	 * 
	 * @return {object} window
	 */
	resizeTabbedWindow: function() {
		var mode = BookingBuddy.TabbedWindowHandler.getMode();
		var properties = BookingBuddy.TabbedWindowHandler.getWindowProperties(mode);

		var win = BookingBuddy.TabbedWindowHandler.win ? BookingBuddy.TabbedWindowHandler.win : window;

		win.moveTo(properties['top'], properties['left']);
		win.resizeTo(properties['width'], properties['height']);

		return win;
	},

	/**
	 * Determine the tabbed browsing window mode based on the browser
	 *
	 * @return string browser window mode
	 */
	getMode: function() {
		var mode = BookingBuddy.TabbedWindowHandler.mode;
		if (mode) {
			return mode;
		}

		if ($.browser.webkit && navigator.appVersion.toLowerCase().match("chrome")) {
			// CHROME
			mode = 'standard';
		} else if ($.browser.msie) {
			// IE
			mode = 'fullscreen';
		} else {
			mode = 'cover';
		}

		BookingBuddy.TabbedWindowHandler.mode = mode;
		return mode;
	},

	/**
	 * Override the default mode that would normally be determined
	 * by the browser
	 *
	 * @param string mode - the window mode
	 */
	setMode: function(mode) {
		BookingBuddy.TabbedWindowHandler.mode = mode;
	},

	/**
	 * Get window properties based on the option mode. Currently available properties include
	 * width, height, and options for opening a new window
	 *
	 * @param mode string - the option mode
	 * @return object - window properties for the specifid mode
	 */
	getWindowProperties: function(mode) {
		if (BookingBuddy.TabbedWindowHandler.winProperties[mode]) {
			return BookingBuddy.TabbedWindowHandler.winProperties[mode];
		}

		var setting, top, left, height, width;

		// Get the coordinates of the top left of the screen relative to the
		// user's monitor.
		var winTop = !$.isUndefined(window.screenY) ? window.screenY : window.screenTop;
		var winLeft = !$.isUndefined(window.screenX) ? window.screenX : window.screenLeft;

		// When tabs open in tabbed search mode, this modifies how
		// far down the page they will open for different browsers
		// FireFox IE and Safari all have unique values and are the
		// only users of this variable
		var faux_tab_placement = $.browser.mozilla ? 55 : ($.browser.msie ? 65 : 90);

		switch (mode) {
			case 'cover':
				height  = $.browser.mozilla ? (screen.availHeight - winTop) : $(window).height() + 10;
				width   = $(window).width() + 10;
				if (width < 1000) {
					width = 1000;
				}
				top     = winTop;
				left    = winLeft;

				setting = "status=0,toolbar=0,location=1,menubar=0,resizable=1,scrollbars=0,height=" + height + ",width=" + width +
					",top=" + top + ",left=" + left;
				break;

			case 'fullscreen':
				height  = screen.availHeight;
				width   = screen.availWidth;
				top     = 0;
				left    = 0;

				setting = "fullscreen=1,status=0,toolbar=0,location=1,menubar=0,resizable=1,scrollbars=0";
				break;

				// regular tabbed browsing for well behaving browsers (not IE!!!)
			case 'standard':
			default:
				height  = 1000;
				width   = 1040;
				top = winTop + 60;
				left    = winLeft + 20;
				setting = "status=1,toolbar=1,location=1,menubar=1,resizable=1,scrollbars=1,height=" + height + ",width=" + width +
					",top=" + top + ",left=" + left;
				break;
		}

		var properties = {
			options: setting,
			height: height,
			width: width,
			top: top,
			left: left
		};

		BookingBuddy.TabbedWindowHandler.winProperties[mode] = properties;
		return BookingBuddy.TabbedWindowHandler.winProperties[mode];
	},

	/**
	 * Returns window.open settings based on option passed in
	 * options: 'cover, fullscreen, search, standard'
	 * @param string option - type of "window.open" setting to return
	 * @returns window.open setting
	 */
	getWindowOptions: function (option) {

		if (BookingBuddy.TabbedWindowHandler.winSettings[option]) {
			return BookingBuddy.TabbedWindowHandler.winSettings[option];
		}

		var properties = BookingBuddy.TabbedWindowHandler.getWindowProperties(option);
		var setting = properties['options'];

		BookingBuddy.TabbedWindowHandler.winSettings[option] = setting;
		return BookingBuddy.TabbedWindowHandler.winSettings[option];
	}

};

})(jQuery);


(function ($){
	/**
	 * Handle updating contents of the hotel details tab box
	 * based on the tabs users select. The first time tabs for the
	 * maps, attractions and reviews are activated, they are loaded
	 * via ajax. 
	 *
	 * @param {jQuery object} element Wrapper element
	 * @param {object} options Plugin options
	 */
	function BBPropertyDetailTabs(element, options) {

		// merge the passed in options with the default options
		this.options = $.extend({}, BBPropertyDetailTabs.defaults, options);
		this.wrapper = $(element);

        this.propertyId = $(this.options.propertyIdSelector, this.wrapper).first().text();
        this.selectedEvent = this.options.selectedEvent;

        this.$attractionsTab = $(this.options.attractionContainer, this.wrapper);
        this.$reviewsTab = $(this.options.reviewContainer, this.wrapper);
        this.$mapTab = $(this.options.mapContainer, this.wrapper);

        this.$defaultTab = $('li.tab_nav.selected', this.wrapper);

		this.init();
  	}

	/**
	 * Default input options for the HotelListing object
	 */
	BBPropertyDetailTabs.defaults = {
		/**
		 * HTML element selector for the attractions tab content
		 * @var {string}
		 */
		attractionContainer: '#attractions_tab_content',

		/**
		 * HTML element selector for the reviews tab content
		 * @var {string}
		 */
		reviewContainer: '#reviews_tab_content',

		/**
		 * HTML element selector for the map tab content
		 * @var {string}
		 */
		mapContainer: '#map_tab_content',

		/**
		 * HTML element selector in which the actual map will be loaded
		 * @var {string}
		 */
		mapContent: '.google_map',

		/**
		 * HTML element selector for element containing the property id
		 * @var {string}
		 */
		propertyIdSelector: '.property_id',

		/**
		 * Event triggered when a tab is selected
		 * @var {string}
		 */
		selectedEvent: 'BBPropertyDetailTabs::selected',

		/**
		 * Templates to use for loading content in the tabs
		 * @var {object}
		 */
        templates: {

            attraction: '<li><span class="ta_rating no_xs no_xs#{rating}"></span> #{distance} mi &mdash; #{name}</li>',

            review: '<div class="review #{hideReview}"> ' +
                        '<div class="review_title">"#{title}"</div>' +
                        '<div class="review_content">' +
                            '<div class="summary">#{summary}<span class="ellipsis #{noSummary}">&#8230;</span> <a href="#" class="#{noSummary}"> more &raquo;</a></div>' +
                            '<div class="full hide">#{fullContent} <a href="#">&laquo; less</a></div>' +
                            '<div class="review_byline">#{date} - #{author}</div> ' +
                        '</div> ' +
                    '</div>'
        },

		tabOptions: {

			attraction: {
				types: ['Attraction', 'Eatery'],
				radius: 75
			},

			reviews: {
				numVisible: 3
			},

			map: {
				zoom: 14,
				mapTypeControl: false
			}
		}

	};

	/**
	 * Initializes the BBPropertyDetailTabs object.
	 */
	BBPropertyDetailTabs.prototype.init = function() {
		var self = this;

		// hook up the event for toggling tabs
		$('.tab_nav', self.wrapper).click($.proxy(self.tabClicked, self));

		// hook up the load events for the first time each tab is clicked
		self.$attractionsTab.one(self.selectedEvent, $.proxy(self.loadAttractions, self));
		self.$reviewsTab.one(self.selectedEvent, $.proxy(self.loadReviews, self));
		self.$mapTab.one(self.selectedEvent, $.proxy(self.loadMap, self));

		// defer loading the intial tab until after page load as to not slow down the page
		$(window).load(function() {
			self.$defaultTab.click();
		});

	};

	/**
	 * Handle a tab being clicked event
	 */
	BBPropertyDetailTabs.prototype.tabClicked = function(e) {
		e.preventDefault();
		e.stopPropagation();

		$('.tab_nav, .tab', this.wrapper).removeClass('selected');

		var $activeTab = $(e.currentTarget),
			tabId = $activeTab.attr('id'),
			$activeTabContent = $('#' + tabId + '_content', this.wrapper);

		$activeTab.addClass('selected');
		$activeTabContent.addClass('selected');

		$activeTabContent.trigger(this.selectedEvent);
	};

	/**
	 * Load the attractions tab content
	 */
	BBPropertyDetailTabs.prototype.loadAttractions = function() {
		var self = this;

		var options = self.buildAttractionTabOptions(),
			template = self.options.templates.attraction;

		$.bbget('Locs.getMultipleInRadius', options, function(data) {

			if ($.isNullOrUndefined(data) || data.length === 0) {
				self.$attractionsTab.children().hide();
				self.$attractionsTab.find('.error_message').show();
				return;
			}

			var $elm, html, params;

			$.each(data, function(type, content) {
				$elm = $('.' + type + '_list', self.wrapper);
				html = '<ul>';

				$.each(content, function(index, entry) {

					params = {
						name: entry.name,
						distance: Math.round(entry.distance * 10) / 10,
						rating: entry.rating
					};
					html += $.tmpl(template, params);
				});

				html += '</ul>';
				$elm.html(html);

			});

		});
	};

	/**
	 * Load the reviews tab content
	 */
	BBPropertyDetailTabs.prototype.loadReviews = function() {
		var self = this;
		var template = self.options.templates.review,
			visibleReviews = self.options.tabOptions.reviews.numVisible - 1;

		$.bbget('HotelReviews.getFullReviews', {ta_hotel_id: self.propertyId}, function(data) {
			var $reviewList = $('.review_list', self.wrapper);

			if (!data || !data.reviews || data.reviews.length === 0) {
				self.$reviewsTab.children().hide();
				self.$reviewsTab.find('.error_message').show();
				return;
			}

			var html = '',
				wordsPerSummary = 35;
			$.each(data.reviews, function(index, review) {

				// split the full review into a summary and the full text
				// will be hidden until users expand it
				var reviewParts = review.reviewText.split(/\s+/),
					summary = reviewParts.slice(0, wordsPerSummary).join(' '),
					full = review.reviewText;

				// if all the review text is in the summary, the
				// more/less links should be hidden
				var noSummary = reviewParts.length <= wordsPerSummary ? 'hide' : '',
					hideReview = index > visibleReviews ? ' hide' : '';

				var data = {
					title: review.reviewTitle,
					summary: summary,
					fullContent: full,
					date: review.reviewPublishDate,
					author: review.reviewer,
					noSummary: noSummary,
					hideReview: hideReview
				};

				html += $.tmpl(template, data);
			});
			$reviewList.html(html);

			$moreReviews = self.$reviewsTab.find('.more_reviews');

			var moreReviewsUrl = '';
			// add tracking to the more reviews link
			if (data.location && data.location.url) {
				moreReviewsUrl = data.location.url;

				if (BookingBuddy.taReviewsMCID) {
					moreReviewsUrl = moreReviewsUrl.replace('-Reviews', '-m' + BookingBuddy.taReviewsMCID + '-Reviews');
				}

				// Send users to TA.co.uk if they're coming from BB.co.uk
				if (BookingBuddy.affiliateName == 'bookingbuddy_co_uk') {
					moreReviewsUrl = moreReviewsUrl.replace('tripadvisor.com', 'tripadvisor.co.uk');
				}
			}

			// The first time the more reviews link is clicked, show any hidden
			// reviews. After the first click, if the link has the 'popup' class,
			// additional clicks will open the full reviews page in a popup. Otherwise, 
			// the default handler takes over.
			$moreReviews.one('click', function(e) {
				e.stopPropagation();
				e.preventDefault();

				$('.review:hidden', $reviewList).slideDown();

				if ($moreReviews.hasClass('popup') && moreReviewsUrl) {
					var externalPopup = new ExternalPopup({
						url: moreReviewsUrl,
						location: ExternalPopup.LOCATIONS.ABOVE,
						type: ExternalPopup.TYPES.EVERYCLICK,
						placement: ExternalPopup.PLACEMENTS.CENTER,
						width: 1000,
						height: 600,
						triggerElement: '#' + $moreReviews.attr('id')
					});
				}

				// Subsequent clicks to the link should publish the '/click/TAHotelReview'
				// event in order to trigger tracking (as of this writing, only UK is subscribing
				// to this event)
				$moreReviews.click(function() {
					$.publish('/click/TAHotelReview', [this]);
				});
			});

			if (moreReviewsUrl) {
				// Only set the href if the element doesn't have the popup class --
				// if it does, the url is handled by the ExternalPopup class
				if (!$moreReviews.hasClass('popup')) {
					$moreReviews.attr('href', moreReviewsUrl);
				}

				$moreReviews.show();
			}

			// set up the more/less links to show/hide the full review content
			self.$reviewsTab.find('.review a').click(function(e) {
				e.preventDefault();

				var $wrapper = $(this).closest('.review_content'),
					$fullContent = $wrapper.find('.full').first(),
					$summary = $wrapper.find('.summary').first();

				$fullContent.toggle();
				$summary.toggle();
			});
		});
	};

	/**
	 * Load the attractions tab content
	 */
	BBPropertyDetailTabs.prototype.loadMap = function() {
		var self = this;
		var options = self.options.tabOptions.map;

		var latitude = $('.latitude', self.$mapTab).text(),
			longitude = $('.longitude', self.$mapTab).text(), 
			centerPoint = { latitude: latitude, longitude: longitude };

		var map = $(self.options.mapContent, self.mapContainer).googleMapFactory(centerPoint, options);

		BookingBuddy.Maps.createMarkerType('basicHotel');

		map.addMarker(self.propertyId, {type: 'basicHotel'}, {coords: centerPoint}, {clickable: false});
		map.toggleMarkersByType(true);
	};

	/**
	 * Build the request parameters used when loading attractions content
	 */
	BBPropertyDetailTabs.prototype.buildAttractionTabOptions = function() {
		var options = this.options.tabOptions &&
						this.options.tabOptions.attraction ?
						this.options.tabOptions.attraction : null,
			types = options && options.types ?
						options.types : ['Attraction', 'Eatery'];
			radius = options && options.radius ?
						options.radius : 20;

		var requests = {};

		for (type in types) {

			requests[types[type].toLowerCase()] = {
				id: this.propertyId,
				radius: radius,
				type_filter: types[type]
			};
		}

		return { requests: requests };
	};

	/**
	 * Plugin setup
	 */
	$.fn.propertyDetailTabs = function (options) {
		return this.each(function () {
			var element = $(this);

			// Return early if this element already has a plugin instance
			if (element.data('propertyDetailTabs')) {
				return;
			}

			// Store plugin object in this element's data
			var propertyDetailTabs = new BBPropertyDetailTabs(this, options);

			element.data('propertyDetailTabs', propertyDetailTabs);
		});
	};  
})(jQuery);


(function ($){
	function HotelListing(element, options) {
		// merge the passed in options with the default options
		this.options = $.extend({}, HotelListing.defaults, options);
		this.element = $(element);
		this.listingWrapper = $(this.options.listingWrapper);
		this.results_container = $(this.options.results_container, this.listingWrapper);
		this.results_list = this.options.results_list ? $(this.options.results_list, this.listingWrapper) : $('<ul/>');
		this.results_counters = $(this.options.results_counters, this.listingWrapper);
		this.results_container.append(this.results_list);
		this.message_container = $(this.options.message_container, this.listingWrapper);
		this.matching_results_message_container = $(this.options.matching_results_message_container, this.listingWrapper);
		this.selectorScrollToTop = $(this.options.selectorScrollToTop);
		this.header_message_container = $(this.options.header_message_container);
		this.headerLoader = $(this.options.headerLoader, this.listingWrapper);
		this.hacReloadCounter = this.options.maxHACReloads;
		this.loader = null;
		this.controls = [];
		this.radius = this.options.defaultRadius;
		this.initial_ta_id;
		this.featured_property = null;
		this.property_container = $(this.options.property_container);
		this.formElements = {};
		this.hotelCount = 0;
		this.init();
  	}

	/**
	 * Default input options for the HotelListing object
	 */
	HotelListing.defaults = {
		/**
		 * Should the Hotel Listing object use the MVC system.
		 * BBS-2026: this var is only useful for the bbs-2026
		 *   AB Test.
		 * Default: false
		 *
		 * @var {bool}
		 */
		bbs2026: false,

		/**
		 * Animation speed for the fade in/out of the hotel listings
		 *
		 * @var {int}
		 */
		animation_speed: 500,

		/**
		 * HTML Element selector containing the main hotel listing content
		 *
		 * @var {string}
		 */
		listingWrapper: '#hotel_listing_results',

		/**
		 * HTML Element selector responsible for housing the result set
		 *
		 * @var {string}
		 */
		results_container: '#hotel_results',

		/**
		 * HTML Element housing the messages (error/no results)
		 *
		 * @var {string}
		 */
		message_container: '#hotel_message',

		/**
		 * HTML Element selector for the matching results message
		 *
		 * @var {string}
		 */
		matching_results_message_container: '#results_message',

		/**
		 * HTML Element selector for the HAC refresh form
		 *
		 * @var {string}
		 */
		selectorHACRefreshForm: 'form.updateHotelListing',

		/**
		 * Message the user will get when they perform a search without
		 * a radius (below the filters but above the sorter)
		 *
		 * @var {string}
		 */
		matching_results_message: '#{count} Hotels match your search criteria',
		/**
		 * Message the user will get when they perform a search with
		 * a radius (below the filters but above the sorter)
		 *
		 * @var {string}
		 */
		matching_results_message_radius: '#{count} Hotels within #{radius} miles of #{location}',

		/**
		 * Message user will see when an ajax error occurs
		 *
		 * @var {string}
		 */
		message_error: 'Sorry, but an error has occurred',

		/**
		 * Message a user will see when there are no results matching their search
		 *
		 * @var {string}
		 */
		message_no_results: 'No Hotels were found matching your criteria.  Please broaden your search.',

		/**
		 * Message the user will get when there are no results matching their search
		 * when requesting date-specific hotel info from the HAC service
		 *
		 * @var {string}
		 */
		message_no_results_for_dates: 'Try changing your dates and search again.',

		/**
		 * HTML Element selector for the filter header message container
		 * This will be updated if/when the user changes the location of the
		 * search.
		 *
		 * @var {string}
		 */
		header_message_container: '#header_title',

		/**
		 * HTML Element selector located within the header message container
		 * to show an inobtrusive loading message
		 *
		 * @var {string}
		 */
		headerLoader: '.loading',

		/**
		 * template for the header message
		 *
		 * @var {string}
		 */
		header_message: '#{location} Hotels: #{count} Hotels Found',

		/**
		 * template for the header message when showing HAC results
		 *
		 * @var {string}
		 */
		header_message_hac: '#{totalCount} #{location} Properties, #{count} Available On Your Dates!',

		/**
		 * HTML ID of the dhtml 'loading results' div
		 *
		 * @var {string}
		 */
		dhtml_loader: 'hotel_listing_loader',

		/**
		 * Initial TA ID to use. Allows an ID to be passed when HotelListings is
		 * initialized, in case one is not available via the querystring. If this
		 * is not set, querystring value will be used.
		 *
		 * @var {int}
		 */
		initial_ta_id: 0,

		/**
		 * Offset (in px) to use for the 'left' CSS attribute of the DHTML loader image.
		 * This value is added to the 'left' attribute of the results container
		 * to determine the positioning of the loader.
		 */
		loader_left_offset: 170,

		/**
		 * Should the hotel listing attempt to load hotels from HAC if possible?
		 */
		hacEnabled: false,

		/**
		 * Maximum number of times to reload the data returned from HAC. The response
		 * from HAC will state if data should no longer be reloaded, but this counter
		 * is a safe-guard to prevent a potential infinite loop (a la OT).
		 */
		maxHACReloads: 4,

		/**
		 * HTML Element selector for a property container. This is currently only used
		 * on BBUK, it's needed here to be able to control certain behavior in the
		 * container based on filter events.
		 *
		 * @var {string}
		 */
		property_container: '.property_wrapper',

		/**
		 * A default radius to use. Values in the querystring will still override this.
		 *
		 * @var {int}
		 */
		defaultRadius: 30,

		/**
		 * Name of the script to use when making an AJAX request to fetch the listings
		 *
		 * @var {string}
		 */
		requestScript: '/hotel_specific',

		/**
		 * Default list of controls that are on the page
		 *
		 * @var {array}
		 */
		controls: {
			/**
			 * Location suggest arrival city input
			 */
			city: {
				input: '#hotel_filter_arrival_city',
				type: 'textbox',
				hide: false,
				options: {
					locs_field: "city"
				}
			},

			/**
			 * Hotel name filter input textbox
			 */
			hotel: {
				input: '#hotel_filter_arrival_name',
				type: 'textbox',
				hide: false,
				options: {
					locs_field: "hotel_name"
				}
			},

			/**
			 * Star rating slider
			 */
			star_rating: {
				input: '#hotel_star_rating',
				type: 'slider',
				hide: false,
				options: {
					image_class: 'star_rating'
				}
			},

			/**
			 * TripAdvisor slider
			 */
			ta_rating: {
				input: '#hotel_ta_rating',
				type: 'slider',
				hide: false,
				options: {
					message: "#{low} & above",
					image_class: 'ta_rating',
					locs_field: "rating"
				}
			},

			/**
			 * Average Prive double slider
			 */
			avg_price: {
				input: '#hotel_avg_price',
				type: 'slider',
				hide: false,
				options: {
					message: "$#{low} - $#{high}",
					locs_field: 'avg_price',
					has_image: false,
					slider_options: {
						min: 0,
						max: 500,
						step: 10,
						values: [0,500],
						range: true
					}
				}
			},

			/**
			 * Amenities multi-select checkbox list
			 * with dhtml layer
			 */
			amenities: {
				input: '#hotel_amenities',
				type: 'amenities',
				hide: false,
				options: {}
			},

			/**
			 * favorites filter
			 */
			favorites: {
				input: '#hotel_favorites',
				type: 'favorites',
				hide: false,
				options: {
					locs_field: 'favorites'
				}
			},

			/**
			 * Pager control
			 */
			pager: {
				input: '#hotel_pager',
				type: 'pager',
				hide: false,
				options: {
					results_limit: 10,
					locs_field: 'offset'
				}
			},

			/**
			 * Results sorting control
			 */
			sorter: {
				input: '#hotel_sorter',
				type: 'sorter',
				hide: false,
				options: {
					label_text: 'Sort by: '
				}
			}
		}
	};

	/**
	 * Initializes the HotelListings object.
	 */
	HotelListing.prototype.init = function() {
		var self = this;

		var q_string = $.extend({}, BookingBuddy.queryString);
		if ('radius' in q_string) {
			this.radius = q_string.radius;
		}

		if (this.options.initial_ta_id) {
			this.initial_ta_id = this.options.initial_ta_id;
		} else if ('tid' in q_string) {
			this.initial_ta_id = q_string.tid;
		}

		if (this.property_container.length > 0) {
			this.featured_property = this.property_container.find('.hotel_id').first().text();
		}

		this.initLoader();

		// hook up any 'reset' clicks on the page, which should reset all filter data
		$j('.reset').live('click', function(e) {
			e.preventDefault();
			self.reset();
		});

		// add all of the controls to the object
		$.each(this.options.controls, function(index, control) {
			if (!control.hide) {
				self.addControl(HotelListingControl_Factory.create(control.input, control.options, control.type));
			} else {
				$(control.input).hide();
			}
		});

		this.setupForm();

		// load the initial filtering
		if (this.canLoadHAC()) {
			this.populateWithHAC(true);
		} else if (this.options.bbs2026) {
			$.publish('/submit/hotelListingFilter', [BookingBuddy.HotelListing.metaData]);
		} else {
			this.populate();
		}
	};

	/**
	 * Add control to the HotelListing object
	 *
	 * @param {HotelListingControl} control
	 */
	HotelListing.prototype.addControl = function (control) {
		var self = this;
		$(control).bind('HotelListingControl:update', function(event, data) { self.populate(); });
		self.controls.push(control);
	};

	/**
	 * reset all active filter controls
	 *
	 */
	HotelListing.prototype.reset = function() {

		$.each(this.controls, function(index, control) {
			control.reset();
		});

		// with reset, we want to bypass hac altogether
		this.hacFormDisabled = true;
		this.populate();
		this.hacFormDisabled = false;
	};

	/**
	 * Attach a search form to the hotel listing if one is present. Handles
	 * updating the meta data required for HAC searches based on the form
	 * fields.
	 */
	HotelListing.prototype.setupForm = function() {
		var $form = $(this.options.selectorHACRefreshForm).first();

		if ($form[0]) {
			BookingBuddy.HotelListing = BookingBuddy.HotelListing || {};
			BookingBuddy.HotelListing.metaData = BookingBuddy.HotelListing.metaData || {};

			// The smartform elements are not initialized at this point because is isn't
			// guaranteed smartform has been loaded yet. The elements will be
			// initialized the first time the corresponding input changes. Initial
			// values for the metaData are set within the page.
			var smartForm = $form.data('smartForm'),
				smartFormInDate = null,
				smartFormOutDate = null;

			// update the metaData number of guests when the element changes
			var $guestsElement = $form.find('select[name="num_travelers"]');
			if ($guestsElement[0]) {
				this.formElements['guests'] = $guestsElement;

				BookingBuddy.HotelListing.metaData.guests = $guestsElement.val();

				$guestsElement.change(function() {
					BookingBuddy.HotelListing.metaData.guests = $(this).val();
				});
			}

			// update the metaData in-date when the input changes
			var $inDateElement = $form.find('input.departureDate');
			if ($inDateElement[0]) {
				this.formElements['inDate'] = $inDateElement;

				var inDateChangedHandler = function() {
					if (!smartFormInDate) {
						smartForm = $form.data('smartForm');
						smartFormInDate = smartForm ? smartForm.getElementByClass('departureDate') : null;
					}

					if (smartFormInDate) {
						// pull the timestamp from smartForm instead of the element value since
						// smartForm properly handles formatting the date based on locale
						BookingBuddy.HotelListing.metaData.inDate = smartFormInDate.getTimeStampFromElement() / 1000;
					}
				};

				$inDateElement.change(inDateChangedHandler);
				// triggered when this element is updated from another form on the page
				$inDateElement.bind("commonInput:Updated", inDateChangedHandler);
			}

			// update the metaData out-date when the input changes
			var $outDateElement = $form.find('input.returnDate');
			if ($outDateElement[0]) {
				this.formElements['outDate'] = $outDateElement;

				var outDateChangedHandler = function() {
					if (!smartFormOutDate) {
						smartForm = $form.data('smartForm');
						smartFormOutDate = smartForm ? smartForm.getElementByClass('returnDate') : null;
					}

					if (smartFormOutDate) {
						// pull the timestamp from smartForm instead of the element value since
						// smartForm properly handles formatting the date based on locale
						BookingBuddy.HotelListing.metaData.outDate = smartFormOutDate.getTimeStampFromElement() / 1000;
					}
				};

				$outDateElement.change(outDateChangedHandler);
				// triggered when this element is updated from another form on the page
				$outDateElement.bind("commonInput:Updated", outDateChangedHandler);
			}

			// keep track if the location input has changed
			// TODO: this should get a better selector
			$form.find('input[name="arrival_city"]').one('change LocationSuggest:selection', function(e) {
				BookingBuddy.HotelListing.locationUpdated = true;
			});
		}
	};

	/**
	 * Adds an error message to the page (user visible)
	 * as well as hiding the results container (making the
	 * error message more visible to the user.
	 *
	 * @param {string} message (Message to disply to the user)
	 */
	HotelListing.prototype.handleError = function(contentMessage, headerMessage) {

		if (headerMessage) {
			this.header_message_container.text(headerMessage);
		}

		this.message_container.html(contentMessage).show();
		this.results_container.hide();
	};

	// START: BBS-2026
	HotelListing.prototype.populateMvc = function() {
		var self = this;
		var params = this.getFilterParams();
		params.action = 'fetchListings';

		// if hotel search pass a tid
		// if city search pass a cityId
		if (this.initial_ta_id) {
			params.tid = this.initial_ta_id;
		} else if (BookingBuddy.queryString.cityId) {
			params.cityId = BookingBuddy.queryString.cityId;
		}

		$.ajax({
				type: 'GET',
				url: self.options.requestScript,
				data: params,
				error: function(e) {
					self.handleError(self.options.message_error);
				},
				beforeSend: function() {
					// resest the error messages if they exist
					self.message_container.hide();
					self.results_container.show();

					// center the loader image
					$loader = $('#' + self.options.dhtml_loader);
					var left = $(window).scrollLeft() + ($(window).width() - $loader.outerWidth()) / 2;
					var top = $(window).scrollTop() + ($(window).height() - $loader.outerHeight()) / 2;
					$loader.css({ left: left + 'px', top:  top + 'px' });

					// display the loader image
					self.loader.show();
				},
				complete: function() {
					self.loader.hide();
				},
				success: function(e) {
						var data = JSON.parse(e);
						if (data.success && BookingBuddy.HotelListing) {
							// update the hotel listing and the metaData
							self.results_container.html(data.html);

							if (BookingBuddy.HotelListing.metaData.count === 0) {
								self.handleError(self.options.message_no_results);
							} else {
								// display price data if necessary
								if (params.filter.prices_active) {
									self.results_container.find('.avg_price').show();
								}

								HotelListing.initCheckRatesForms();
							}

							// Update the controls with the new metadata
							$.publish('/submit/hotelListingFilter', [BookingBuddy.HotelListing.metaData]);
						}
				}
		});
	};
	// END: BBS-2026

	/**
	 * Using the filter parameters and form inputs, an ajax request is made to
	 * pull hotel info to show in the Results container. If possible, hotels
	 * will also be filtered by HAC data to provide the best price for each
	 * hotel. HAC may be polled up to a set number of times to try and obtain
	 * a complete list of hotel available from HAC.
	 *
	 * @param bool showLoader Should the main loading icon be shown?
	 */
	HotelListing.prototype.populateWithHAC = function(showLoader) {
		var self = this,
	   		options = this.options,
			params = this.getFilterParams();

		params.action = 'fetchListings';

		if (this.initial_ta_id) {
			params.tid = this.initial_ta_id;
		}

		// determine if any filters have been changed from their default values
		var filteringActive = this.isFilteringActive();

		$.ajax({
			type: 'GET',
			url: self.options.requestScript,
			data: params,
			error: function(e) {
				self.handleError(self.options.message_error);
				self.unmaskElement(self.listingWrapper);
				self.headerLoader.fadeOut(options.animation_speed);
				self.loader.hide();
				self.hacReloadCounter = self.options.maxHACReloads;
			},
			beforeSend: function() {
				// reset the error messages if they exist
				self.message_container.hide();
				self.results_container.show();

				if (showLoader) {
					self.loader.show();
					// put a mask over the results while they're loading
					self.maskElement(self.listingWrapper);
				}
			},
			complete: function() { },
			success: function(e) {
				var results = JSON.parse(e);
				if (results && results.success && results.metaData) {
					var metaData = results.metaData;
					var count = metaData.count;
					$.publish('/submit/hotelListingFilter', [metaData]);

					if (count && metaData.city_name) {
						// update the filter header message with a possibly new location and count
						var headerMessage = '';
						if (metaData.using_hac) {
							headerMessage = $.tmpl(options.header_message_hac, {
								'count': count,
								'totalCount': metaData.total_count,
								'location': metaData.city_name
							});
						} else {
							headerMessage = $.tmpl(options.header_message, {
								'location': metaData.city_name,
								'count': count
							});
						}
						self.header_message_container.text(headerMessage);

						// update the matching results message with new data
						var message = $.tmpl(options.matching_results_message, {'count': count});
						self.matching_results_message_container.text(message);
					}

					var refreshingHAC = metaData.auto_reload && self.hacReloadCounter > 0;

					if (count === 0) {
						// No hotels, refreshing HAC
						if (refreshingHAC) {
							// Do nothing - keep whatever loading images are there 
							// until we have data or are no longer refreshing
							self.hacReloadCounter--;
							setTimeout(function() { self.populateWithHAC(true); }, 1000);
						// No hotels, not refreshing HAC
						} else {
							// hide main loader (in case it's present)
							self.loader.hide();
							// hide header loader (in case it's present)
							self.headerLoader.fadeOut(options.animation_speed);
							// unmask results div (in case it's masked)
							self.unmaskElement(self.listingWrapper);
							// remove saved results div hotels
							self.results_list.find('li').fadeOut(self.options.animation_speed).remove();
							// show error message
							var resetLink = '<a href="#" class="reset clearForm">view all hotels</a>';
							var errorMessage = filteringActive ? options.message_no_results : options.message_no_results_for_dates;
							self.handleError($.tmpl(errorMessage, {'reset': resetLink}), options.header_message_no_results);
							// reset hac reload counter
							self.hacReloadCounter = self.options.maxHACReloads;

							if (metaData.using_hac) {
								$.publish('/submit/HACSearch');
							}

							// Even if no results are returned from the fetch, if there's a property-specific module,
							// make sure the image & check rates are loaded
							if (self.property_container[0]) {
								HotelListing.initCheckRatesForms();
							}
						}
					} else {
						self.loader.hide();
						self.unmaskElement(self.listingWrapper);

						// Only update the content if we have a different amount of hotels.
						// Account for changes in price to existing hotels by always updating
						// the contents if we won't be refreshing again.
						if ((!refreshingHAC || self.hotelCount != count) && results.html) {
							/**
							 * In an attempt to make the content replacement as smooth as possible,
							 * we're first fading out the old content. Once the animation has ended, 
							 * we explicity set the container height and remove the old content. Then, 
							 * the new HTML chunk is added and the content is faded in. We're also 
							 * checking for opacity animation support, since IE8 can't handle it.
							 */
							
							if ($.support.opacity) {
								var height = self.results_container.height();
								self.results_container.animate({opacity: 0}, 200, 'linear', function() {

									self.results_container.height(height).empty().append(results.html);

									// Only load check rates & hotel images once we're done refreshing
									if (!refreshingHAC) {
										HotelListing.initCheckRatesForms();
									}

									self.results_container.animate({opacity: 1}, 200, 'linear', function() {
										self.results_container.css('height', 'auto');
									});
								});
							} else {
								self.results_container.empty().append(results.html);

								// Only load check rates & hotel images once we're done refreshing
								if (!refreshingHAC) {
									HotelListing.initCheckRatesForms();
								}
							}
						}

						self.hotelCount = count;

						// For showing property-specific price on BBUK
						if (metaData.show_price && self.property_container[0]) {
							var $hotelPrice = self.property_container.find('.hotel_price');
							if (metaData.property_price) {
								// toggle the text accompanying the price depending on the type
								// of price returned - best vs average price nightly rate
								if (metaData.using_hac) {
									$hotelPrice.addClass('show_best');
								} else {
									$hotelPrice.removeClass('show_best');
								}

								$hotelPrice.find('.price_number').text(metaData.property_price);
							} else {
								$hotelPrice.removeClass('show_best');
							}
							$hotelPrice.show();
						}

						if (refreshingHAC) {
							// show header loader
							self.headerLoader.fadeIn(options.animation_speed);
							// attempt to reload hac again after 1 second
							self.hacReloadCounter--;
							setTimeout(function() { self.populateWithHAC(false); }, 1000);
						} else {
							// hotels, not refreshing hac

							// hide header loader (in case it's present)
							self.headerLoader.fadeOut(options.animation_speed);
							// reset hac reload counter
							self.hacReloadCounter = self.options.maxHACReloads;

							if (metaData.using_hac) {
								$.publish('/submit/HACSearch');
							}
						}
					}
				} else {
					self.handleError(options.message_error);
					self.unmaskElement(self.listingWrapper);
					self.headerLoader.fadeOut(options.animation_speed);
					self.loader.hide();
					self.hacReloadCounter = self.options.maxHACReloads;

					// Initialize hotel image & check rates if there's a property specific module
					if (self.property_container[0]) {
						HotelListing.initCheckRatesForms();
					}
				}
			}
		});
	};

	/**
	 * Using the filter parmeters from the controls this method
	 * makes an ajax request to get the corresponding hotels
	 * then it will add them to the results container.  It will also
	 * place a loader gif on the page while it's retrieving the results
	 */
	HotelListing.prototype.populate = function() {
		if (this.canLoadHAC()) {
			this.populateWithHAC(true);
			return;
		}
		// START: BBS-2026
		if (this.options.bbs2026) {
			this.populateMvc();
			return;
		}
		// END: BBS-2026

		var self = this,
			data = null,
			item = null,
			options = this.options;

		// resest the error messages if they exist
		this.message_container.hide();
		this.results_container.show();

		this.loader.show();
		$.bbget('HotelListing.fetchFiltered', self.getFilterParams(), function(results) {
			if (results && results.success) {
				$.publish('/submit/hotelListingFilter', [results]);
				self.results_list.find('li').fadeOut(self.options.animation_speed).remove();

				if ('count' in results && 'city_name' in results) {
					// update the filter header message with a possibly new location and count
					self.header_message_container.text($.tmpl(options.header_message, {'count': results.count,'location': results.city_name}));

					// update the matching results message with new data
					var message = '';
					if (results.is_city && results.radius) {
						// we only want to display the radius message if we have a radius search on a city
						// based location.  Neightborhoods have a default radius search of 1 mile and we
						// don't want to display the radius message for those searches
						message = $.tmpl(options.matching_results_message_radius, {
							'radius': results.radius,
							'count': results.count,
							'location': results.city_name});
					} else {
						message = $.tmpl(options.matching_results_message, {'count': results.count});
					}
					self.matching_results_message_container.text(message);
				}

				if (results['hotels'].length === 0) {
					self.handleError(options.message_no_results);
				} else {
					$.each(results['hotels'], function(ta_id, content) {
						item = $('<li />', { 'class': 'hide', 'html': content });
						self.results_list.append(item);
						item.fadeIn(options.animation_speed);
					});
					HotelListing.initCheckRatesForms();

					// For showing property-specific price on BBUK
					if (results.show_price && self.property_container.length > 0) {
						self.property_container.find('.hotel_price').show();
					}
				}
			} else {
				self.handleError(options.message_error);
			}
			self.loader.hide();
		});
	};

	/**
	 * Mask the specified element with a transparent overlay
	 *
	 * @param {jquery Object} $element - jQuery object to mask
	 */
	HotelListing.prototype.maskElement = function ($element) {
		var $mask = $element.find('.mask');
		if (!$mask[0]) {
			$mask = $('<div class="mask"></div>').hide();
			$element.append($mask);
		}

		if ($mask.is(':hidden')) {
			$element.addClass('masked');
			$mask.show();
		}
	};

	/**
	 * Unmask the specified element
	 *
	 * @param {jquery Object} $element - jQuery object to mask
	 */
	HotelListing.prototype.unmaskElement = function ($element) {
		$element.find('.mask').hide();
		$element.removeClass('masked');
	};

	/**
	 * initialize the Spinning Loading DHTML Layer
	 *
	 */
	HotelListing.prototype.initLoader = function () {
		// set up the dhtml page loading popup spinner
		this.loader = DHTMLPopup_Factory.create({type: 'layer', id: this.options.dhtml_loader});
		// adjust the positioning
		var resultsContainerOffset = this.results_container.offset();
		$('#' + this.options.dhtml_loader).css({
			left: resultsContainerOffset.left + this.options.loader_left_offset + 'px',
			top:  resultsContainerOffset.top + 'px'
		});
		this.loader.addElement(this.options.dhtml_loader);
		this.loader.hide();
	};

	/**
	 * Initialize checkrates and smart forms on the page
	 * need to do this after pulling in the dynamic hotel result
	 * forms.
	 */
	HotelListing.initCheckRatesForms = function() {
		var isBBUK = BookingBuddy.affiliateName == 'bookingbuddy_co_uk',
			submitHandlers = isBBUK ? BookingBuddy.ukSubmitHandlers : BookingBuddy.bbSubmitHandlers,
			init = isBBUK ? BBUKInit : BBInit,
			hotelImageLoader = new BBHotelImages();
		// Check Hotel Rates widget form
		$(".smartForm.checkHotelRates").each( function() {
			$(this).smartFormFactory( {
				noDefaultDates: true,
				submitHandler: function(form, target, e) {
					submitHandlers.submitForm(form, target, e);
				}
			});
		});
		init.taCheckRates();
		hotelImageLoader.loadHotelImages();
	};

	/**
	 * Updates the initial_ta_id parameter
	 *
	 * Added for BBS-2026
	 *
	 * @param {int} id - TA ID for the city to base the
	 *   listing search on.
	 */
	HotelListing.prototype.setTaId = function(id) {
		this.options.initial_ta_id = id;
	};

	/**
	 * Returns a object representation of the filter options
	 * to be used in the retrieval of hotel listings
	 * from the ajax service.
	 *
	 * @returns {array}
	 */
	HotelListing.prototype.getFilterParams = function() {
		// send the current date time stamp to prevent the browser
		// caching the checkrates forms and causing all sorts of
		// form submission issues.
		var params = {now: new Date().getTime(), mcid: BookingBuddy.taCheckRatesMCID},
			self = this;

		// add the radius and the initial ta id (the one from the query
		// string) to the filter parameters
		if (this.radius) {
			params.radius = this.radius;
		}

		if (this.initial_ta_id) {
			params.location = this.initial_ta_id;
		}

		// add in form data for performing HAC requests
		var metaData = BookingBuddy.HotelListing ? BookingBuddy.HotelListing.metaData : null;
		if (metaData && !this.hacFormDisabled) {
			params.inDate = metaData.inDate;
			params.outDate = metaData.outDate;
			params.guests = metaData.guests;
		}

		// If a specific property is featured on the page (BBUK only), send
		// its ID so that it can be excluded from the list of results
		if (this.featured_property) {
			params.featured_property_id = this.featured_property;
		}

		if (this.controls) {
			// grab the data from each control
			$.each(self.controls, function(index, control) {
				if (control) {
					$.extend(true, params, control.getParams());
				}
			});
		}

		return params;
	};

	/**
	 * Check if any of the control filters are active (have values set to
	 * something other than their default value
	 *
	 * @return {bool}
	 */
	HotelListing.prototype.isFilteringActive = function() {
		var self = this;

		var filteringActive = false;
		if (self.controls) {
			// grab the data from each control
			$.each(self.controls, function(index, control) {
				if (control) {
					if (control.isFilteringActive()) {
						filteringActive = true;
						// exit the each loop
						return false;
					}
				}
			});
		}

		return filteringActive;
	};

	/**
	 * Check if we're capable of loading HAC data for this page
	 * TODO: This should be updated to check the correct meta data for a HAC
	 * request exists, but then error message handling on BBUK will need to
	 * be adjusted as well.
	 *
	 * @return {bool}
	 */
	HotelListing.prototype.canLoadHAC = function() {
		return this.options.hacEnabled;
	};

	/**
	 * --------------------------------------------------------------
	 *   HotelListingControl Definitions
	 * --------------------------------------------------------------
	 */

	/**
	 * HotelListingControl class definition
	 *
	 * contains common functionality for all it's subclasses
	 *
	 * @abstract
	 */
	var HotelListingControl = Object.subClass({
		init: function (element, options) {
			var self = this;
			this.element = $(element);
			this.options = $.extend({}, this.defaults, options);
			this.element.data('HotelListingControl', this);
			$.subscribe('/submit/hotelListingFilter', $.proxy(this.hostUpdated, this));
		},

		/**
		 * Associated arrray of default input variables
		 */
		defaults: {},

		/**
		 * Function called after the HotelListing object gets data back
		 * from the ajax call.  Use this to update the control after an
		 * update if necessary. (i.e. update pager etc)
		 *
		 * @param {array} data
		 */
		hostUpdated: function (data) {},

		/**
		 * get the json represtations of the control's data
		 *
		 * @return {array}
		 */
		getParams: function () {},

		/**
		 * Reset the control
		 * bring it back to the default position/value
		 */
		reset: function () {},

		/**
		 * Determine if any filtering is turned on for a control
		 */
		isFilteringActive: function() {}
	});

	/**
	 * HotelListingControl_sorter class definition
	 *
	 */
	var HotelListingControl_sorter = HotelListingControl.subClass({
		init: function (element, options) {
			this._super(element, options);
			this.value = this.options.default_key;
			this.sort_order = this.options.default_sort_order;
			var self = this;

			// Used by BBUK to determine whether to show prices in the hotel results
			this.prices_active = false;

			self.element.append($('<label />', { text: self.options.label_text }));

			$.each(this.options.fields, function(index, options) {
				var active = index == self.options.default_key;
				self.element.append($('<a />', {
					href: '#',
					text: options.text,
					'class': active ? 'active ' + options.order : options.order,
					click: function(event) {
						event.preventDefault();

						var $this = $(this);
						// If user clicks on the active link, switch the sort order
						// from desc to asc or vice-versa, but keep it as active
						if (index == self.value) {
							if ($this.hasClass('desc')) {
								$this.removeClass('desc').addClass('asc');
								$this.next('.arrow.active').removeClass('desc').addClass('asc');
								self.sort_order = 'asc';
							} else if ($this.hasClass('asc')) {
								$this.removeClass('asc').addClass('desc');
								$this.next('.arrow.active').removeClass('asc').addClass('desc');
								self.sort_order = 'desc';
							}
						}
						// Otherwise the user is switching between sort types, so remove
						// and add 'active' classes where needed
						else {
							self.value = index;
							self.sort_order = $this.hasClass('desc') ? 'desc' : 'asc';
							$this.siblings('a.active').removeClass('active');
							$this.addClass('active');
							$this.siblings('.arrow.active').removeClass('active');
							$this.next('.arrow').addClass('active');
						}

						if (index == 'price_avg') {
							self.prices_active = true;
						}
						$(self).trigger('HotelListingControl:update', self);
					}
				}));
				self.element.append($("<span />", { 'class': 'arrow ' + options.order + (active ? ' active' : '') }));
				self.element.append($("<span />", { 'class': 'delimeter', 'html': self.options.delimiter}));
			});
			// trim off the last delimeter
			self.element.find('span.delimeter').last().remove();
		},

		/**
		 * Associated arrray of default input variables
		 */
		defaults: {
			/**
			 * Text for this control's label
			 *
			 * @var {string}
			 */
			label_text: 'Sort by:',

			/**
			 * Key used by the HotelListing ajax controller
			 *
			 * @var {string}
			 */
			locs_field: 'sort_field',

			/**
			 * text of the delimeter between the sort filter links
			 *
			 * @var {string}
			 */
			delimiter: '|',

			/**
			 * on initial loading of the host object what should
			 * the default sorting be?  Key of the fields object
			 *
			 * @var {string} (key from fields object)
			 */
			default_key: 'rating',

			/**
			 * on initial loading of the host object what should
			 * the default sort order be?
			 *
			 * @var {string} ('asc' or 'desc')
			 */
			default_sort_order: 'desc',

			/**
			 * List of sort links
			 * key is the locs fields name
			 * value is the Link display text
			 *
			 * @var {array}
			 */
			fields: {
				star_rating: {
					text: 'Star Rating',
					order: 'desc'
				},
				rating: {
					text: 'Popularity',
					order: 'desc'
				},
				price_avg: {
					text: 'Price',
					order: 'desc'
				}
			}
		},

		/**
		 * get the object represtations of the control's data
		 * @return {array}
		 */
		getParams: function () {
			var result = {filter:{}};
			if (this.value) {
				result.filter[this.options.locs_field] = this.value;
			}

			if (this.sort_order) {
				result.filter['sort_order'] = this.sort_order;
			}

			if (this.prices_active) {
				result.filter['prices_active'] = this.prices_active;
			}

			return result;
		},

		/**
		 * Reset the control
		 * bring it back to the default position/value
		 */
		reset: function() {

		},

		/**
		 * Determine if any filtering is turned on for the control
		 */
		isFilteringActive: function() {
			// sorter doesn't filter overall results
			return false;
		}
	});

	/**
	 * HotelListingControl_pager class definition
	 *
	 */
	var HotelListingControl_pager = HotelListingControl.subClass({
		init: function (element, options) {
			this._super(element, options);
			this.page_number = 0;
			this.pages = 0;
			this.isReset = false;
			var self = this;

			$(document).bind('HotelListingControl:pagerUpdated', function(event, page) {
				self.updateDisplay(page);
			});

			this.buildControl();
		},

		/**
		 * Associated arrray of default input variables
		 */
		defaults: {
			/**
			 * Limit the number of results on each page to what?
			 *
			 * @var {int}
			 */
			results_limit: 10,

			/**
			 * template for the message that appears in the middle of the pager
			 *
			 * @var {string}
			 */
			message: 'Page #{low} of #{high}',

			/**
		 	 * HTML Element selector to scroll to the top of when the user
		 	 * clicks on one of the bottom page navigation buttons
		 	 *
		 	 * @var {string}
		 	 */
			selectorScrollToTop: '#hotel_listing_results'
		},

		/**
		 * Function to add all of the links to the control container
		 * and add all of the click functionality
		 */
		buildControl: function () {
			this.element.hide();

			var self = this,
				id = this.element.attr('id');

			this.first = $('<a />', {
				id: id + '_first',
				'class': 'first pointer',
				text:  "    ",
				target: '_blank',
  				click: function() {
					self.clickHandler(0);
				}
			});

			this.prev = $('<a />', {
				id: id + "_prev",
				'class': 'previous pointer',
				text: "   ",
				target: '_blank',
  				click: function() {
					self.clickHandler(self.page_number - 1);
				}
			});

			this.message = $('<span />', {id: id + "_message"});

			this.next = $('<a />', {
				id: id + "_next",
				'class': 'next pointer',
				text: "   ",
				target: '_blank',
				click: function() {
					self.clickHandler(self.page_number + 1);
				}
			});

			this.last = $('<a />', {
				id: id + "_last",
				'class': "last pointer",
				text: "    ",
				target: '_blank',
  				click: function() {
					self.clickHandler(self.pages);
				}
			});

			this.element.append(self.first);
			this.element.append(self.prev);
			this.element.append(self.message);
			this.element.append(self.next);
			this.element.append(self.last);
		},

		/**
		 * the host has been populated.
		 * Update the control with the newest page counts
		 *
		 * @param {array} data
		 */
		hostUpdated: function(data) {
			var self = this;

			// number of pages starts at zero, not one
			this.pages = Math.ceil(data['count'] / self.options.results_limit) - 1;
			if (this.page_number < 0 || this.page_number > this.pages) {
				this.page_number = 0;
			}
			this.updateDisplay(self.page_number);

			// only display the pager if there are multiple pages
			if (this.pages <= 0) {
				this.element.hide();
			} else {
				this.element.show();
			}
		},

		/**
		 * Handle all click events on the pager.
		 * updates display and triggers the reloading of the results
		 *
		 * @param {int} page (what page to go to)
		 */
		clickHandler: function (page) {
			// can't page beyond the boundries of the result set
			if (this.page_number == page || page < 0 || page > this.pages) {
				return;
			}

			if (this.element.hasClass('pager_bottom')) {
				var top = $j(this.options.selectorScrollToTop).position().top;
				$j('html, body').animate({ scrollTop: top }, 'slow');
			}

			// Causes this and any other pager controls on the page to update
			// their display & keep page_number consistent across pager controls
			$(document).trigger('HotelListingControl:pagerUpdated', page);

			if (!this.isReset) {
				$(this).trigger('HotelListingControl:update', this);
			}
		},

		/**
		 * Update the display text of the control with the newest
		 * data (page number)
		 *
		 * @param {int} page (page number you are on)
		 */
		updateDisplay: function(page) {
			this.page_number = page;
			this.message.text($.tmpl(this.options.message, { low: this.page_number + 1, high: this.pages + 1 }));
		},

		/**
		 * get the object represtations of the control's data
		 *
		 * @return {array}
		 */
		getParams: function () {
			return {
				limit: this.options.results_limit,
				offset: this.page_number * this.options.results_limit
			};
		},

		/**
		 * Reset the control
		 * bring it back to the default position/value
		 */
		reset: function() {
			this.isReset = true;
			this.clickHandler(0);
			this.isReset = false;
		},

		/**
		 * Determine if any filtering is turned on for the control
		 */
		isFilteringActive: function() {
			// pager doesn't filter overall results
			return false;
		}
	});

	/**
	 * HotelListingControl_textbox Class Definition
	 *
	 * Handles the specific textbox inputs with an apply link
	 */
	var HotelListingControl_textbox = HotelListingControl.subClass({
		init: function (element, options) {
			var self = this;
			this._super(element, options);
			this.textbox = this.element.find('input:text');
			this.location_suggest = (function(obj) {
				return obj.hasClass('locationSuggest');
			})(this.textbox);

			if (this.options.apply_object) {
				this.apply_object = $(this.options.apply_object);
			} else {
				this.apply_object = this.element.find('.apply');
			}

			this.apply_object.bind(this.options.apply_trigger, function() {


				// location suggest text box will attempt to locate the
				// new city's TA id and redirect the user to that page
				if (self.location_suggest) {
					var params = {
						location: self.textbox.val()
					};

					// START: BBS-2026
					// hijack the callback function on MVC based pages cause it works diff
					if (self.options.bbs2026) {
						$.ajax({
								url: 'hotel_specific',
								data: {
									action: 'getTAIdFromName',
									name: self.textbox.val()
								},
								success: function(e) {
									var results = JSON.parse(e);
									if (results.success) {
										if (results.id) {
												// we need to construct the new URL
												var qString = BookingBuddy.queryString;
												qString['cityId'] = results.id;
												delete(qString['tid']);
												$(location).attr('href', '/hotel_specific.php?' + $.toQueryString(qString));
										}
									}
								}
						});
						return;
					}
					// END: BBS-2026

					$.bbget('HotelListing.getTAIdFromCityName', params, function (results) {
						if (results) {
							$(location).attr('href', '/hotel_listings.php?tid=' + results);
						}
					});
				} else {
					$(self).trigger('HotelListingControl:update', self);
				}
			});
		},

		/**
		 * Update the value of the locationSuggest textbox on update
		 */
		hostUpdated: function(data) {
			if (this.location_suggest && 'city_display_text' in data) {
				this.textbox.val(data.city_display_text);
			}
		},

		/**
		 * Associated arrray of default input variables
		 */
		defaults: {
			/**
			 * Should the Hotel Listing object use the MVC system.
			 * BBS-2026: this var is only useful for the bbs-2026
			 *   AB Test.
			 * Default: false
			 *
			 * @var {bool}
			 */
			bbs2026: false,

			/**
			 * What is the default value of the checkbox?
			 * (i.e. what should is say when reset?)
			 *
			 * @var {string}
			 */
			default_value: '',

			/**
			 * Field to pass this as when passed to the
			 * ajax service.
			 *
			 * @var {string}
			 */
			locs_field: '',

			/**
			 * Event to trigger the update of the hotel
			 * listings.
			 *
			 * @var string
			 */
			apply_trigger: 'click',
			apply_object: null
		},

		/**
		 * get the object represtations of the control's data
		 *
		 * @return {array}
		 */
		getParams: function () {
			var result = {filter: {}};
			result.filter[this.options.locs_field] = this.textbox.val();
			return result;
		},

		/**
		 * Reset the control
		 * bring it back to the default position/value
		 */
		reset: function() {
			// START: BBS-2026
			if (this.options.bbs2026) {
				return;
			}
			// END: BBS-2026
			this.textbox.val(this.options.default_value);
		},

		/**
		 * Determine if any filtering is turned on for the control
		 */
		isFilteringActive: function() {
			return this.textbox.val() != this.options.default_value;
		}
	});

	/**
	 * HotelListingControl_amenities Class Definition
	 *
	 * Subclass of the HotelListingControl class
	 * Handles the specific needs for the amenities checkbox list
	 * control when used in the hotel listing filter
	 */
	var HotelListingControl_amenities = HotelListingControl.subClass({
		init: function(element, options) {
			this._super(element, options);
			var self = this;
			this.popup = DHTMLPopup_Factory.create({type: 'base', id: this.options.popup_id});
			this.popup_div = $('#' + this.options.popup_id);
			this.launcher = $('#' + this.options.launcher_id);

			// clicking on anything in the launcher div will just open the dhtml layer
			this.launcher.children().click(function(event) {
				event.stopPropagation();
				event.preventDefault();
				self.popup.show(self.options.launcher_id);
			});

			// map the clicking of the apply button
			$('.apply', this.popup_div).click(function() {
				self.popup.hide();
				self.updateLauncher();
				$(self).trigger('HotelListingControl:update', self);
			});

			this.reset();
		},

		/**
		 * Associated arrray of default input variables
		 */
		defaults: {
			launcher_id: 'amenities_launcher',
			popup_id: 'amenities_popup',
			locs_field: 'amenities'
		},

		/**
		 * Function called after the HotelListing object gets data back
		 * from the ajax call.  Use this to update the control after an
		 * update if necessary. (i.e. update pager etc)
		 *
		 * @param {array} data
		 */
		hostUpdated: function(data) {
			if (data && 'amenities' in data) {
				var self = this,
					key = null,
					counter = null,
					amenities = data['amenities'];

				self.element.find('input:checkbox').each(function () {
					key = $(this).nextAll('span.value').text();
					counter = $(this).nextAll('span.count');
					if (key in amenities) {
						counter.text(' (' + amenities[key] + ')');
						this.disabled = false;
					} else {
						counter.text(' (0)');
						if (!this.checked) {
							this.disabled = true;
						}
					}
				});
			}
		},

		/**
		 * Update the launcher checkboxes with what has been selected
		 * on the popup.
		 */
		updateLauncher: function() {
			this.popup_div.find('li input:checkbox').each(function() {
				$('#' + this.id.replace('_', '_launcher_')).attr('checked', $(this).is(':checked'));
			});
		},

		getCheckedValues: function() {
			var results = [];
			$('input:checked', this.popup_div).each(function() {
				results.push($(this).nextAll('span.value').text());
			});
			return results;
		},

		/**
		 * get the json represtations of the control's data
		 *
		 * @return {array}
		 */
		getParams: function () {
			var result = {filter:{}};
			result.filter[this.options.locs_field] = this.getCheckedValues();
			return result;
		},

		/**
		 * Reset the control
		 * bring it back to the default position/value
		 */
		reset: function() {
			this.launcher.find('input:checked').attr('checked', false);
			this.popup_div.find('input:checked').attr('checked', false);
		},

		/**
		 * Determine if any filtering is turned on for the control
		 */
		isFilteringActive: function() {
			return this.getCheckedValues().length > 0;
		}
	});

	/**
	 * HotelListingControl_checkbox Class Definition
	 *
	 * Subclass of the HotelListingControl class for general checkbox list filters
	 */
	var HotelListingControl_checkbox = HotelListingControl.subClass({
		init: function(element, options) {
			this._super(element, options);
			this.checkboxes = this.element.find('input:checkbox');
			this.nondefault_checkboxes = this.element.find('input:checkbox.nondefault');
			this.initial_load = true;
			var self = this;

			// Checking any of the checkboxes will cause an update to fire
			$('.apply', this.element).click(function() {
				// Checking any of the non-'All' checkboxes will cause the 'All'
				// checkbox to uncheck
				// Checking the 'All' checkbox will cause any of the non-'All'
				// checkboxes to uncheck
				// Need to do this before any of the filter settings are grabbed
				if ($(this).is(':checked')) {
					if ($(this).hasClass('default')) {
						self.nondefault_checkboxes.attr('checked', false);
					} else {
						$('input.default', self.element).attr('checked', false);
					}
				}

				$(self).trigger('HotelListingControl:update', self);
			});
/*
			if (this.options.collapsable) {
				this.initCollapsable();
			}
*/
			this.reset();
		},

		/**
		 * Associated arrray of default input variables
		 */
		defaults: {
			locs_field: 'star_rating',
			collapsable: false,
			showMoreText: "Show more &#187;",
			showLessText: "Show less &#171;",
			collapseMoreClass: 'bbs2026More',
			collapseLessClass: 'bbs2026Less',
			startCollapsed: false,
			minCollapsed: 10
		},

		getHeights: function(max) {
			var height = 0;
			var $items = this.element.find('ul li');
			// calculate the height of the 1st N objects
			for (var i = max; i--;) {
				if ($items[i]) {
					height += $items.eq(i).outerHeight(true);
				}
			}
			return height;
		},

		updateHeights: function() {

		},

		initCollapsable: function() {
			// Add the show more/less clicky
			var linkText = this.options.startCollapsed ? this.options.showMoreText :
				this.options.showLessText;
			this.collapseLink = $('<div />', {
				'class': 'show_more_less pointer',
				'html': linkText
			});
			this.element.after(this.collapseLink);

			// calculate the min height of the div
			this.minHeight = (function(that) {
				return that.getHeights(that.options.minCollapsed);
			}(this));
			this.maxHeight = (function(that) {
				return that.getHeights(that.checkboxes.length);
			}(this));

			if (this.options.startCollapsed) {
				this.element.height(this.minHeight + 'px');
				this.element.addClass(this.options.collapseMoreClass);
			}

			this.collapseLink.click($.proxy(this.collapseClickHandler, this));
		},

		collapseClickHandler: function() {
			var $this = this.collapseLink;
			if (this.element.hasClass(this.options.collapseMoreClass)) {
				this.element.animate({ height: this.maxHeight + 'px' }, 500);
				this.element.removeClass(this.options.collapseMoreClass)
					.addClass(this.options.collapseLessClass);
				$this.html(this.options.showLessText);
			} else {
				this.element.animate({ height: this.minHeight + 'px' }, 500);
				this.element.removeClass(this.options.collapseLessClass)
					.addClass(this.options.collapseMoreClass);
				$this.html(this.options.showMoreText);
			}
		},

		/**
		 * Function called after the HotelListing object gets data back
		 * from the ajax call.  Use this to update the control after an
		 * update if necessary. (i.e. update pager etc)
		 *
		 * @param {array} data
		 */
		hostUpdated: function(data) {
			if (data && this.options.locs_field in data) {
				var self = this,
					key = null,
					$counter = null,
					counts = data[this.options.locs_field],
					max = this.checkboxes.length,
					$chbx = null;

				for (var i = max; i--;) {
					$chbx = $(this.checkboxes[i]);
					key = $chbx.nextAll('span.value').text();
					$counter = $chbx.nextAll('span.count');
					if (key in counts) {
						$counter.text(' (' + counts[key] + ')');
						$chbx.attr('disabled', false);
					} else {
						$counter.text(' (0)');
						if (!$chbx.is(':checked')) {
							$chbx.attr('disabled', true);
						}
					}
				}

				// This may seem like an unlikely spot to initialize
				// the collapsable functionality but it needs to be
				// initialized after the first loading of count data.
				// Adding the count data to the checkboxes adds length
				// to the text and can possibly change the height of
				// the li object.  Thus the min/max heights need to
				// be calculated after the inital load of data.
				if (this.options.collapsable && this.initial_load) {
					this.initCollapsable();
				}
			}
			this.initial_load = false;
		},

		getCheckedValues: function() {
			var results = [];
			$('input:checked', this.element).each(function() {
				results.push($(this).nextAll('span.value').text());
			});
			return results;
		},

		/**
		 * get the json represtations of the control's data
		 *
		 * @return {array}
		 */
		getParams: function () {
			var result = {filter:{}};
			result.filter[this.options.locs_field] = this.getCheckedValues();
			return result;
		},

		/**
		 * Reset the control
		 * bring it back to the default position/value
		 */
		reset: function() {
			this.element.find('input:checked').attr('checked', false);
			$('input.default', this.element).attr('checked', true);
		},

		/**
		 * Determine if any filtering is turned on for the control
		 */
		isFilteringActive: function() {
			var checkedValues = this.getCheckedValues();
			// this assumes the value for the defalt is 'All'
			return checkedValues.length != 1 || checkedValues[0] != 'All';
		}
	});

	/**
	 * HotelListingControl_favorites Class Definition
	 *
	 * Subclass of the HotelListingControl class
	 * Responsible for the favorite input
	 */
	var HotelListingControl_favorites = HotelListingControl.subClass({
		init: function(element, options) {
			this._super(element, options);
			var self = this;
			this.checkbox = this.element.find('input:checkbox');
			this.counter = $('span.fav_count', this.element);
			this.cookie_name = '';
			this.city_id = null;

			this.updateControl();

			this.checkbox.change(function() {
				$(self).trigger('HotelListingControl:update', self);
			});

			$('.hotelListingFavorites').live('click', function() {
				self.favoriteClickHandler(this);
			});

			this.reset();
		},

		/**
		 * Associated arrray of default input variables
		 */
		defaults: {
			/**
			 * Text in the favorites link when the property
			 * is marked as a favorite
			 *
			 * @var {string}
			 */
			message_active: 'Favorite',

			/**
			 * Text in the favorites link when the property
			 * is not marked as a favorite
			 *
			 * @var {string}
			 */
			message_inactive: 'Mark as Favorite',

			/**
			 * prefix for the favorites cookie
			 *
			 * @var {string}
			 */
			cookie_prefix: 'hotel_listing_',

			/**
			 * What should the key be for the value passed
			 * to the AJAX service
			 *
			 * @var {string}
			 */
			locs_field: 'favorites'
		},

		/**
		 * Update the favorites control based on
		 * the current state of the favorites cookie
		 */
		updateControl: function () {
			var classes = 'active enabled disabled';
			this.element.removeClass(classes);
			// there are no favorites for this city
			var num = this.getFavorites().length;
			if (num === 0) {
				if (this.checkbox.is(':checked')) {
					this.reset();
				} else {
					this.element.addClass('disabled');
				}
			} else {
				if (this.checkbox.is(':checked')) {
					this.element.addClass('active');
				} else {
					this.element.addClass('enabled');
				}
			}
			this.checkbox.attr('disabled', num === 0);
			this.counter.text("(" + num + ")");

			if (num === 0 && this.checkbox.is(':checked')) {
				this.reset();
			}
		},

		/**
		 * Manages all click events on Hotel Listing Favorites
		 * (The links in the results.)
		 *
		 * @param {HTMLElement} object calling the click event
		 */
		favoriteClickHandler: function (object) {
			var $favbox = $(object),
				self = this,
				$star = $('div.favorite_img', $favbox),
				$message = $('div.favorite_message', $favbox),
				hotel_id = $('div.hotel_id', $favbox).text(),
				cookie = BookingBuddy.getCookie(this.cookie_name),
				ids = [];

			if ($favbox.hasClass('active')) {
				if (cookie) {
					ids = cookie.split(',');
					ids.splice($.inArray(hotel_id, ids), 1);
				}
				BookingBuddy.createCookie(this.cookie_name, ids.join(','));
				$message.text(self.options.message_inactive);
				$favbox.removeClass('active');
			} else {
				if (cookie) {
					ids = cookie.split(',');
				}

				if ($.inArray(hotel_id, ids) == -1) {
					ids.push(hotel_id);
					BookingBuddy.createCookie(this.cookie_name, ids.join(','));
				}

				$message.text(self.options.message_active);
				$favbox.addClass('active');
			}
			this.updateControl();
		},

		/**
		 * Function run when the HotelListing has been updated
		 *
		 * @param {array} data
		 */
		hostUpdated: function(data) {
		  // update the city our favorites are in based on what the user is searching for
			if (data && 'city_ta_id' in data) {
				this.city_id = data['city_ta_id'];
				this.cookie_name = this.options.cookie_prefix + this.city_id;
			}
			this.updateControl();
		},

		/**
		 * Get a list of favorites in the current city
		 * array of ta id's
		 *
		 * @return {array}
		 */
		getFavorites: function () {
			var results = [];
			if (this.city_id) {
				favs = BookingBuddy.getCookie(this.cookie_name);
				results = favs ? favs.split(',') : [];
			}
			return results;
		},

		/**
		 * Does the user have any favorites, since this can change after
		 * initializiation an immediate function wrapper isn't used.
		 *
		 * @return boolean
		 */
		hasFavorites: function() {
		  return this.getFavorites().length > 0;
		},

		/**
		 * get the object represtations of the control's data
		 *
		 * @return {array}
		 */
		getParams: function () {
			var result = {};
			result[this.options.locs_field] = [this.checkbox.is(':checked'),this.options.cookie_prefix];
			return result;
		},

		/**
		 * Reset the control
		 * bring it back to the default position/value
		 */
		reset: function() {
			this.checkbox.attr('checked', false);
		},

		/**
		 * Determine if any filtering is turned on for the control
		 */
		isFilteringActive: function() {
			return this.hasFavorites();
		}
	});

	/**
	 * HotelListingControl_slider class definition
	 *
	 * HotelListingControl SubClass
	 */
	var HotelListingControl_slider = HotelListingControl.subClass({
		init: function (element, options) {
			this._super(element, options);
			this.isReset = false;
			var self = this,
				value = null;

			// Used by BBUK to determine whether to show prices in the hotel results
			this.prices_active = false;

			/**
			 * imediate function: which means that this comparison will only be run once.
			 * faster than just using a normal function :)
			 */
			this.is_range = (function(obj) {
				return obj.options.slider_options.range === true;
			})(this);

			/**
			 * Immediate Function
			 * build the default value for the slider once at startup
			 */
			this.default_value = (function(obj) {
				var items = [];
				items.push(obj.options.slider_options.min);
				if(obj.is_range) {
					items.push(obj.options.slider_options.max);
				}
				return items;
			})(this);

			this.currentValues = this.default_value;

			this.message_container = this.options.message ? $('<div />', { 'class': this.options.message_class }) : null;
			this.image_container = this.options.has_image ? $('<span />', { 'class': this.options.image_class }) : null;
			this.slider = $('<div />', { 'class': 'slider' }).slider(this.options.slider_options);

			this.element.append(this.image_container);
			this.element.append(this.slider);
			this.element.append(this.message_container);

			// hook into the slide event and have the star image and text
			// change as you slide the slider :)
			this.reset();
			this.slider.slider("option", "slide", function(event, ui) {
				self.slide(ui.values || [ui.value]);
			});

			this.slider.slider("option", "change", function(event, ui) {
				if (self.options.locs_field == 'avg_price' && !self.isReset) {
					self.prices_active = true;
				}

				// sliders with ranges will set ui.values; single sliders will set ui.value
				var newValues = ui.values || [ui.value],
					valueChanged = false;

				// verify the value in the slider has changed before triggering an update
				for (i in newValues) {
					valueChanged = valueChanged || newValues[i] != self.currentValues[i];
				}

				self.currentValues = newValues;

				// trigger the results thing to get updated...
				if (valueChanged && !self.isReset) {
					$(self).trigger('HotelListingControl:update', self);
				}
			});
		},

		/**
		 * Associated arrray of default input variables
		 */
		defaults: {
			/**
			 * Message that appears below the slider
			 * {low} : position of 1st slider
			 * {high} : position of 2nd slider
			 *
			 * @var {string}
			 */
			message: "#{low} stars & above",

			/**
			 * Message for when the value is low value is 0 and there isn't a high value.
			 *
			 * @var {string}
			 */
			message_all: 'All',

			/**
			 * Class name for the message
			 *
			 * @var {string}
			 */
			message_class: 'slider_message',

			/**
			 * Does this slider have an image that needs
			 * to be updated?
			 *
			 * @var {bool}
			 */
			has_image: true,

			/**
			 * Class name for the image
			 *
			 * @var {string}
			 */
			image_class: '',

			/**
			 * Class name prefix for images
			 *
			 * @var {string}
			 */
			image_prefix: 's',

			/**
			 * Field to pass to the ajax service
			 * when getting the results
			 *
			 * @var {string}
			 */
			locs_field: 'star_rating',

			/**
			 * Options for the jquery slider control
			 *
			 * @var {array}
			 */
			slider_options: {
				min: 0,
				max: 5,
				step: 0.5,
				range: "max"
			}
		},

		/**
		 * Transforms the value of the slider into something that the
		 * jquery .tmpl method can use.
		 *
		 * @param {array} items
		 */
		templatize_value: function(items) {
			var value = {};
			if (items[0] !== undefined) {
				value.low = items[0];
			}
			if(items[1] !== undefined) {
				value.high = items[1];
			}
			return value;
		},

		/**
		 * Update the slider control's images and message
		 * with the inputed values
		 *
		 * @param {array} value
		 */
		slide: function(value) {
			this.value = value;
			var data = this.templatize_value(value);
			if (this.image_container) {
				// remove all other star image class names
				this.image_container[0].className = this.image_container[0].className.replace(/ s\d{1,2}/g, '');
				// add the appropriate class name to the image container

				var percentage = data.low * 10;
				if(percentage < 10) { percentage = '0' + percentage; }
				this.image_container.addClass(this.options.image_prefix + percentage);
			}

			if (this.message_container) {
				this.message_container.text(
					(data.low !== undefined) && (data.high === undefined) && (data.low === 0) ?
					this.options.message_all : $.tmpl(this.options.message, data)
				);
			}
		},

		/**
		 * get the json represtations of the control's data
		 * @return {array}
		 */
		getParams: function () {
			var result = {filter: {}};
			result.filter[this.options.locs_field] = this.value;
			if (this.prices_active) {
				result.filter['prices_active'] = this.prices_active;
			}
			return result;
		},

		/**
		 * Reset the control
		 * bring it back to the default position/value
		 */
		reset: function() {
			this.isReset = true;
			this.slider.slider(this.is_range ? "values" : "value", this.default_value);
			this.slide(this.default_value);
			this.isReset = false;
		},

		/**
		 * Determine if any filtering is turned on for the control
		 */
		isFilteringActive: function() {
			return this.value != this.default_value;

		}
	});

	/**
	 * Factory Class
	 *
	 * builds the correct type of control based on the inputs.
	 */
	var HotelListingControl_Factory = {
		types: {
			slider: HotelListingControl_slider,
			amenities: HotelListingControl_amenities,
			favorites: HotelListingControl_favorites,
			textbox: HotelListingControl_textbox,
			sorter: HotelListingControl_sorter,
			pager: HotelListingControl_pager,
			checkbox: HotelListingControl_checkbox
		},

		create: function (element, options, type) {
			var item = null;
			if (type && type in this.types) {
				var class_name = this.types[type];
				item = new class_name(element, options);
			}

			return item;
		}

	};

	/**
	 * Plugin setup
	 */
	$.fn.hotelListing = function (options) {
		return this.each(function () {
			var element = $(this);

			// Return early if this element already has a plugin instance
			if (element.data('hotelListing')) {
				return;
			}

			// Store plugin object in this element's data
			var hotelListing = new HotelListing(this, options);

			element.data('hotelListing', hotelListing);
		});
	};
})(jQuery);

/**
 * Class to handle the fetching and displaying of hotel thumbnail images.
 *
 * === Usage ===
 *
 * These 3 HTML elements are required for each hotel. The "default_hotel_img"
 * div will be replaced with the hotel's thumbnail image if one is found.
 *
 * <div class="hotel_img">
 * 		<span class="hotel_id">12345</span>
 * 		<div class="default_hotel_img"></div>
 * </div>
 *
 * In the JS:
 *
 * var hotelImageLoader = new BBHotelImages();
 * hotelImageLoader.loadHotelImages();
 *
 */
var BBHotelImages = Object.subClass({
	/**
	 * Array of TA hotel IDs to fetch thumbnails for
	 */
	hotelIDs: [],

	/**
	 * Array of jquery span objects corresponding to the above IDs
	 */
	hotelIDElms: [],

	/**
	 * The image size to use from the AJAX results
	 */
	imgSize: 'thumbnail',

	/**
	 * Gather up the hotel IDs & elements
	 */
	init: function() {
		var $hotels = $j('div.hotel_img > span.hotel_id:not(.loaded)'),
			self = this;

		// If there are no hotel images to load, return
		if ($hotels.length === 0) {
			return this;
		}

		// reset the contents of the lists so we only request current hotels
		self.hotelIDs = [];
		self.hotelIDElms = [];

		$hotels.each(function(index, element) {
			self.hotelIDs.push($j(element).text());
			self.hotelIDElms.push($j(element));
		});

		return this;
	},

	/**
	 * Makes the AJAX request to fetch the thumbnail images, and
	 * adds any that are found to the correct place on the page
	 */
	loadHotelImages: function() {
		var hotelIDs = this.hotelIDs,
			hotelIDElms = this.hotelIDElms,
			self = this;

		if (hotelIDs.length === 0 || hotelIDElms.length === 0) {
			return this;
		}

		var params = {
			// The HotelListing AJAX request requires the location parameter,
			// however it isn't needed for this call
			location: '',
			hotel_ids: hotelIDs,
			thumbsize: 'large'	// Using the large version gives us more of a selection
		};

		$j.bbget('HotelListing.fetchHotelImages', params, function(results) {
			if (results) {
				var $span,
					$container_div,
					$default_img_div,
					$img,
					$link,
					found = false,
					ta_id = null,
					length = hotelIDs.length,
					img_size = self.imgSize,
					is_bbuk = BookingBuddy.affiliateName === 'bookingbuddy_co_uk',
					is_featured_property = false;

				// Decrementing the counter to zero is much faster than incrementing it
				for (var i = length; i--;) {
					ta_id = hotelIDs[i];
					$span = hotelIDElms[i];
					$container_div = $span.parent();
					$default_img_div = $span.siblings('.default_hotel_img').first();

					// If there's no default image to replace, just continue
					if ($default_img_div.length === 0) {
						continue;
					}

					// The image size to use is generally what is specified as the option above,
					// however, an image can override it with the image-size data attribute
					var imageSizeToUse = $container_div.attr('data-image-size');
					if (!imageSizeToUse) {
						imageSizeToUse = img_size;
					}

					// Make sure we actually got an image
					found = results[ta_id] && results[ta_id]['pictures'] && results[ta_id]['pictures'][imageSizeToUse];
					if (found) {
						$img = $j('<img />', {
							'class': 'hotel_img hotel_photo',
							'src': results[ta_id]['pictures'][imageSizeToUse]
						});

						if (results[ta_id]['pictures']['thumbnail_link']) {
							$link = $j('<a />', {
								'class': 'HotelAIMLink',
								'target': '_blank',
								'href': results[ta_id]['pictures']['thumbnail_link']
							});
							$link.append($img);
							$default_img_div.replaceWith($link);
						} else {
							$default_img_div.replaceWith($img);
						}

						$span.addClass('loaded');
					}

					if (is_bbuk) {
						// If we are fetching the image for a featured property and none is
						// found, the lefthand column div is hidden and the TA Rating is
						// shown in the title bar. Otherwise the TA Rating is shown below
						// the hotel image.
						is_featured_property = $container_div.parent().hasClass('property_lefthand');

						if (is_featured_property) {
							if (found) {
								$container_div.parent().show();
								$j('.ta_rating_title').hide();
							} else {
								$container_div.parent().hide();
								$j('.ta_rating_title').show();
							}
						}
					}

					$span = null;
				}
			}
		});

		return this;
	}
});

(function ($){
	/**
	 * Handle creating and registring a popunder to be automatically
	 * reloaded based on changes to user inputs in a form.
	 *
	 * @param {jQuery object} element Wrapper element
	 * @param {object} options Plugin options
	 */
	function BBPopUnderAd(element, options) {

		// merge the passed in options with the default options
		this.options = $.extend({}, BBPopUnderAd.defaults, options);
		this.wrapper = $(element);

		/**
		 * The first click external popup object
		 * @var object
		 */
		this.firstClick = {};

		/**
		 * The pop on exit external popup object
		 * @var object
		 */
		this.popOnExit = {};

		/**
		 * Data on the possible BB ad being loaded
		 * @var object
		 */
		this.adData = {};

		/**
		 * Has a direct sale ad been loaded in the popunder?
		 * @var bool
		 */
		this.directSaleAdLoaded = false;

		/**
		 * Does this popunder need an explicit call to trigger omniture tracking?
		 * @var bool
		 */
		this.needsManualAdTracking = $.browser.msie && ($.browser.version == "7.0" || $.browser.version == "8.0");

		/**
		 * The base URL for loading in the popunder
		 * @var string
		 */
		this.popUnderURL = this.options.popUnderURL || this.options.defaultPopUnderURL;

		this.init();
  	}

	/**
	 * Actions the popunder can take for an AJAX request
	 */
	BBPopUnderAd.ACTIONS = {
		BUILD_REDIRECT: 'A1',
		LOAD_DEFAULT: 'A2'
	};

	/**
	 * Actions the popunder can take for an AJAX request
	 */
	BBPopUnderAd.EVENTS = {
		INITIALIZED: 'BBPopUnderAd::initialized',
		URL_CHANGED: 'BBPopUnderAd::urlChanged'
	};

	/**
	 * Mapping of search modes to load in the popunder (value) based on the
	 * search mode users are currently viewing (key). E.g. {'air': 'hotel'}
	 * means for a flight search, load a hotel ad in the popunder.
	 */
	BBPopUnderAd.SEARCH_MODE_MAPPING = {
		'air': 'hotel',
		'hotel': 'car',
		'car': '' /* SUPPLY A BLANK TO PROMPT DEFAULT POPUNDER FOR CAR!!! */
	};

	/**
	 * Default input options for the HotelListing object
	 */
	BBPopUnderAd.defaults = {

		/**
		 * Form jquery selectors for forms and inputs that should trigger popunder
		 * contents to reload.
		 */
		triggers: {

			forms: [
				// step1 form
				'#bb_widget form',
				// google lander form
				'#step_1_widget form',
				// tab searches form
				'#dhtml_edit_details form'
			],

			/**
			 * BLUR AND CHANGE EVENTS PURPOSELY DISABLED BUT RETAINED
			 * jQuery selectors for the form input fields that will trigger a blur
			 * event we want to listen for.
			 *
			blurElements: [
				'input:[id*=arrival_city]',
				'input:[id*=departure_city]'
			],

			**
			 * jQuery selectors for the form input fields that will trigger a change
			 * event we want to listen for.
			 *
			changeElements: [
				'input:[id*=date1]',
				'input:[id*=date2]',
				'select:[id*=cruise_date]',
				'select#cruise_destination'
			],

			**
			 * jQuery selectors for the search buttons that will trigger a change
			 * event we want to listen for.
			 * 1st selector hits non-"cheap" pages, 2nd covers "cheap" pages
			 */
			clickElements: [
				'.submitTrigger',
				'div.submit div.submit_btn input'
			]
		},

		/**
		 * The base url which the popunder should load.
		 */
		defaultPopUnderURL: '/popunder/deals_popunder.php?',

		/**
		 * Default window sizes for the popunder window.
		 */
		windowOpts: {
			large: {width: 1000, height: 1040},
			regular: {width: 560, height: 730}
		}

	};

	/**
	 * Initializes the BBPopUnderAd object.
	 */
	BBPopUnderAd.prototype.init = function() {
		var self = this;

		if (self.popUnderURL.indexOf('?') != self.popUnderURL.length - 1) {
			self.popUnderURL += '&';
		}

		var requestData = self.loadSavedSearchData(),
			dimensions = self.options.windowOpts.regular,
			url = null;

		requestData['ad_loaded'] = self.directSaleAdLoaded;
		requestData['action'] = BBPopUnderAd.ACTIONS.LOAD_DEFAULT;

		$.ajax({
			type: 'GET',
			url: self.options.defaultPopUnderURL,
			data: requestData,
			/**
			 * On failure, load the popunder with default content.
			 */
			error: function(e) {
				requestData['action'] = BBPopUnderAd.ACTIONS.LOAD_DEFAULT;
				url = self.getPopupUrl(requestData);
				self.initializePopUnders(url, dimensions);
			},
			/**
			 * On success, redirect the popunder to the url specified. If one does not
			 * exist, load the popunder with default content.
			 */
			success: function(e) {
				var data = JSON.parse(e);

				if (data['url']) {

					self.adData = data['ad'];
					url = data['url'];
					// if we're redirecting, we should be opening a larger window
					dimensions = self.options.windowOpts.large;

				} else {
					requestData['action'] = BBPopUnderAd.ACTIONS.LOAD_DEFAULT;
					url = self.getPopupUrl(requestData);
				}

				self.initializePopUnders(url, dimensions);
			}
		});
	};

	/**
	 * Create the instances of the first click and exit popunders and hook up
	 * input change listeners.
	 *
	 * @param string url The redirect url to load in the popunder
	 * @param object dimensions The dimensions of the popunder window
	 */
	BBPopUnderAd.prototype.initializePopUnders = function(url, dimensions) {
		var self = this;

		self.firstClick = new ExternalPopup({
			url: url,
			location: ExternalPopup.LOCATIONS.UNDER,
			type: ExternalPopup.TYPES.FIRSTCLICK,
			placement: ExternalPopup.PLACEMENTS.PARENT,
			cookieName: 'BBDPopunder',
			width: dimensions.width,
			height: dimensions.height,
			params: 'toolbar=1,location=1,directories=1,status=1,menubar=1,scrollbars=1,resizable=1'
		});

		self.popOnExit = new ExternalPopup({
			url: url,
			location: ExternalPopup.LOCATIONS.UNDER,
			type: ExternalPopup.TYPES.ONEXIT,
			placement: ExternalPopup.PLACEMENTS.PARENT,
			width: dimensions.width,
			height: dimensions.height,
			params: 'toolbar=1,location=1,directories=1,status=1,menubar=1,scrollbars=1,resizable=1'
		});

		// Attach on blur and on change event handlers on the form input fields
		// to auto-update the popunder window
		var triggers = self.options.triggers,
			$forms = $(triggers.forms.join(', '));

		if (triggers.clickElements && triggers.clickElements.length > 0) {
			$(triggers.clickElements.join(', '), $forms).one('click', $.proxy(self.submitSearchDataCallback, self));
		}

		if (self.adData) {
			// Create a BB ad instance for the omniture tracking that will be
			// triggered later on when the popunder gets popped.
			var adId = self.adData['id'],
				ad = BBAd.singleton(adId);
			ad.setMetaDataMulti(self.adData);
		}

		// We only want to use redirects for opening the popunder. Subsequent
		// refreshes should use the default popunder url in case a redirect was used.
		$([self.firstClick, self.popOnExit]).one('ExternalPopup:popped', function() {

			self.popUnderURL = self.options.defaultPopUnderURL;
			if (self.popUnderURL.indexOf('?') != self.popUnderURL.length - 1) {
				self.popUnderURL += '&';
			}

			if (self.adData && self.adData['id']) {

				self.directSaleAdLoaded = true;

				// Publish ad click tracking for IE 7/8.  All other browsers will do this
				// for you from the popunder in templates/pages/search_redirect.tpt.php
				if (self.needsManualAdTracking) {
					$.publish('/page/search_redirect/', [{ad_id: self.adData['id']}]);
				}

			}

			BookingBuddy.Tracking.trackPopunderPopped();

		});

		self.wrapper.trigger(BBPopUnderAd.EVENTS.INITIALIZED, [self]);
	};

	/**
	 * Callback event handler for events that should trigger an ad load in the popunder.
     * Update the popunder url based on the forms fields and perform basic validation to
	 * determine its likely desired size.
	 *
	 * @param {object} The event object
	 */
	BBPopUnderAd.prototype.submitSearchDataCallback = function(event) {
		var self = this;

		var requestData = self.getFormDataFromElement($(event.target));
		if (!requestData) {
			return;
		}

		requestData['ad_loaded'] = self.directSaleAdLoaded;
		requestData['action'] = BBPopUnderAd.ACTIONS.BUILD_REDIRECT;

		// Need 'async: false' to ensure Chrome executes error/success condition before yielding to page submit
		$.ajax({
			type: 'GET',
			async: false,
			url: self.options.defaultPopUnderURL,
			data: requestData,
			/**
			 * On failure, load the popunder with default content.
			 */
			error: function(e) {
				var dimensions = self.options.windowOpts.regular;
				requestData['action'] = BBPopUnderAd.ACTIONS.LOAD_DEFAULT;
				$([self.firstClick, self.popOnExit]).trigger('ExternalPopup:setUrl', {url: self.getPopupUrl(requestData)});

				$([self.firstClick, self.popOnExit]).trigger('ExternalPopup:resizeWindow', dimensions);
			},
			/**
			 * On success, check if we have an ad id associated with the redirect and if
			 * so, track an ad click in omniture. Also redirect the popunder to the url
			 * specified. If one does not exist, load the popunder with default content.
			 */
			success: function(e) {
				var data = JSON.parse(e),
					dimensions = self.options.windowOpts.regular;
				if (data && data['url']) {

					if (data['ad'] && data['ad']['id']) {
						var oldAdId = (self.adData && self.adData['id']) ? self.adData['id'] : 0;
						self.adData = data['ad'];

						var adId = self.adData['id'];
						// Create a BB ad instance for the omniture tracking that will be
						// triggered from the redirect page.
						var ad = BBAd.singleton(adId);
						ad.setMetaDataMulti(self.adData);

						self.directSaleAdLoaded = true;

						// Publish ad click tracking for IE 7/8.  Other browsers will do this
						// for you from the popunder in templates/pages/search_redirect.tpt.php
						// Also, ensure the first click window actually popped before tracking,
						// and make sure we're doing this only if the incoming ad isn't the same as the outgoing ad
						if (self.needsManualAdTracking && (oldAdId != adId) && self.firstClick.win) {
							$.publish('/page/search_redirect/', [{ad_id: adId}]);
						}
					}

					dimensions = self.options.windowOpts.large;
					$([self.firstClick, self.popOnExit]).trigger('ExternalPopup:setUrl', {url: data['url']});

				} else {
					requestData['action'] = BBPopUnderAd.ACTIONS.LOAD_DEFAULT;
					$([self.firstClick, self.popOnExit]).trigger('ExternalPopup:setUrl', {url: self.getPopupUrl(requestData)});
				}

				$([self.firstClick, self.popOnExit]).trigger('ExternalPopup:resizeWindow', dimensions);
			},
			/**
			 * On success or error, notify the popunder url has been updated
			 */
			complete: function(e) {
				self.wrapper.trigger(BBPopUnderAd.EVENTS.URL_CHANGED, [self]);
			}
		});
	};

	/**
	 * Pull form data for the popunder url from BB Saved Search Data
	 *
	 * @return {object}
	 */
	BBPopUnderAd.prototype.loadSavedSearchData = function() {
		var self = this,
			searchMode = BookingBuddy.searchMode === 'home' ? 'air' : BookingBuddy.searchMode;
			searchMode = self.mapSearchMode(searchMode);

		if (searchMode !== '') {
			var savedData = BBSavedSearchData.singleton().getCurrentState().getDataByMode(searchMode),
				formData = {
					mode: searchMode
				};

			if (savedData.c1) {
				formData.from_city = savedData.c1;
			}
			if (savedData.c2) {
				formData.to_city = savedData.c2;
			}
			if (savedData.d1) {
				formData.from_date = self.formatDate(savedData.d1);
			}
			if (savedData.d2) {
				formData.to_date = self.formatDate(savedData.d2);
			}
			if (savedData.c3) {
				formData.destination = savedData.c3;
			}
			if (savedData.d3) {
				formData.travel_month = savedData.d3;
			}
			if (savedData.o55) {
				formData.over_55 = savedData.o55;
			}
			if (savedData.clen) {
				formData.cruise_length = savedData.clen;
			}
			if (savedData.clin) {
				formData.cruise_line = savedData.clin;
			}
		} else {
			formData = {};
		}
		return formData;
	};

	/**
	 * Pull out location and date information from the given search form
	 * and format the fields to be recognized by the popunder window
	 *
	 * @param {jQuery object} $form
	 * @return {object}
	 */
	BBPopUnderAd.prototype.getFormDataFromElement = function($element) {
		var self = this;

		if (!$element[0]) {
			return null;
		}

		// Form data is pulled from the smartForm data attribute stored on the
		// given element. We're using smartForm, since it's the easiest way to
		// retrieve input fields across all search modes.
		var smartForm  = $element.parents('form').data('smartForm'),
			searchMode = self.mapSearchMode(smartForm.mode),
			formData = {};


		if (searchMode === 'cruise') {
			var destination = smartForm.elements['_destination'],
				cruiseDate = smartForm.elements['_date'],
				cruiseLine = smartForm.elements['_line'],
				tripLength = smartForm.elements['_trip_length'],
				over55 = smartForm.elements['_over_55'];
			formData = {
				destination: destination ? $(destination.currentElement).val() : '',
				travel_month: cruiseDate ? $(cruiseDate.currentElement).val() : '',
				cruise_line: cruiseLine ? $(cruiseLine.currentElement).val() : '',
				cruise_length: tripLength ? $(tripLength.currentElement).val() : '',
				over55: over55 ? $(over55.currentElement).val() : '',
				mode: searchMode
				};
		} else {
			var fromCity   = smartForm.elements['_departure_city'],
				fromDate   = smartForm.elements['_date1'],
				toCity     = smartForm.elements['_arrival_city'],
				toDate     = smartForm.elements['_date2'];
			formData = {
				from_city: fromCity ? $(fromCity.currentElement).val() : '',
				from_date: fromDate ? $(fromDate.currentElement).val() : '',
				to_city: toCity ? $(toCity.currentElement).val() : '',
				to_date: toDate ? $(toDate.currentElement).val() : '',
				mode: searchMode
			};
		}

		return formData;
	};

	/**
	 * Determine the width and height of the popunder window based on basic
	 * validation of form data and the expected contents of the popunder.
	 *
	 * @param {object} data
	 * @return {object}
	 */
	BBPopUnderAd.prototype.getPopupSize = function(data) {
		var dims = this.options.windowOpts.regular;

		if (typeof data != 'object') {
			return dims;
		}

		// if we have any data, we assume the larger window should be loaded
		dims = this.options.windowOpts.large;

		return dims;
	};

	/**
	 * Given form data, generate the popunder url
	 *
	 * @param {object} data
	 * @return {string}
	 */
	BBPopUnderAd.prototype.getPopupUrl = function(data) {
		if (typeof data != 'object') {
			data = {};
		}

		// TODO: BB dependency
		return this.popUnderURL + $.toQueryString(data);
	};

	/**
	 * Format a timestamp into the mm/dd/yyyy string format. Note this is only set
	 * up to work on BBUS. If BBUK ever uses this, locale needs to be taken into
	 * account. Or get a js library to handle date formatting.
	 *
	 * @param {int} timestamp The timestamp to convert
	 * @return {string}
	 */
	BBPopUnderAd.prototype.formatDate = function(timestamp) {
		var date = new Date(timestamp),
			day = date.getDate(),
			month = date.getMonth() + 1,
			year = date.getFullYear();

		return month + '/' + day + '/' + year;
	};

	/**
	 * Map the given search mode for the current page to the search mode that
	 * should be used within the popunder. If no mapping exists, use the given
	 * search mode. This allows cross-selling hotel popunder ads on flights
	 * searches, for instance.
	 *
	 * @param {string} fromSearchMode The search mode to map
	 * @return {string}
	 */
	BBPopUnderAd.prototype.mapSearchMode = function(fromSearchMode) {
		var toSearchMode = fromSearchMode;

		if (BBPopUnderAd.SEARCH_MODE_MAPPING[fromSearchMode] !== undefined) {
			toSearchMode = BBPopUnderAd.SEARCH_MODE_MAPPING[fromSearchMode];
		}

		return toSearchMode;
	};

	/**
	 * Plugin setup
	 */
	$.fn.popUnderAd = function (options) {
		return this.each(function () {
			var element = $(this);

			// Return early if this element already has a plugin instance
			if (element.data('popUnderAd')) {
				return;
			}

			// Store plugin object in this element's data
			var popUnderAd = new BBPopUnderAd(this, options);

			element.data('popUnderAd', popUnderAd);
		});
	};
})(jQuery);



/*-------------------------------------------------------------------------------
	BookingBuddy.Deals - 
 --------------------------------------------------------------------------------*/
BookingBuddy.Deals = {

	/**
	 * publisher ids by affiliate
	 *
	 */
	pubIDS : {
		tiphero: 69,
		travelmuse_v2: 65,
		trazzler: 23,
		frommers: 68,
		nileguide: 67,
		canuckabroad: 73,
		travelation: 36
	},

	/**
	 * Array of deal objects
	 * @var array
	 */
	dealsArray: [],

	/**
	 * The AJAX service class and function name
	 * @var string
	 */
	feedUrl : null,

	/** 
	 * initialize module. This should be called after one
	 * or more requests have been registered by calling
	 * BookingBuddy.Deals.register. Each registered request
	 * will then be executed.
	 * @param config - deal request config settings 
	 */
	init: function (config) {

		$j.each(config, function(key, value) {
			BookingBuddy.Deals[key] = value;
		});

		$j.each(BookingBuddy.Deals.dealsArray, function(index, deal){
			if ($j("#"+deal.displayConfig.target_input).length > 0) {
				// loadDeals on Change
				if ($j("#"+deal.displayConfig.target_input).get(0).tagName === "select") {
					$j("#"+deal.displayConfig.target_input).change( function() {
						deal.options.arrival_city = $j(this).val();
						BookingBuddy.Deals.fetchBBDeals(deal);
						return;
					});
				} else {
					if ($j("#"+deal.displayConfig.target_input).hasClass("locationSuggest")) {
						$j("#"+deal.displayConfig.target_input).bind("LocationSuggest:valid", function() {
							deal.options.arrival_city = $j(this).val();
							BookingBuddy.Deals.fetchBBDeals(deal);
							return;
						});
					}
					else {
						$j("#"+deal.displayConfig.target_input).blur( function() {
							deal.options.arrival_city = $j(this).val();
							BookingBuddy.Deals.fetchBBDeals(deal);
							return;
						});
					}
				}
			}
			BookingBuddy.Deals.fetchBBDeals(deal);
		});

		// reset the deals array to prevent requests from being
		// made more than once
		BookingBuddy.Deals.dealsArray = [];
	},

	/**
	 * register configuration for a deal to be loaded 
	 * @param options - BB deals settings
	 * @param config - display configuration 
	 *
	 */ 
	register: function (deal_options, config, type) {
		var _config = {
				insertType: 'update',										// Defines what function to call to update the deals section (insert or update)
				divDealsHeader: 'BB-deals-header',							// Deals Header div that will contain the Location Targeted City
				headerMsg: ' Deals',										// Message to be displayed after the location name
				divDeals: 'BB-deals-section',								// Deals div
				divErrorMsg: 'BB-deals-error-msg',							// Deal Error div
				errorMsg: 'No deals available at this time.',				// Error msg to be displayed
				onError: null,												// Function to be executed if no deals are found
				onSuccess: null,											// Function to be exectued if deals are found
				dealTemplate: BookingBuddy.Strings.Deals.DefaultTemplate,	// Deal Template
				cityName: null,												// Will get  set if a location targeted deal is found
				target_input: undefined										// Input to observe for onchange or blur event
		};

		if (!$j.isNullOrUndefined(config)) {
			$j.extend(_config, config);
		}

		var _deal_options = {
			'no_ads' : '5',
			'deal_type' :  BookingBuddy.searchMode == 'vacation_rental' ? 'car' : BookingBuddy.searchMode == 'air' ? 'vacation' : BookingBuddy.searchMode
		};

		$j.extend(_deal_options, deal_options);

		// Add publisher id
		if (typeof this.pubIDS[BookingBuddy.affiliateName] !== 'undefined') {
			deal_options.publisher_id = this.pubIDS[BookingBuddy.affiliateName];
		}

		BookingBuddy.Deals.dealsArray.push({
			options: _deal_options,
			displayConfig: _config,
			type: type
		});
	},

	/**
	 * Add click tracking to the given deal
	 *
	 * @param jqueryObject $deal	The bbdn deal to add tracking to
	 * @param object dealData		Tracking metadata for the deal
	 *
	 * @return jqueryObject
	 */
	addDealTracking: function($deal, dealData) {

		$deal.click(function(event) {
			event.preventDefault();	
			$j.publish('/click/bbdeal', [dealData, this]);
		});

		return $deal;
	},


	/**
	 * Fetch bbdn deals from the AJAX service and handle displaying the results
	 * based on the display config type
	 *
	 * @param object metaData	Options and configurations for loading and displaying deals
	 */
	fetchBBDeals: function(metaData) {

		$j.bbget(BookingBuddy.Deals.feedUrl, metaData.options, function(result) {

			if ($j.isNullOrUndefined(result)) { return; }	

			if (!result.errorstr) {
				// The city name is prepended to the json output if arrival city is
				// passed in the bbdn request. So, check for its existence, store
				// and remove it before further processing.
				if (result[0].destination) {
					metaData.displayConfig.cityName = result[0].destination;
					result.splice(0,1); 
				} else {
					metaData.displayConfig.cityName = null;
				}
			}
		

			switch (metaData.type) {

				case 'targeted_deals':
					BookingBuddy.Deals.View.targetedDeals(result, metaData.displayConfig);

					break;
				case 'split_deals':
					BookingBuddy.Deals.View.splitDeals(result, metaData.displayConfig);

					break;
				case 'top_deals':
				default:
					BookingBuddy.Deals.View.topDeals(result, metaData.displayConfig);
					break;
			}
		}, 'json');
	}
};

/**
 * Handle formatting and displaying bbdn deals
 */
BookingBuddy.Deals.View = {
	/**
	 * Format deals with minimal content formating and
	 * configuration. Deals are passed directly from the 
	 * request output to the template.
	 *
	 * @param object json	The deal content in json format
	 * @param object config	Display configurations for the deals
	 */
	targetedDeals: function(json, config) {
		$targetedDiv = config.containerDivs;
		
        if (!$targetedDiv[0]) { return; }

        if (!json.errorstr) {

			// construct the deal html
			$j.each(json, function(index, deal) {

				var dealHTML = $j.tmpl(config.dealTemplate, deal);
				var $deal = BookingBuddy.Deals.addDealTracking($j(dealHTML), deal.obj);

				$targetedDiv.append($deal);
			});

			// on Success Hook call
			if (typeof config.onSuccess == "function") {
				config.onSuccess();
			}

		} else {

			// on Error Hook call
			if (typeof config.onError == "function") {
				config.onError();
			}
		}
	},

	/**
	 * Display deals in even groups based on the existence
	 * of multiple deals modules with the "split deals" class.
	 * Each split deals module will get an equal number of deals.
	 * No special formatting is done to deals.
	 *
	 * @param array	json	The list of bbdn deals
	 * @param object config	Display configurations for deals
	 */
	splitDeals: function(json, config) {

		$splitDivs = config.containerDivs;
		if (!$splitDivs[0]) { return; }

		if (!json.errorstr) {

			var numSplits = $splitDivs.size();

			var numDeals = $j.getSize(json);
			var numDealsPerSplit = Math.ceil(numDeals / numSplits);

			// add ad html evenly to each div split
			$j.each(json, function(index, deal) {
				index = Math.floor(index / numDealsPerSplit);

				var dealHTML = $j.tmpl(config.dealTemplate, deal);
				var $deal = BookingBuddy.Deals.addDealTracking($j(dealHTML), deal.obj);

				$j($splitDivs[index]).append($deal);
			});

			// on Success Hook call
			if (typeof config.onSuccess == "function") {
				config.onSuccess();
			}

		} else {

			// on Error Hook call
			if (typeof config.onError == "function") {
				config.onError();
			}
		}
	},

	/**
	 * Format deals for display in the classic top deals module.
	 * The formatting performed is controlled based on the configuration
	 * values passed in.
	 *
	 * @param array	json	The list of bbdn deals
	 * @param object config	Display configurations for deals
	 */
	topDeals: function(json, config) {

		$topDealsDiv = config.containerDivs;
		if (!$topDealsDiv[0]) { return; }

		// find the div that should contain the deals
		$contentDiv = $topDealsDiv.find('#' + config.divDeals);
		if (!$contentDiv[0]) { 
			$contentDiv = $topDealsDiv;
		}

		if (!json.errorstr) {
			// Display location name if needed
			if (config.cityName) {
				var $header = $topDealsDiv.find('.' + config.divDealsHeader);
				if ($header[0]) {	
					$header.html(config.cityName + config.headerMsg);				
				}
			}

			var html = '';
			var deal_row = 0;

			if (config.insertType == "update") {
				// Clear the contents of the div before appending more deals to it
				$j('#' + config.divDeals).html('');
			}

			$j.each(json, function(index, deal) {

				if (deal_row % 2 === 0) {
					deal.row_type = 'odd';
				}

				deal.dollar = '';
				if (config.priceNoDollarSymbol !== undefined) {
					if (deal.price.charAt(0) === '$') {
						deal.dollar = "$";
						deal.price = deal.price.substring(1);
					} 
				}

				// Fold destination text in two lines at the comma
				if (config.fold_destination) {
					var indx = deal.destination_name.indexOf(',');
					if (indx > 0) {
						deal.destination_name_folded = deal.destination_name.replace(/, /i,",<br />");
					} else {
						deal.destination_name_folded = deal.destination_name;
					}
				}

				// This is a hack for "$/% off" deals on the rev deals box.
				if (config.fold_price) {
					if (deal.price.toLowerCase().indexOf('off') > 0) {
						deal.price = deal.price.replace(/ off/i,"<br />off");
					}
				}

				var dealHTML = $j.tmpl(config.dealTemplate, deal);
				var $deal = BookingBuddy.Deals.addDealTracking($j(dealHTML), deal.obj);

				$contentDiv.append($deal);
				deal_row++;
			});

			// this needs to done since we're appending instead of updating the div
			if ($j('#' + config.divErrorMsg)[0]) {
				$j('#' + config.divDeals).hide();
			}

			// on Success Hook call
			if (typeof config.onSuccess == "function") {
				config.onSuccess();
			}

		} else {

			// on Error Hook call
			if (typeof config.onError == "function") {
				config.onError();
			}

			// update deals section with error message
			var errorMsg = '<span id="' + config.divErrorMsg + '">' + config.errorMsg + '</span>';
			$j('#' + config.divDeals).html(errorMsg);

			if ($j('#' + config.divErrorMsg).length > 0) {
				$j('#' + config.divErrorMsg).show();
			}
		}
	}
};

var SortableFareListing = Object.subClass({
	
	/**
	 * Handle to the table to display fares
	 *
	 * @type Element
	 */
	fare_table: null,
	
	/**
	 * Prototype template used to display fares
	 *
	 * @type Template
	 */
	fare_row_template: null,
	
	/**
	 * Array of fares
	 *
	 * @type array
	 */
	fares: [],
	
	/**
	 * Field to currently sort by
	 *
	 * @type string
	 */
	sort_field: '',
	
	/**
	 * Controls whether currently sorting ASC or DESC
	 *
	 *@type bool
	 */
	sort_asc: true,
	
	/**
	 * Array of sortable header columns
	 *
	 * @type array
	 */
	sort_headers: [],

	/**
	 * Array of filters
	 * @type array
	 */
	filters: [],
	
	/**
	 * Constructor. Sets up the fare_table and fare_row_template
	 *
	 * @param string table_id ID of the table to display fares in
	 * @param Template template Template used to display fares
	 */
	init: function(table_id, template) {	
		this.fare_table = $j('#' + table_id);
		this.fare_row_template = template;
	},
	
	/**
	 * Adds an individual fare to the fares array
	 *
	 * @param Object Fare
	 */
	addFare: function(fare) {
		this.fares.push(fare);
	},
	
	/**
	 * Adds all the given fares to the fare array
	 *
	 * @param array fares
	 */
	addFares: function(fares) {
		$j.each(fares, $j.proxy(function(index, fare) {
			this.addFare(fare);
		}, this));
	},
	
	/**
	 * Clears the fares out of the table
	 */
	clearTable: function() {
		//Get a list of all the fare rows
		var rows = $j('.sortable_fare_listing_row');
		if (rows.length > 0) { 
			//Hide each row and mark it for deletion. I know what you're thinking.
			//"Why wouldn't you just delete the rows rather then hiding them?" Well I'm
			//glad you asked.  Removing rows is veeeeeery slow and makes sorting seem
			//sluggish.  By doing this we can make it seem as if the rows are removed,
			//carry on with the display of the table, then come back and clean them
			//up later while the user is doing something else.
			$j.each(rows, function(index, row) {
				$j(row).hide().addClass('sortable_fare_listing_garbage_collect');
			});

			//Set the timer to come back and remove all the rows marked for deletion
			setTimeout(function() {
				$j('.sortable_fare_listing_garbage_collect').remove();
			}, 1500);
		}
	},
	
	
	/**
	 * Displays the fares currently in the fares array by eval'ing fare_row_template
	 * and appending them to the fare_table
	 */
	display: function() {
		this.clearTable();
		
		var tbody = $j(this.fare_table).find('tbody');

		// Create all rows and append them to the table
		var row = '';
		$j.each(this.filterFares(), $j.proxy(function(index, fare) {
			// Creating striped rows containing data evaluated by the template
			row += '<tr class="sortable_fare_listing_row '; 
			if (index % 2) { row += ' odd'; }
		    row +=	'">' + $j.tmpl(this.fare_row_template, fare) + '</tr>';

		}, this));
		$j(tbody).append(row);
	},
	

	/**
	 * Add filter based on input and filter_field
	 *
	 * @param string input_id
	 * @param string filter_field
	 * @param string container
	 */
	addFilterToggle: function(input_id, filter_field, container) {
		var input = $j('#' + input_id);
		
		if (input.length > 0) { 
			this.filters.push({input: input, field: filter_field, container: $j('#' + container)});
			input.bind('change', $j.proxy(this.display, this));
		}
	},
	
	/**
	 * Returns filtered_fares, enables/disables checkboxes and sets 
	 * their class names
	 * @returns array  - filtered_fares
	 */
	filterFares: function() {
		var active_filters = [];
		
		//Determine which filters to apply
		$j.each(this.filters, function(index, filter) {
			var input = $j(filter.input);

			var filter_value = input.val();
			if (filter_value.length === 0) { return; }
			
			if (input.is(':checked')) {
				active_filters.push({field: filter.field, value: filter_value});
			}
		});
		
		//Produce a list of fares with the filters applied
		var filtered_fares = [];
		var dispaly_fare = true;
		$j.each(this.fares, function(index, fare) {
			display_fare = true;
			
			$j.each(active_filters, function(index, filter) {
				if (fare[filter.field] != filter.value) { display_fare = false; }
			});
			
			if (display_fare) { filtered_fares.push(fare); }
		});
		
		//Enable/disable filters
		$j.each(this.filters, function(index, filter) {
			var enable_filter = false;
			
			$j.each(filtered_fares, function(index, fare) {
				if (fare[filter.field] == filter.input.val()) { enable_filter = true; }
			});
			
			if (enable_filter) {
				filter.input.removeAttr('disabled');	
			} else {
				filter.input.attr('disabled', true);
			}
			
			// add/remove css classes
			if (filter.container.length > 0) {
				if (enable_filter) {
					filter.container.removeClass('filter_disabled');
				} else {
					filter.container.addClass('filter_disabled');
				}
			}
		});
		
		return filtered_fares;
	},
	
	/**
	 * Adds a clickable element that sorts by the given field
	 *
	 * @param string header_id ID of the element to use as a sort header
	 * @param string sort_field Field to sort by
	 * @return bool
	 */
	addSortableColumn: function(header_id, sort_field) {
		var header = $j('#' + header_id);
		
		if (header.length > 0 ) { 
			this.sort_headers.push(header);
			
			header.addClass('sortable');
			header.bind('click', $j.proxy(function() {
				this.sortTable(header_id, sort_field);
			}, this));
			
			return true;
		} else {	
			return false;
		}
	},
	
	/**
	 * Removes the asc and desc classes from all sort headers
	 */
	clearSortHeaders: function() {
		$j.each(this.sort_headers, function(header) {
			$j(header).removeClass('asc desc');
		});
	},
	
	/**
	 * Sorts the fares in the fares array and calls display to refresh the table
	 *
	 * @param string header_id ID of the column sort header
	 * @param string sort_field Name of the field to sort by
	 */
	sortTable: function(header_id, sort_field) {
		var header = $j('#' + header_id);
		
		if (header.length > 0) {
			this.clearSortHeaders();
			
			if (sort_field == this.sort_field) {
				this.sort_asc = !this.sort_asc;
			}
			else {
				this.sort_field = sort_field;
				this.sort_asc = true;
			}
			
			header.addClass(this.sort_asc ? 'asc' : 'desc');
			
			this.fares.sort($j.proxy(this.sortHelper, this));
			this.display();
		}
	},

	/**
	 * Sort helper function to compare two fares by the current sort_field and sort_asc
	 *
	 * @param Object fare_1
	 * @param Object fare_2
	 * @return int
	 */
	sortHelper: function(fare_1, fare_2) {
		var value_1 = fare_1[this.sort_field];
		var value_2 = fare_2[this.sort_field];
		var ret;

		if (value_1 > value_2) {
			ret = 1;
		} else if (value_1 === value_2) {
			ret = 0;
		} else {
			ret = -1;
		}
		
		if (!this.sort_asc) {
			ret = -ret;
		}
		
		return ret;
	}
});

(function($) {
	
	$(document).ready(function() {
		if($("form#change_prefs_form").length > 0) {
			// Reset function for changing user prefs

			$("#edit_prefs_section_initial" ).bind("STMToggle:initialStateShow", function(e) {
				var _initial_email = $.trim($("#email_initial").html());
				var _initial_zip_us = $.trim($("#zip_us_initial").html());
				var _initial_zip_other = $.trim($("#zip_other_initial").html());
				var _initial_airport = $.trim($("#airport_initial").html());
				
				$("#change_prefs_new_email").val(_initial_email);
				$("#change_prefs_new_email_verify").val(_initial_email);
				$("#change_prefs_postalcode_us").val(_initial_zip_us);
				$("#change_prefs_postalcode_other").val(_initial_zip_other);
				$("#change_prefs_preferred_airport").val(_initial_airport);
				$("#change_prefs_form").data("smartForm").clearErrors();
			});
			$("#edit_prefs_section_initial" ).bind("STMToggle:initialStateHide", function(e) {
				var _initial_email = $.trim($("#email_initial").html());
				var _initial_zip_us = $.trim($("#zip_us_initial").html());
				var _initial_zip_other = $.trim($("#zip_other_initial").html());
				var _initial_airport = $.trim($("#airport_initial").html());
				
				$("#change_prefs_new_email").val(_initial_email);
				$("#change_prefs_new_email_verify").val("");
				$("#change_prefs_postalcode").val(_initial_zip);
				$("#change_prefs_preferred_airport").val(_initial_airport);				
			});
			
			$("#change_prefs_form" ).smartFormFactory();
			
			$("#change_prefs_form" ).bind("SmartForm:invalid", function(e) {
				if( $("#basic_content").position()['top'] ) {
		            window.scrollTo(0, $("#basic_content").position()['top']);
		         } 
			});
		}
	});
	
	SubPrefsPane = Object.subClass( {
		checkbox: null,
		prefs_pane: null,
		subscription_count: 0,
		subscriptions_index: 0,
		max_subscriptions: -1,
		subscriptions_div: null,
		add_buttons: [],
		route_prefix: "",
		subscription_row_template: "",
	
		init: function(checkbox_id, pane_id, subscriptions_div_id) {
			this.checkbox = $("#"+checkbox_id);
			this.prefs_pane = $("#"+pane_id);
			this.subscriptions_div = $("#"+subscriptions_div_id);

			//IE doesn't always fire change events, so listen for click as well
			this.checkbox.change($.proxy( this.toggleDisplay, this ));
			this.checkbox.click($.proxy( this.toggleDisplay, this ));
			
			this.toggleDisplay();
		},

		toggleDisplay: function() {
			if (this.checkbox.is(":checked")) {
				this.prefs_pane.show();
			} else {
				this.prefs_pane.hide();
			}
		},

		addSubscriptionRow: function(params) {
			if (!params) {
				params = {};
			}
			this.subscription_count++;
			this.subscriptions_index++;
			var id = this.subscriptions_index;
	
			params.id = id;
			var _row = $j.tmpl(this.subscription_row_template, params);
			this.subscriptions_div.append(_row);
			
			//If the user has hit the max number of subscriptions hide all of the add buttons
			if(this.subscription_count >= this.max_subscriptions) {
				$.each(this.add_buttons, function() { $(this).hide(); } );
			}
			
			//Attach listener to the remove button
			$("#remove_subscription_" + this.id_prefix + id).click( $.proxy(function() { this.removeSubscriptionRow(id); },this));
			return id;
		},

		removeSubscriptionRow: function(id) {
			var row = $("#subscription_row_" + this.id_prefix + id);
			
			if (!row) {
				return;
			}
			
			this.subscription_count--;
			row.remove();
			
			if(this.subscription_count === 0) {
				this.checkbox.attr("checked", false);
				this.toggleDisplay();
				
				//Add an empty row so when the user comes back they'll have something to look at
				this.addSubscriptionRow();
			}
	
			//If the user is under the max number of subscriptions show all of the add buttons
			if(this.subscription_count < this.max_subscriptions) {
				$.each(this.add_buttons, function() { $(this).show(); } );
			}
		},
	
		createAddRowButton: function(button_id) {
			var button = $("#" + button_id);
			this.add_buttons.push(button);
			button.click( $.proxy( this.addSubscriptionRow, this) );
			}
			
		});
	
	DeparturePrefsPane = SubPrefsPane.subClass({
		subscription_row_template: "<div id='subscription_row_departure_#{id}' class='departure_city_sub'>" +
				 "<input type='text' id='departure_city_#{id}' name='departure_city[]' value='#{city}' />" +
				 "<select id='departure_frequency_#{id}' class='departure_nl_frequency' name='departure_frequency[]'>" +
					"<option value='daily'>Daily (up to 7 times per week)</option>"+
					"<option value='frequent'>2-3 times per week</option>"+
					"<option value='weekly'>Weekly</option>"+
				"</select>" +
				 "<div id='remove_subscription_departure_#{id}' class='remove_departure_city'>remove</div>" +
				 "</div>",

		max_subscriptions: 5,
		id_prefix: "departure_",
		addSubscriptionRow: function(city, frequency) {
			if(!$.isString(city)) {
				city = "";
			}
			if (!$.isString(frequency)) {
				frequency = 'frequent';
			}
			var id = this._super({city: city, frequency: frequency});
			
			$j('#'+ this.id_prefix + 'frequency_' + id).val(frequency);
			var _ls_departure = new LocationSuggest("departure_city_" + id, ["airport"]);
		}
			
	});
	
	RoutePrefsPane = SubPrefsPane.subClass({
		subscription_row_template: "<div id='subscription_row_route_#{id}' class='route_sub'>" +
				 "<div class='route_inputs'><input type='text' id='route_departure_city_#{id}' name='route_departure_city[]' value='#{departure_city}' /> to " +
				 "<input type='text' id='route_arrival_city_#{id}' name='route_arrival_city[]' value='#{arrival_city}' /></div>" +
				 "<div id='remove_subscription_route_#{id}' class='remove_route'>remove</div>" +
				 "</div>",
		
		max_subscriptions: 25,

		id_prefix: "route_",

		addSubscriptionRow: function(departure_city, arrival_city) {
			if(!$.isString(departure_city)) {
				departure_city = "";
			}
			if(!$.isString(arrival_city)) {
				arrival_city = "";
			}

			var id = this._super({departure_city: departure_city, arrival_city: arrival_city});
			var _ls_departure = new LocationSuggest("route_departure_city_" + id, ["airport"]);
			var _ls_arrival = new LocationSuggest("route_arrival_city_" + id, ["airport"]);
		}

	});

	HotelWatchPrefsPane = SubPrefsPane.subClass({
		subscription_row_template: "<div id='subscription_row_hotel_watch_#{id}' class='hotel_watch_city_sub'>" +
				 "<input type='text' id='hotel_watch_city_#{id}' name='hotel_watch_city[]' value='#{city}' />" +
				 "<select id='hotel_watch_frequency_#{id}' class='hotel_watch_frequency' name='hotel_watch_frequency[]'>" +
					"<option value='daily'>Daily (up to 7 times per week)</option>"+
					"<option value='frequent'>2-3 times per week</option>"+
					"<option value='weekly'>Weekly</option>"+
				"</select>" +
				"<div id='remove_subscription_hotel_watch_#{id}' class='remove_hotel_watch_city'>remove</div>" +
				 "</div>",

		max_subscriptions: 10,
		id_prefix: "hotel_watch_",
		addSubscriptionRow: function(city, frequency) {
			if(!$.isString(city)) {
				city = "";
			}
			if (!$.isString(frequency)) {
				frequency = 'frequent';
			}
			var id = this._super({city: city});
			$j('#'+ this.id_prefix + 'frequency_' + id).val(frequency);
			var _ls_departure = new LocationSuggest("hotel_watch_city_" + id, ["city"]);
		}

	});
	
	
})(jQuery);

/**
 * Class to allow loading of page content to be defered until
 * after the DOM is loaded and then load the content in an 
 * iframe. This prevents 3rd party content from slowing down our
 * pages (e.g. DART Ads, Facebook 'Like' button). Required and
 * extra data may be passed as JSON-encoded text in a child element
 * with a specific class name.
 *
 * Example:
 * <code>
 * <div class="partner_content" style="display: none;">
 *     <div class="meta_data">
 *     {"endpoint": "http:\/\/www.example.com\/blah\/"}
 *     </div>
 * </div>
 * <script>
 * $j(document).ready(function() {
 *     $j('.partner_content').each(function(index, elm) {
 *         var loader = new ContentFrame(elm);
 *         loader.load();
 *      });
 * });
 * </script>
 * </code>
 *
 * The above code would cause example.com content to be loaded using 
 * an iframe and inserted in the page only after it has finished 
 * loading. Any extra data contained in "meta_data" would be passed
 * as query string parameters to the endpoint URL.
 *
 * The following keys in the meta data JSON are required or have
 * special meaning. All keys are passed as query string params
 * except "endpoint":
 * -- "endpoint" Required URL src for the content
 * -- "height" If present, the iframe will be set to this height
 * -- "width" If present, the iframe will be set to this width
 *
 * Requires jQuery and Utility.js
 */
var ContentFrame = Object.subClass({

	/**
	 * Element we should look for meta data in and which the
	 * iframed content will be loaded in
	 * 
	 * @type jQuery
	 */
	elm: null,

	/**
	 * jQuery selector that will be used to load meta data for
	 * this content
	 *
	 * @type String
	 */
	data_sel: '.meta_data',

	/**
	 * Object of any meta data parsed for this framed content object
	 *
	 * @type Object
	 */
	data: null,

	/**
	 * Base URL of the framed content
	 *
	 * @type String
	 */
	endpoint: '',

	/**
	 * Set the container element that currently contains the meta data
	 * and will contain the iframe. Parse any meta data found.
	 *
	 * @param {String|jQuery} Element or selector to find it
	 */
	init: function(selector) {
		this.elm = $j(selector);
		if (!this.elm.length) {
			return;
		}

		this.data = {};
		this._loadMetaData();
	},

	/**
	 * Build the iframe and add it to the page if we have all the required
	 * information to display it
	 *
	 * @param bool show_now - if the content frame should be shown immediately
	 *
	 * @return bool
	 */
	load: function(show_now) {
		if (!this.endpoint || !this.elm.length) {
			return false;
		}

		var ad_frame = this._buildFrame();
		if (!ad_frame) {
			return false;
		}

		this.elm.html(ad_frame);

		if (show_now !== false) {
			this.elm.show();
		}
		return true;
	},

	/**
	 * Try to find an element containing meta data JSON for this placement
	 * and parse and store it
	 *
	 * @return void
	 */
	_loadMetaData: function() {
		var meta = $j(this.data_sel, this.elm);
		if (!meta.length) {
			return;
		}

		this.data = $j.parseJSON(meta.text());

		if (this.data.endpoint) {
			this.endpoint = this.data.endpoint;
			delete this.data.endpoint;
		}
	},

	/**
	 * Construct and return an iframe object based on any meta data
	 * that has been set thus far
	 *
	 * @return jQuery
	 */
	_buildFrame: function() {
		// use attr here since specifying the properties
		// as part of the constructor doesn't seem to work
		// consistently
		var ad_frame = $j('<iframe />').attr({
			'src': this._getFrameSrc(),
			'scrolling': 'no',
			'frameborder': '0'
		});

		if (this.data.width) {
			ad_frame.attr('width', this.data.width);
		}

		if (this.data.height) {
			ad_frame.attr('height', this.data.height);
		}

		return ad_frame;
	},
	
	/**
	 * Build the iframe src using the endpoint and any extra
	 * data that should be passed as query string params.
	 *
	 * @return String
	 */
	_getFrameSrc: function() {
		var out = this.endpoint;
		if (-1 === this.endpoint.search(/\?/)) {
			out += '?';
		}

		if (!$j.isEmptyObject(this.data)) {
			out += Object.toQueryString(this.data);
		}

		return out;
	}
});

/**
 * BookingBuddy.Maps
 * @static
 * 
 * A controller of sorts for the stm_google_maps.  This class handlers creating and
 * toggling markers on BookingBuddy google maps.  Marker information can either be
 * stored initially or be loaded asynchronously.
 *
 * BookingBuddy.Maps.Data
 *
 * Stores map data by the element id for use later when a map is created
 */
(function($) {
	BookingBuddy.Maps = {

		/**
		 * List of ta location ids for which hotel data
		 * has already been loaded
		 */
		locationData: {},

		/**
		 * Default marker parameters. Changes made here will potentially affect all
		 * marker types. Any specific parameter overrides should be made to the
		 * BookingBuddy.Maps.markerIcons object.  
		 */
		markerDefaults: {
			icon: {
				// The icon image
				image: 'http://i.slimg.com/bookingbuddy/geo/attraction-pin.png',
				// The active icon image; e.g. for use with hovering
				activeImage: null,
				// The image width and height in pixels
				size: [27, 31],
				// The x, y coordinates or the origin point for the image. This
				// is used as a relative position for determining the anchor position.
				// 0, 0 is the top left
				origin: [0, 0],
				// The x, y pixel offsets from origin to the point of the image
				// (where the image is anchored on the map)
				anchor: [11, 27]
			},
			shape: {
				// The bounding box outlining the clickable/active region of the image.
				// Each pair of numbers represents a point on the image
				coord: [0, 0, 23, 0, 23, 22, 17 , 22, 12, 27, 6, 22, 0, 22],
				type: 'poly'
			}
		},

		/**
		 * Marker parameters for each type. This mimics the structure of markerDefaults
		 * for each marker type. Any missing parameters for a type will be filled in
		 * with default values. Any modifications to markerIcons parameters must happen
		 * before the marker type is created. Otherwise, they will have no effect.
		 */
		markerIcons: {
			attraction: {
				icon: {
					image: 'http://i.slimg.com/bookingbuddy/geo/attraction-pin.png',
					activeImage: 'http://i.slimg.com/bookingbuddy/geo/attraction-pin-on.png'
				}
			},
			hotel: {
				icon: {
					image: 'http://i.slimg.com/bookingbuddy/geo/hotel-pin.png',
					activeImage: 'http://i.slimg.com/bookingbuddy/geo/hotel-pin-on.png'
				}
			},
			basicHotel: {
				icon: {
					image: 'http://i.slimg.com/bookingbuddy/geo/hotel-pin-on.png'
				}
			}
		},

		/**
		 * Show or hide markers on the map for location data loaded from an ajax request
		 * 
		 * @param object map - the stm_google_map
		 * @param object locations - a map of locations to actions for each location
		 * @param bool show - if the markers should be shown
		 * @static
		 */
		loadLocationData: function(map, locations, show) {

			var idsToLoad = {};
			$.each(locations, function(id, action) {

				// verify the location hasn't already been loaded
				if ($.isUndefined(BookingBuddy.Maps.locationData[id])) {
					if ($.isUndefined(idsToLoad[action])) {
						idsToLoad[action] = [];
					}

					idsToLoad[action].push(id);
				} else {
					// the type should be specified as the prefix for the action
					// e.g. hotel_radius to perform a radius search and return hotels
					var type = BookingBuddy.Maps.locationData[id].type;

					map.toggleMarkersByType(show, type, id);
				}
			});

			if ($.getSize(idsToLoad) <= 0) {
				map.resize();
				return;
			}

			// loop through all actions 
			$.each(idsToLoad, function(action, ids) {
				map.element.trigger(GoogleMap.constants.EVENT_LOADING);

				var type = action.split('_')[0];

				$.bbget('Locs.' + action, {ids: ids}, function(data) {

					if ($.isNullOrUndefined(data) || $.isNullOrUndefined(data.success)) {
						map.element.trigger(GoogleMap.constants.EVENT_LOADED);
						return;
					}
					data = data.success;

					var type = data.type;

					$.each(data.data, function(locId, children) {
						BookingBuddy.Maps.locationData[locId] = {
							type: type,
							data: children
						};

						$.each(children, function(childId, childData) {
							map.addMarker(childId, {type: type, subType: locId}, childData, {});
						});

						map.toggleMarkersByType(true, type, locId);
					});

					map.resize();
					map.element.trigger(GoogleMap.constants.EVENT_LOADED);
				});
			});
		},

		/**
		 * Show or hide markers on the map for given pois
		 * 
		 * @param object map - the stm_google_map
		 * @param object pois - a map of poi types and data
		 * @param bool show - if the markers should be shown
		 */
		loadPOIs: function(map, pois, show) {

			if ($.isNullOrUndefined(pois)) {
				return;
			}

			// add the point of interest markers
			$.each(pois, function(markerType, pointsOfInterest) {

				// loop over each poi in the list of marker types
				$.each(pointsOfInterest, function(key, data) {

					// data has already been loaded, so just add them
					if (data && data.coords) {
						map.addMarker(key, {type: markerType}, data, {});
					}

				});

				map.toggleMarkersByType(show, markerType);
			});

			map.resize();
		},


		/**
		 * Add google geo landing page-specific marker types to the map
		 *
		 * @param string type - the identifier and type of the marker template
		 */
		createMarkerType: function(type) {
			// verify the google maps api has been loaded
			if ($.isUndefined(window['google'])) {
				return;
			}
		
			var mType = GoogleMap.MarkerType.singleton(type);

			// combine the default icon params with ones specific to this marker type
			var params = $.extend(true, {}, BookingBuddy.Maps.markerDefaults, BookingBuddy.Maps.markerIcons[type]);

			// initialize parameters for the google.maps.MarkerImage call
			var options = {
				image: params.icon.image,
				size: new google.maps.Size(params.icon.size[0], params.icon.size[1]),
				origin: new google.maps.Point(params.icon.origin[0], params.icon.origin[1]),
				anchor: new google.maps.Point(params.icon.anchor[0], params.icon.anchor[1])
			};

			var icon = new google.maps.MarkerImage(options.image, options.size, options.origin, options.anchor);

			// check if an active state icon exists for this marker type
			if (!$.isNullOrUndefined(params.icon.activeImage)) {
				options.image = params.icon.activeImage;
				var activeIcon = new google.maps.MarkerImage(options.image, options.size, options.origin, options.anchor);
			}

			mType.setOptions({
				icon: icon,
				shape: params.shape
			});

			var triggers = {};

			switch (type) {
				case 'hotel':

					mType.createInfoWindow('hotel_check_rates_map');
					triggers['mouseover'] = function(map, marker, config) {
							if (map.currentMarker && map.currentMarker.id == marker.id) {
								return;
							}

							marker.googleMarker.setZIndex(10000);
							marker.googleMarker.setIcon(activeIcon);

							var markerType = GoogleMap.MarkerType.singleton(marker.type);

							var operations = function() {

								var element = $(this);
								element.find('.hotel_search_title').html(config.name);

								// update the provider and set the hotel id to guarantee the check rates
								// are updated
								var provider = element.find('input[name=provider]');
								provider.val(config.name);
								provider.data('hotel_id', config.id);
								provider.change();
							};

							markerType.updateInfoWindow(operations);
							markerType.showInfoWindow(map, marker, { left: -40, top: -3 });
					};
					triggers[GoogleMap.constants.EVENT_BLUR] = function(map, marker, config) {
							var markerType = GoogleMap.MarkerType.singleton(marker.type);

							marker.googleMarker.setZIndex(null);
							marker.googleMarker.setIcon(icon);

							markerType.hideInfoWindow();
					};
					break;
				case 'attraction':
					var template =
						'<div class="map_attraction">' +
						'   <div class="top"><div class="closePopup"></div></div>' +
						'   <div class="middle">' +
						'       <div class="contents">' +
						'           <div class="ta_rating s00"></div>' +
						'           <div class="title"></div>' +
						'           <div class="address"></div>' +
						'       </div>' +
						'   </div>' +
						'    <div class="bottom"></div>' +
						'</div>';

					mType.createInfoWindow('map_attraction_popup', template);

					triggers['mouseover'] = function(map, marker, config) {
							if (map.currentMarker && map.currentMarker.id == marker.id) {
								return;
							}

							marker.googleMarker.setZIndex(10000);
							marker.googleMarker.setIcon(activeIcon);

							var markerType = GoogleMap.MarkerType.singleton(marker.type);

							var operations = function() {
								var rating = parseInt(config.rating, 10);
								rating = rating + '' == 'NaN' ? 0 : rating;
								rating = rating < 10 ? '0' + rating : rating;

								var element = $(this);
								element.find('.address').html(config.address);
								element.find('.title').html(config.name);
								element.find('.ta_rating').removeClass().addClass('ta_rating s' + rating);
							};

							markerType.updateInfoWindow(operations);
							markerType.showInfoWindow(map, marker, { left: -40, top: -3 });
					};
					triggers[GoogleMap.constants.EVENT_BLUR] = function(map, marker, config) {
							var markerType = GoogleMap.MarkerType.singleton(marker.type);

							marker.googleMarker.setZIndex(null);
							marker.googleMarker.setIcon(icon);

							markerType.hideInfoWindow();
					};
					break;
				default:
					break;
			}

			mType.setTriggerEvents(triggers);
		}
	};

	/**
	 * Constructor for the class to hold map data for any maps being shown on the
	 * page. Markers can be added to the map as points of interest with all marker
	 * information already set, or as a location id to load additional information
	 * in an ajax request.  Event handlers for actions on the map or map info
	 * window can be added as well.
	 *
	 * @param string id - an identifier for the specific map
	 * @param object coords - latitude and longitude coordinates
	 *		for the center point of the map
	 */
	BookingBuddy.Maps.Data = function(id) {

		this.id = id;
		this.centerPoint = {};
		this.options;
		this.events;

		this.pointsOfInterest = {};
		this.locations = {};
		this.mapData = {id: id};
	};

	$.extend(BookingBuddy.Maps.Data, {

		/**
		 * instance pool for map data
		 */
		pool: {},


		/**
		 * singleton pattern for creating/retrieving specific
		 * instances of the map data class
		 *
		 * @param string id - the identifier for a map
		 */
		singleton: function(id) {
			if ($.isUndefined(BookingBuddy.Maps.Data.pool[id])) {
				BookingBuddy.Maps.Data.pool[id] = new BookingBuddy.Maps.Data(id);
			}

			return BookingBuddy.Maps.Data.pool[id];
		},

		prototype: {

            /**
             * get the id of this map
             * 
             * @return string
             */
            getId: function() {
                return this.id;
            },

			/**
			 * get the center point coordinates of this map
			 * 
			 * @return object
			 */
			getCenterPoint: function() {
				return this.centerPoint;
			},

			/**
			 * get options set for this map
			 * 
			 * @return object
			 */
			getOptions: function() {
				return this.options;
			},

			/**
			 * get events set for this map
			 * 
			 * @return object
			 */
			getEvents: function() {
				return this.events;
			},

			/**
			 * get all points of interest set on this map
			 * 
			 * @return object
			 */
			getPOIs: function() {
				return this.pointsOfInterest;
			},

			/**
			 * get points of interest set on this map for the specified type
			 * 
			 * @param string type - the type of attraction
			 * @return object
			 */
			getPOIsByType: function(type) {
				if ($.isUndefined(this.pointsOfInterest[type])) {
					return null;
				}

				var result = {};
				result[type] = this.pointsOfInterest[type];
				return result;
			},

			/**
			 * get all location data set on this map
			 * 
			 * @return object
			 */
			getLocations: function() {
				return this.locations;
			},

			/**
			 * get location data on this map for the specific id
			 * 
			 * @param string id - the id of the location
			 * @return object
			 */
			getLocation: function(id) {
				if ($.isUndefined(this.locations[id])) {
					return null;
				}

				var result = {};
				result[id] = this.locations[id];
				return result;
			},

			/**
			 * set the center point of this map
			 *
			 * @param object coords - the latitude of longitude of the map center point
			 */
			setCenterPoint: function(coords) {
				this.centerPoint = coords;
			},

			/**
			 * set options for this map
			 * 
			 * @param object options - options to pass to the constructor for the map
			 */
			setOptions: function(options) {
				this.options = options;
			},

			/**
			 * set events and handlers for this map
			 * 
			 * @param object events - event types and handler functions for this map
			 */
			setEvents: function(events) {
				this.events = events;
			},

			/**
			 * add a point of interest to this map
			 * 
			 * @param string key - identifier of the poi
			 * @param string type - type of poi (e.g. attraction, hotel, etc.)
			 * @param object data - center point and any other information for the poi
			 */
			addPOI: function(key, type, data) {
				if ($.isUndefined(this.pointsOfInterest[type])) {
					this.pointsOfInterest[type] = {};
				}

				this.pointsOfInterest[type][key] = data;
			},

			/**
			 * set all points of interest on this map
			 * 
			 * @param object pois - object containing poi types and data
			 */
			setPOIs: function(pois) {
				this.pointsOfInterest = pois;
			},

			/**
			 * add a location id to look up for this map
			 * 
			 * @param int id - the location id
			 * @param string action - The type of request desired to be made
			 * 		for this location. E.g. hotel_radius, hotel_children
			 */
			addLocation: function(id, action) {
				this.locations[id] = action;
			},

			/**
			 * overwrite any existing location information
			 * 
			 * @param object locations - mapping of location ids to actions
			 */
			setLocations: function(locations) {
				this.locations = locations;
			}
		}
	});

})(jQuery);


/*
 * jQuery Smarter Travel Media Smart Form plugin
 *
 *
 *
 */
(function($) {

	$.smartFormMap = {};
	$.commonInputsMap = {};

	$.extend($.fn, {
		smartFormFactory: function(options, skip_override) {

			// if nothing is selected, return nothing; can't chain anyway
			if (!this.length) {
				return null;
			}

			// check if a smart form for this form was already created
			var smartForm = $.data(this[0], 'smartForm');
			if (smartForm) {
				if (!skip_override) {
					smartForm.settings = $.extend(smartForm.settings, options);

					// if there are supplied message overrides apply them recursivly
					if (smartForm.settings.message_overrides) {
					    $.extend(true, smartForm.settings.messages, smartForm.settings.message_overrides);
					}
				}
				return smartForm;
			}

			smartForm = new $.smartForm(options, this[0]);
			$.data(this[0], 'smartForm', smartForm);

			$.smartFormMap[smartForm.id] = smartForm;

			// Override the submit function for this form
			this.submit( $.proxy( function(e)  {
				smartForm.submit();
				if (this.settings.debug) {
					this.debug();
				}
				if (this.valid) {
					this.cleanForSubmit();
					if (this.searchForm) {
						this.bbSearchData.addRecentSearchData(this.mode);
						this.showRecentSearchData();

					}
					if (this.settings.submitHandler) {
						this.submitHandlerData = {};
						var _handlerResult =  this.settings.submitHandler(this.currentForm[0], this.settings.submitTarget, e);
						if ( this.settings.debug ) {
							BBDebug.dir(this.submitHandlerData);
						}
						return _handlerResult;
					}
					else {
						return ! this.settings.debug;
					}
				}
				// LOGGING INVALID FORM TO SERVER
				var _msg = [];
				$.each( this.invalidElements, function(id, elem) {
					if (elem) {
						var _elem_msg = elem.elementId + "='" + elem.value() + "' ";
						var _err_msg = ", failed:";
						var comma = "";
						$.each(elem.ruleStatus, function(id, status) {
							if (!status) {
								_err_msg = _err_msg + comma + id;
								 comma = ", ";
							}
						});
						_msg.push(_elem_msg + _err_msg);
					}
				});
				//BBDebug.log("Invalid form submission: " + this.id + ", values: " + _msg.join(" :: ") + ", " + window.location.href);
				BBDebug.logErrorToServer("Invalid form submission for " + this.id + ":  values = " + _msg.join(" :: "), window.location.href);
				e.stopPropagation();
				e.preventDefault();
				return false;
			}, smartForm) );
			this.trigger("SmartForm:ready");
			return smartForm;
		}
	});

	$.smartFormsBBSavedSearchData = null;

	$.smartFormsDebug = function(  ) {
		$.each( $.smartFormMap, function(id, smartForm) {
			smartForm.debug();
		});
		/*$.each( $.commonInputsMap, function(id, inputs) {
			BBDebug.group("Common Inputs: " + id, true);
			BBDebug.dir(inputs);
			BBDebug.groupEnd();
		});*/

	};

	// constructor for smartForm
	$.smartForm = function(options, form) {
		// default to using the forms TARGET attribute if it is set
		var _target = $(form).attr("target");
		if (!$.emptyString(_target)) {
			options.submitTarget = _target;
			// Standardize on '_top' instead of '_self'
			if (options.submitTarget === "_self") {
				options.submitTarget = "_top";
			}
		}

		this.settings = $.extend({}, $.smartForm.defaults, options);
		this.currentForm = $j(form);
		this.currentForm.keypress(function(e) { return $.data(this, 'smartForm').settings.keyPress(e); });
		this.id = $(form).attr("id");
		this.searchForm = false;
		this.validators = [];
		this.submitHandlerData = {};
		this.init();
	};

	$.extend($.smartForm, {
		/*
		 * Supported date formats
		 */
		dateFormats:  {
			DEFAULT:/^\d{2}[\/-]\d{2}[\/-]\d{4}$/,
			UK:/^\d{2}[\/-]\d{2}[\/-]\d{4}$/,
			ISO:/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/
		},
		constants:  {
				SMART_FORM: 'smartForm',  // Add this class to a form to have it recognized as a smart form
				SMART_ELEMENT: 'smartElement', // Add this class to a form to have it recognized as a smart element
				SEARCH_FORM: 'searchForm',
				NOT_VALIDATED: 'noValidation',
				COMMON: 'common',
				REQUIRED: 'required',
				ZIP: 'zip',
				EMAIL: 'emailCheck',
				EMAIL_VERIFY: 'emailVerify',
				DATE: 'date',
				ARRIVAL_CITY_CONTEXT_ID: '_arrival_city',
				DEPARTURE_CITY_CONTEXT_ID: '_departure_city',
				DEPARTURE_DATE: 'departureDate',
				RETURN_DATE: 'returnDate',
				PRINT_DATE_TO_TEXT: 'printDateToText',
				LS: 'locationSuggest',
				LS_COUNTRY: 'country',
				LS_REGION: 'region',
				LS_STATE: 'state',
				LS_CITY: 'city',
				LS_AIRPORT: 'airport',
				LS_VALID: 'LSValid',
				LS_INVALID: 'LSInvalid',
				LS_UNSUPPORTED_LOCATION: 'LSUnsupportedLocation',
				LS_WARNING_PREFIX: 'LocationWarning_',
				LS_AWD_DEP_CITY: 'awdDepartureCity',
				INVALID: 'invalid',
				CALENDAR: 'calendarUI',
				DEFAULT_CLEAR: 'defaultClear',
				RECENT_SEARCHES_DIV: 'recent_searches',
				DELAYED_LOAD: 'delayedLoad', // Do not load current search data until toggled on
				ERROR_MSG_CLASS: "smartFormErrorMessages",
				UK_LOCALE_CLASS: "UKLocale"
		},

		messages: {
			required: "'#{field_name}' is a required field.",
			date: "Please enter a valid '#{field_name}'.",
			dateNotPast: "Please enter a '#{field_name}' date that is not in the past.",
			departureDate: "Please enter a '#{field_name}' date that is not in the past.",
			returnDate: "Please enter a '#{field_name_1}' date that is not earlier than the '#{field_name_2}' date.",
			ls_invalid: "Please enter a valid #{location_type} in the '#{field_name}' field to continue.",
			ls_required: "Please enter a location in the '#{field_name}' field to continue.",
			ls_departure_required: "Please enter a departure #{location_type} in the '#{field_name}' field to continue.",
			ls_arrival_required: "Please enter an arrival #{location_type} in the '#{field_name}' field to continue.",
			ls_multiple_suggestions: "There may be more than one option for the #{location_type} you entered in the '#{field_name}' field.",
			ls_unsupported_location: "We're sorry, we do not currently support searches within #{location}",
			zip: "Please enter a valid zip code - leave blank for Non-US/Canadian codes",
			email: "Please enter a valid Email address in the '#{field_name}' field to continue.",
			emailVerify: "The Email addresses do not match",
			arrivalCity: "Please enter different '#{field_name_1}' and '#{field_name_2}' locations to continue.",
			holiday_departure_required: "Please select an airport from the '#{field_name}' field to continue.",
			holiday_arrival_required: "Please select a destination from the '#{field_name}' field to continue."
		}

	});
	$.extend($.smartForm, {
		defaults: {
			messages: $.smartForm.messages,
			locale: "DEFAULT",
			dateFormat: $.smartForm.dateFormats.DEFAULT,
			debug: false,
			//calendar: 'undefined',
			//calendar: calendar,
			calNumMonths: 2,
			departureDateOffset: 21,
			returnDateOffset: 7,
			noDefaultDates: false,
			defaultDatesOnEmptyOnly: false,
			locationSuggest: LocationSuggest,
			locationSuggestMax: false,
			submitHandler: null,
			submitTarget: "_top",
			keyPress: function(e) {
				var code = (window.event) ? e.keyCode : e.which;
				return (code != 13);
			}
		},
		prototype: {
			init: function() {
				this.elements = {};
				this.invalidElements = {};
				this.valid = true;
				this.submitting = false;
				this.isValidated = !this.currentForm.hasClass($.smartForm.constants.NOT_VALIDATED);
				var smartForm = this;

				var data_mode = $j(this.currentForm).attr('data-mode');
				this.mode = data_mode ? data_mode : this.id.split("_")[0];

				// if there are supplied message overrides apply them recursivly
				if (smartForm.settings.message_overrides) {
				    $.extend(true, smartForm.settings.messages, smartForm.settings.message_overrides);
				}

				// Special case for vacation rental form
				if (this.mode === "vacation" && this.id.split("_")[1] === "rental") {
					this.mode = "vacation_rental";
				}
				this.bbsearchmode = this.mode;
				// Special case for specific hotel check rates form
				if (/^hotelcheckrates/.test(this.bbsearchmode)) {
					this.bbsearchmode = "hotel";
				}
				// Check for a locale class, this overrides and options passed to the contructor
				if (this.currentForm.hasClass($.smartForm.constants.UK_LOCALE_CLASS)) {
					this.settings.locale = "UK";
					this.settings.dateFormat =  $.smartForm.dateFormats.UK;
				}

				// Initialize the smartElements
				this.currentForm.find("." + $.smartForm.constants.SMART_ELEMENT).each(function() {
					var element = new $.smartElement(this, smartForm);
					smartForm.elements[element.formContextId] = element;
				});

				BBDebug.log(this.id + ": Initialized all smart elements...");
				// Load current search data if this is a search form
				this.searchForm = this.currentForm.hasClass($.smartForm.constants.SEARCH_FORM);
				if (this.searchForm) {
					$.smartFormsBBSavedSearchData = BBSavedSearchData.singleton();
					this.bbSearchData = $.smartFormsBBSavedSearchData;

					if (!this.currentForm.hasClass($.smartForm.constants.DELAYED_LOAD)) {
						this.loadCurrentBBSearchData();
					} else {
						// avoid making lots of validation calls (if we can) by waiting till the form is displayed on the page
						this.currentForm.one('STMToggle:show', $.proxy(this.loadCurrentBBSearchData, this));
					}

					this.showRecentSearchData();
				}

				// Hook up any declared submit triggers
				$('#' + this.id + ' .submitTrigger').live('click', $.proxy(function(e) {
					this.submitTrigger = e.currentTarget;
					this.currentForm.submit();
					$(e.currentElement).blur();
					e.stopPropagation();
					e.preventDefault();
					return false;
				}, this));
			},

			loadRecentBBSearchData: function(searchIndex) {
				$.each(this.elements, $.proxy(function(id, element) {
					var val = this.bbSearchData.getRecentSearchData(element.name, this.mode, searchIndex);
					if (!$.isNullOrUndefined(val)) {
						element.value(val);
						element.updateCurrentSearchData();
						element.updateCommon();
					} else {
						element.clear();
					}
				}, this));
			},

			loadCurrentBBSearchData: function() {
				$.each(this.elements, $.proxy(function(id, element) {
					// if there is a Current Search value to populate in the form
					var val = this.bbSearchData.getCurrentSearchData(element.name, this.bbsearchmode);
					if (!$.emptyString(val) && !element.currentElement.hasClass('noSavedData')) {
						if (element.type !== "hidden") {
							BBDebug.log("Setting element value from current search:	 [" + this.bbsearchmode + "][" + element.name + "] = " + val );

							element.value(val);

							// Make sure the element has been given a valid location for the field
							if (element.isLocationSuggestElement) {
								element.locationSuggest.validate("quickcheck");
							}
						}
					// else, if there is no Current Search value to populate in the form,
					// update Current Search with the current form value so the two are in sync
					} else if (element.type !== 'hidden') {
						element.updateCurrentSearchData();
					}
				}, this));

				$.each(this.elements, $.proxy(function(id, element) {
					// Make sure element has a valid date
					if (element.isDateElement()) {
						element.dateUpdate();
					}
				}, this));

				BBDebug.log(this.id + ": Loaded current BBSearch data...");
			},

			showRecentSearchData: function() {
				var _rs_id = "#"+this.mode + "_" + $.smartForm.constants.RECENT_SEARCHES_DIV;
				if ($(_rs_id).length > 0) {
					$(_rs_id + " .rsList ul").remove();
					var _recentCt = this.bbSearchData.getRecentSearchCount(this.bbsearchmode);
					if (_recentCt > 0) {
						$(_rs_id).show();
						var _list = $("<ul />");
						var _rsClick = function(e) {
							var _this = e.currentTarget;
							// split this element's ID and take the third piece
							// (however, it may be the case that we need to take the fourth
							//   piece, if the search mode is vacation_rentals, which
							//   has an extra underscore)
							var pieces = $(_this).attr('id').split('_');
							var _index = !isNaN(parseInt(pieces[2], 10)) ? parseInt(pieces[2], 10) : parseInt(pieces[3], 10);

							// load the stored recent search data into the form
							this.loadRecentBBSearchData(_index);
							$.publish('/show/recentSearchData', [this]);

							e.stopPropagation();
							e.preventDefault();

							return false;
						};
						for(var i = 0; i < _recentCt ; i++) {
							var _li = $("<li />");
							$("<a />", {
								id: this.mode + "_rs_"+ i,
								click: $.proxy( _rsClick, this),
								text: this.bbSearchData.displayRecentSearch(this.bbsearchmode, i)
							}).appendTo(_li);
							_li.appendTo(_list);
						}
						$(_rs_id + " .rsList").append(_list);
						$(_rs_id).show();
					} else {
						$(_rs_id).hide();
					}
				}

			},

			reset: function() {
				this.clearErrors();
				this.clearValues();
				if (this.settings.submitHandler) {
					this.submitHandlerData = {};
				}
			},
			preValidate: function() {
				this.submit();
			},
			submit: function() {
				this.submitting = true;
				this.checkForm();
				this.submitting = false;

			},
			checkForm: function() {
				this.clearErrors();
				if (this.isValidated) {
					$.each(this.elements, $.proxy(function(id, elem) {
						if (elem.isValidated) {
							if (!elem.check()) {
								this.invalidElements[elem.formContextId] = elem;
								this.valid = false;
							}
						}
					}, this));
				}

				if (this.valid) {
					$.each( this.validators, $.proxy( function(index, validator) {
						if (!validator.doValidation(this)) {
							this.messages[ validator.name ] =  validator.message;
							this.valid = false;
						}

					}, this));
				}

				var event = this.valid ? "SmartForm:valid" : "SmartForm:invalid";
				this.currentForm.trigger(event);
				this.showErrors();
			},

			invalidateElement: function(elem, ruleId) {
				if ($.isString(elem)) {
					elem = this.getElementById(elem);
				}
				if (!$.isNullOrUndefined(elem)) {
					if (!$.isNullOrUndefined(ruleId)) {
						if (ruleId === "locationSuggest") {
							elem.locationSuggest.validate();
						} else {
							this.valid = false;
							var _rule = elem.getRule(ruleId);
							elem.valid = false;
							this.invalidElements[elem.formContextId] = elem;
							if (!$.isNullOrUndefined(_rule)) {
								elem.messages[ruleId] = _rule.getMessage(elem);
							} else {
								_msg = $j.tmpl(this.smartForm.settings.messages[ruleId], {field_name: elem.title});
								if (!$.isNullOrUndefined(_msg)) {
									elem.messages[ruleId] = _msg;
								}
							}
						}
					}
				}
				return elem;
			},

			hasErrors: function() {
				return !this.valid;
			},
			showErrors: function() {
				// attempt to look for error message containers located within
				// the form before looking for the specific error container id
				var $errorMessages = this.currentForm.find('.smartFormErrorMessages:first');
				if (!$errorMessages[0]) {
					$errorMessages = $("#" + this.id + "_errorMessages");
				}
				$errorMessages.hide();
				$errorMessages.find('ul').remove();
				if (!this.valid) {
					var _list = $("<ul />");

					$.each( this.invalidElements, function(id, elem) {
						if (elem) {
							elem.showError();
							$.each(elem.messages, function(id, msg) {
								if (!$.emptyString(msg)) {
									$("<li />", {
									id: elem.elementId + "_" + id + "_msg",
									text: msg
									}).appendTo(_list);
								}
							});
						}
					});

					$.each( this.messages, $.proxy( function(index, msg) {
						BBDebug.log(msg);
						if (!$.emptyString(msg)) {
							$("<li />", {
							text: msg
							}).appendTo(_list);
						}
					}, this));
					_list.appendTo($errorMessages);
					if ($errorMessages.find('ul li').length > 0) {
						$errorMessages.show();
					}


				}
			},
			clearErrors: function() {
				$.each( this.invalidElements, function(id, elem) {
					if (elem) {
						elem.clearError();
					}
				});
				this.invalidElements = {};
				this.messages = {};
				$("#"+this.id + "_errorMessages").hide();
				this.valid = true;

			},
			clearError: function(elem, ruleId) {
				$("#"+elem.elementId + "_" + ruleId + "_msg").remove();
				if ($("#"+this.id + "_errorMessages ul li").length < 1) {
					$("#"+this.id + "_errorMessages").hide();
				}
			},
			clearValues: function() {
				$.each( this.elements, function(id, elem) {
					elem.clear();
				});
			},
			getElement: function(elementContextId) {
				// Make sure there is a leading _
				elementContextId = /^_/.test(elementContextId) ? elementContextId : "_"+elementContextId;
				var _elem = this.elements[elementContextId];
				return _elem;
			},
			getElementById: function(elementId) {
				var _elem = null;
				var _finds = this.currentForm.find("#"+elementId);
				if (_finds.length > 0) {
					_elem =	 $.data(_finds[0], 'smartElement');
				}
				return _elem;
			},
			getElementByClass: function(elementClass) {
				var _elem = null;
				var _finds = this.currentForm.find("."+elementClass);
				if (_finds.length > 0) {
					_elem =	 $.data(_finds[0], 'smartElement');
				}
				return _elem;
			},
			getLSElements: function() {
				var _finds = this.currentForm.find(".locationSuggest");
				return _finds;
			},
			cleanForSubmit: function( ) {
				$.each( this.elements, function(id, elem) {
					if (!elem.isDateElement()) {
						var _val = elem.value();
						var _defVal =  elem.defaultVal;
						if (_val === _defVal) {
							$(elem.currentElement).val("");
						}
					}
				});
			},
			addValidator: function( formValidator ) {
				this.validators.push(formValidator);
			},
			isOneWaySearch: function() {
				return $("#" + this.mode + "_date2_block").togglerHidden();
			},
			debug: function() {

				var _status = this.hasErrors() ? "INVALID" : "VALID";

				BBDebug.group("Smart Form: " + this.currentForm.id + " : " + _status, true);
				BBDebug.dir(this);
				BBDebug.groupEnd();
				BBDebug.group(this.currentForm.id + " Elements: ", true);
				$.each(this.elements, function() {
					this.debug();
				});
				BBDebug.groupEnd();

			}

		}
	});

	// constructor for smartElement
	$.smartElement = function( element, smartForm ) {
		this.currentElement = $(element);
		this.smartForm = smartForm;
		this.elementId = this.currentElement.attr("id");
		this.name = this.currentElement.attr("name");
		this.title = this.currentElement.attr("title");

		this.type = this.currentElement[0].tagName.toLowerCase();
		if (this.type == "input") {
			var type = this.currentElement.attr("type");
			this.type = type ? type.toLowerCase() : "";
		}
		// Make sure context id starts with '_' to prevent unsafe id values (ie - 'length')
		var _contextId = "";
		if (this.elementId) {
			_contextId = this.elementId.substring(this.smartForm.mode.length);
		}
		this.formContextId = /^_/.test(_contextId) ? _contextId : "_"+_contextId;
		this.isValidated = !this.currentElement.hasClass($.smartForm.constants.NOT_VALIDATED);
		this.label = null;
		this.rules = [];
		this.ruleStatus = {};
		this.messages = {};
		this.init();
		if (this.rules.length > 0 && $.emptyString(this.title)) {
			BBDebug.log("Warning: " + this.elementId + " should have a 'title' set if it is going to display error messages, please fix");
		}
		$.data(element, 'smartElement', this);
	};

	$.extend($.smartElement, {

		prototype: {
			init: function() {

				this.valid = true;
				this.label = $j(this.currentElement[0].form).find("label[for=" + this.currentElement.attr("id") + "]");

				// Set default clear behavior
				if (this.currentElement.hasClass($.smartForm.constants.DEFAULT_CLEAR)){
					this.defaultVal = this.value();

					this.currentElement.focus( $.proxy( function() {
						if (this.value()===this.defaultVal) {
							this.value("");
						}
					}, this ) );

					this.currentElement.blur( $.proxy( function() {
						if (this.value()==='') {
							this.value(this.defaultVal);
						}
					}, this ) );
				}

				// Initialize location suggest
				if (this.currentElement.hasClass($.smartForm.constants.LS)) {
						var types = [];
						if (this.currentElement.hasClass($.smartForm.constants.LS_COUNTRY)) {
							types[types.length] = 'country';
						}
						if (this.currentElement.hasClass($.smartForm.constants.LS_REGION)) {
							types[types.length] = 'region';
						}
						if (this.currentElement.hasClass($.smartForm.constants.LS_STATE)) {
							types[types.length] = 'state';
						}
						if (this.currentElement.hasClass($.smartForm.constants.LS_CITY)) {
							types[types.length] = 'city';
						}
						if (this.currentElement.hasClass($.smartForm.constants.LS_AIRPORT)) {
							types[types.length] = 'airport';
						}

						// Clear the element title, it gets in the way of the suggestions
						this.currentElement.attr("title","");

						this.currentElement.attr("autocomplete", "off");
						this.locationSuggest = new LocationSuggest(this.elementId, types, this.elementId + "_hidden");
						if (this.smartForm.settings.locationSuggestMax) {
							this.locationSuggest.setMaxSuggestions(this.smartForm.settings.locationSuggestMax);
						}

						if (!$.emptyString(this.defaultVal)){
							this.locationSuggest.defaultText = this.defaultVal;
						}

						if (this.smartForm.isValidated) {
							this.invalidLocationDropDown = new InvalidLocationDropdown(this.elementId);
						}
						this.lsValidationCallbacks = [];

						this.currentElement.bind('LocationSuggest:valid', $.proxy( function() {
							BBDebug.log(" LocationSuggest:valid triggered on " + this.elementId);

							if (this.smartForm.isValidated) {
								this.invalidLocationDropDown.removeSelect();
							}
							this.currentElement.addClass($.smartForm.constants.LS_VALID).removeClass($.smartForm.constants.LS_INVALID);

							if (this.smartForm.searchForm) {
								this.updateCurrentSearchData();
							}
							this.updateCommon();
						}, this ));

						this.currentElement.bind('LocationSuggest:valid:quickcheck', $.proxy( function() {
							BBDebug.log(" LocationSuggest:valid:quickcheck triggered on " + this.elementId);

							if (this.smartForm.isValidated) {
								this.invalidLocationDropDown.removeSelect();
							}

							this.currentElement.addClass($.smartForm.constants.LS_VALID).removeClass($.smartForm.constants.LS_INVALID);

							//If location suggest fills up two cities with the same name this removes the departure city
							if (this.smartForm.elements._departure_city && this.smartForm.elements._arrival_city.currentElement.val() == this.smartForm.elements._departure_city.currentElement.val()) {
								//Directly access the value and change it to blank, blur the field to remove the text
								this.smartForm.elements._departure_city.currentElement.val("").blur();
								//Bypass annoying but useful restrictions on updating cookie
								this.smartForm.bbSearchData.addCurrentSearchData(this.smartForm.elements._departure_city.name, " ", this.smartForm.bbsearchmode);
							}
						}, this ));

						this.currentElement.bind('LocationSuggest:invalid', $.proxy( function(event, suggestions) {
							BBDebug.log(" LocationSuggest:invalid triggered on " + this.elementId);
							if (!$.emptyString(this.value())) {
								if (this.locationSuggest.airportsOnly ||
									this.currentElement.hasClass($.smartForm.constants.LS_UNSUPPORTED_LOCATION)) {
									this.currentElement.removeClass($.smartForm.constants.LS_VALID).addClass($.smartForm.constants.LS_INVALID);
								} else {
									this.currentElement.trigger('LocationSuggest:valid:quickcheck');
								}
							}
						}, this ));

						this.currentElement.bind('LocationSuggest:invalid:quickcheck', $.proxy( function(event, suggestions) {
							BBDebug.log(" LocationSuggest:invalid:quickcheck triggered on " + this.elementId);
							if (this.locationSuggest.airportsOnly ||
								this.currentElement.hasClass($.smartForm.constants.LS_UNSUPPORTED_LOCATION)) {
								this.currentElement.trigger('LocationSuggest:invalid',[suggestions]);
							} else {
								this.currentElement.trigger('LocationSuggest:valid:quickcheck');
							}
						}, this ));
						this.isLocationSuggestElement = true;
				}

				// Set onchange method for common elements
				if (this.currentElement.hasClass($.smartForm.constants.COMMON)){
					if (!$.commonInputsMap[this.formContextId]) {
						$.commonInputsMap[this.formContextId] = [];
					}
					$.commonInputsMap[this.formContextId].push(this.elementId);
					this.currentElement.blur( $.proxy( function() {
						if (!this.isLocationSuggestElement && this.check()) {
							this.updateCommon();
						}
					}, this ) );
				}

				// Set onchange method for search form elements
				this.currentElement.change( $.proxy( function() {
					if (this.smartForm.searchForm) {
						// always update the Current Search data on change
						this.updateCurrentSearchData();
					}
				}, this ) );

				// Initialize calendars and date behaviors
				var _date = new Date();

				// Set the default val before the calendar wipes it
				if (this.isDateElement()) {
					this.defaultVal = this.value();
				}

				if (this.currentElement.hasClass($.smartForm.constants.CALENDAR)) {
					this.attachCalendar();
				}

				if (this.currentElement.hasClass($.smartForm.constants.DEPARTURE_DATE)) {
					this.currentElement.change( $.proxy( function() {
						this.dateUpdate();
					}, this ) );

					if (this.smartForm.settings.noDefaultDates ||
						(this.smartForm.settings.defaultDatesOnEmptyOnly && (this.defaultVal !== 'mm/dd/yyyy'))) {
						this.currentElement.val(this.defaultVal);
					} else {
						_date.setDate(_date.getDate()+this.smartForm.settings.departureDateOffset);
						this.setElementFromDate(_date);
					}
					this.defaultVal = this.value();
				}
				else if (this.currentElement.hasClass($.smartForm.constants.RETURN_DATE)) {
					if (this.smartForm.settings.noDefaultDates ||
						(this.smartForm.settings.defaultDatesOnEmptyOnly && (this.defaultVal !== 'mm/dd/yyyy'))) {
						this.currentElement.val(this.defaultVal);
					} else {
						_date.setDate(_date.getDate()+this.smartForm.settings.departureDateOffset+this.smartForm.settings.returnDateOffset);
						this.setElementFromDate(_date);
					}
					this.defaultVal = this.value();
				}

				// Attach validators
				if (this.isValidated) {
					if (this.currentElement.hasClass($.smartForm.constants.REQUIRED)) {
						this.rules.push($.smartValidators.required);
					}

					if (this.currentElement.hasClass($.smartForm.constants.ZIP)) {
						this.rules.push($.smartValidators.zip);
					}

					if (this.currentElement.hasClass($.smartForm.constants.EMAIL)) {
						this.rules.push($.smartValidators.email);
					}

					if (this.currentElement.hasClass($.smartForm.constants.EMAIL_VERIFY)) {
						this.rules.push($.smartValidators.emailVerify);
					}

					if (this.currentElement.hasClass($.smartForm.constants.DATE)) {
						this.rules.push($.smartValidators.date);
					}

					if (this.currentElement.hasClass($.smartForm.constants.DEPARTURE_DATE)) {
						this.rules.push($.smartValidators.date);
						this.rules.push($.smartValidators.dateNotPast);
					}

					if (this.currentElement.hasClass($.smartForm.constants.RETURN_DATE)) {
						this.rules.push($.smartValidators.date);
						this.rules.push($.smartValidators.dateNotPast);
						this.rules.push($.smartValidators.returnDate);
					}

					if (this.currentElement.hasClass($.smartForm.constants.LS)) {
						this.rules.push($.smartValidators.locationSuggest);
					}
					if (this.formContextId === $.smartForm.constants.ARRIVAL_CITY_CONTEXT_ID) {
						this.rules.push($.smartValidators.arrivalCity);
					}
				}
			},
			getRule: function( ruleId ) {
				var _rule = null;
				$.each( this.rules, function() {
					if (this.name === ruleId) {
						_rule = this;
						return false;
					}
				});
				return _rule;
			},
			value: function( val ) {
				if ($.isNullOrUndefined(val)) {
					return this.currentElement.val();

				}

				if (this.isDateElement()){
					if (!isNaN(val)) {
						this.setElementFromTimeStamp(val);
					} else {
						var realDate = false;
						// Make sure the string can be parsed into a proper date for the current locale
						var datePart = val.split('/');
						if (datePart.length == 3) {
							var month = datePart[0];
							var day = datePart[1];
							var year = datePart[2];
							if (this.smartForm.settings.locale === "UK") {
								day = datePart[0];
								month = datePart[1];
							}
							month = parseInt(month, 10)-1;
							var date = new Date(year, month, day);
							if ( Object.prototype.toString.call(date) === "[object Date]" ) {
								if ( !isNaN( date.getTime() ) ) {
									realDate = true;
									// Get the current date at midnight
									var now = new Date();
									now.setHours(0,0,0,0);

									// Verify the date is not in the past or too far in the future
									if (date < now || date.getFullYear() > now.getFullYear() + 2) {
										BBDebug.log("Date out of range in date element value() call: " + val);
									} else {
										this.setElementFromDate(date);
									}
								}
							}

						}
						if (!realDate) {
							BBDebug.log("Bad date string passed in date element value() call: " + val);
						}

					}
				} else {
					switch(this.type) {
					case 'select':
						this.currentElement.find("option").each( function() { $().attr("selected", false); });
						var _opt = this.currentElement.find("option[value='"+val+"']");
						if (_opt.length > 0) {
							_opt.attr("selected", true);
							this.currentElement.val(val);

						}
						break;
					case 'checkbox':
						if (this.currentElement.val() == val) {
							this.currentElement.click();
							this.currentElement[0].checked = true;
						} else {
							this.currentElement[0].checked = false;
						}
						break;
					case 'radio':
						if (this.currentElement.val() == val) {
							this.currentElement.click();
							this.currentElement[0].checked = true;
						} else {
							this.currentElement[0].checked = false;
						}
						break;
					default:
						if (this.isLocationSuggestElement){
							val = this.cleanLSValue(val);
						}

						this.currentElement.val(val);
						break;
					}
				}

				return this.currentElement.val();
			},

			check: function( ) {
				var _smartElement = this;
				this.valid = true;
				$.each( this.rules, function() {
					if (_smartElement.valid) {
						var _valid = this.apply(_smartElement);
						var _ruleId = this.name;
						_smartElement.ruleStatus[_ruleId] = _valid;
						if (!_valid) {
							_smartElement.messages[_ruleId] = this.getMessage(_smartElement);
							_smartElement.valid = false;
						} else {
							_smartElement.messages[_ruleId] = null;
						}
					}
				});
				if (this.valid) {
					this.clearError();
				}
				return this.valid;
			},

			showError: function() {
				if (!this.valid) {
					this.currentElement.addClass($.smartForm.constants.INVALID);
					if (this.label) {
						$(this.label).addClass($.smartForm.constants.INVALID);
						var _title = "";
						var _sep ="";
						$.each(this.messages, function(rule, msg) {
							if (!$.emptyString(msg)) {
								_title += _sep + msg;
								_sep = " -- ";
							}
						});
						$(this.label).attr("title", _title);
					}
					if (this.locationSuggest && !this.ruleStatus.locationSuggest) {
						$(this.element).addClass($.smartForm.constants.LS_INVALID);
						if (!$.isNullOrUndefined(this.invalidLocationDropDown.select)) {
							$(this.invalidLocationDropDown.select).show();
						}
					}
					if (this.isDateElement() && $("#"+this.elementId + "_icon_div").length > 0) {
						$("#"+this.elementId + "_icon_div").addClass($.smartForm.constants.INVALID);
					}
				}
			},
			clearError: function() {
				if (!this.locationSuggest || !this.currentElement.hasClass($.smartForm.constants.LS_INVALID)) {
					this.valid = true;
					$.each(this.messages, $.proxy(function(id, msg) {
						this.smartForm.clearError(this, id);
					}, this));
					this.messages = {};
					$.each(this.ruleStatus, $.proxy(function(id, status ) {
						this.ruleStatus[id] = true;
					}, this));
					this.smartForm.invalidElements[this.formContextId] = null;
					this.currentElement.removeClass($.smartForm.constants.LS_UNSUPPORTED_LOCATION);
					this.currentElement.removeClass($.smartForm.constants.INVALID);
					if (this.label) {
						$(this.label).removeClass($.smartForm.constants.INVALID);
						$(this.label).attr("title", "");
					}
					if (this.isDateElement() && $("#"+this.elementId + "_icon_div").length > 0) {
						$("#"+this.elementId + "_icon_div").removeClass($.smartForm.constants.INVALID);
					}
				}
			},
			clear: function() {
				switch(this.type) {
					case 'select':
						var _opts = this.currentElement.find("option");
						if (_opts.length > 0) {
							_opts.attr("selected", false);
							$(_opts.get(0)).attr("selected", true);
							this.currentElement.val($(_opts.get(0)).attr("value"));
						}
						break;
					case 'checkbox':
						this.currentElement.attr("checked", false);
						break;
					case 'radio':
						this.currentElement.attr("checked", false);
						break;
					default:
						this.value(this.defaultVal ? this.defaultVal : '');
						break;
				}

			},
			debug: function() {
				var _str = "ID: " + this.elementId + " / NAME: " + this.name + " = " + this.value();
				if (this.currentElement.attr("type") == "checkbox" || this.currentElement.attr("type") == "radio") {
					_str += " - " + this.currentElement.attr("checked");
				}
				BBDebug.log(_str);
			},
			updateCommon: function() {
				if (!this.currentElement.hasClass($.smartForm.constants.COMMON)){
					return;
				}

				if (this.value() === this.defaultVal) {
					return;
				}

				var _elementId = this.elementId;
				var _fromCheckRatesForm =  /^hotelcheckrates/.test(this.smartForm.mode);
				$.each($.commonInputsMap[this.formContextId], $.proxy(function(index, elementId) {
					if (elementId == _elementId) {
						return true;
					}

					var _val = this.value();
					var _elem = $("#"+elementId).get(0);
					if (_elem && $(_elem).hasClass($.smartForm.constants.COMMON)) {
						var _smartElement = $.data(_elem, "smartElement");
						if (!$.emptyString(_val)) {
							// Don't update from a hotel check rates form to a bbsearch form
							// TODO: See if this check actually does anything. It only evals to false
							// for check rates forms that have a bbSearch class
							if (!_fromCheckRatesForm || !$(_smartElement.smartForm.currentForm).hasClass("bbSearch")) {
								if (_smartElement.isLocationSuggestElement){
									_val = _smartElement.cleanLSValue(_val);
								}
								BBDebug.log("Setting common values for " + _smartElement.smartForm.id + "::" +_smartElement.formContextId + " - " + _val);

								$(_smartElement.currentElement).val(_val);
								// Make sure the element has been given a valid location for the field
								if (_smartElement.isLocationSuggestElement) {
									_smartElement.locationSuggest.validate("quickcheck");
								}

								$(_smartElement.currentElement).trigger("commonInput:Updated");
							}
						} else {
							BBDebug.log("Not moving common value from " + this.smartForm.id + "::" + this.formContextId + " to incompatible field " + _smartElement.smartForm.id + "::" +_smartElement.formContextId);
						}
					}


					return true;
				}, this));
			},
			cleanLSValue: function( val ) {
				var _val = val;
				// Clean value for location suggest field if necessary (i.e. the location suggest does lookup airports)
				if (this.isLocationSuggestElement){
					if (this.locationSuggest.citiesOnly) {
						_val = this.locationSuggest.convertAirportToCity(_val);
					}
				}
				return _val;
			},
			updateCurrentSearchData: function() {
				if (this.smartForm.searchForm) {

					if (this.check()) {
						var _value = '';
						if (this.isDateElement()) {
							_value = this.getTimeStampFromElement();
						} else {
							switch(this.type) {
								case 'checkbox':
									if (this.currentElement.attr("checked")) {
										_value = this.value();
									}
									break;
								case 'radio':
									if (this.currentElement.attr("checked")) {
										_value = this.value();
									}
									else {
										_value = null;
									}
									break;
								case 'select':
									var selectedOption = this.currentElement.children(':selected');
									if (selectedOption.hasClass('storeText')) {
										var trimmed = selectedOption.text().replace(/^\-*\s*/, '');
										_value = trimmed;
									} else {
										_value = this.value();
									}
									break;
								default:
									_value = this.value();
									break;
							}
						}
						if (!$.isNull(_value)) {
							BBDebug.log("Setting current search value ["+this.smartForm.bbsearchmode+"][" + this.name + "] to " + _value + " -- " + this.value());
							this.smartForm.bbSearchData.addCurrentSearchData(this.name, _value, this.smartForm.bbsearchmode);

						}
					}
				}
			},
			isDateElement: function () {
				return this.currentElement.hasClass($.smartForm.constants.DATE) ||
					   this.currentElement.hasClass($.smartForm.constants.DEPARTURE_DATE) ||
					   this.currentElement.hasClass($.smartForm.constants.RETURN_DATE) ;
			},
			attachCalendar: function() {
				var element = $('#' + this.elementId);
				// disable silly events
				element.bind('cut copy paste keypress', function(e) {
					// allow people to tab to the next field in FireFox (TAB == keyCode 9)
 					if ( e.type == "keypress" && e.keyCode == 9) {
						return true;
					}

					e.stopPropagation();
					e.preventDefault();
					return false;
				});

				// blur on click
				element.bind('click', function(e) { this.blur(); });

				// Settings for date calendar
				element.datepicker({
					// Alters week days display
					dayNamesMin: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],

					// set min date (first day allowed) to be today
					minDate: new Date(),

					// before showing the the retun date calendar
					beforeShow: $.proxy(function(input, inst) {
						var $input = $(input);
						if ($input.hasClass($.smartForm.constants.RETURN_DATE)) {
							// get the date value from the departure date calendar
							// and set its minDate to the departure date selected
							var departureDateElement = this.smartForm.getElementByClass($.smartForm.constants.DEPARTURE_DATE).currentElement;

							// Record and re-set the current value in the date field because
							// the call to update the minDate will change the date field's
							// value to the min date if it doesn't validate
							var currentVal = this.value();
							$input.datepicker('option', 'minDate', $(departureDateElement).datepicker('getDate'));
							$input.val(currentVal);
						}
					}, this),

					onClose: function(dateText, inst) {
						element.blur();
					}

				});

				// Date setting for UK - DD/MM/YY
				if (this.smartForm.settings.locale === 'UK') {
					element.datepicker('option', 'dateFormat', 'dd/mm/yy');
				}

				return element;
			},

			getDateString: function (date) {
				var day = [];
				var month = [];
				var ret = [];

				month[0] = "Jan";
				month[1] = "Feb";
				month[2] = "Mar";
				month[3] = "Apr";
				month[4] = "May";
				month[5] = "Jun";
				month[6] = "Jul";
				month[7] = "Aug";
				month[8] = "Sept";
				month[9] = "Oct";
				month[10] = "Nov";
				month[11] = "Dec";

				day[0] = "Sun";
				day[1] = "Mon";
				day[2] = "Tue";
				day[3] = "Wed";
				day[4] = "Thu";
				day[5] = "Fri";
				day[6] = "Sat";

				ret[0] = day[date.getDay()];
				ret[1] = month[date.getMonth()];
				ret[2] = date.getDate();

				return ret.join(' ');
			},

			setElementFromDate: function( date ) {
				if (this.isDateElement()) {
					if (this.currentElement.hasClass($j.smartForm.constants.PRINT_DATE_TO_TEXT)) {
						this.currentElement.text(this.getDateString(date));
					} else {
						var _month = date.getMonth()+1;
						if (_month < 10 ) { _month =  '0' + _month; }

						var _day = date.getDate();
						if (_day < 10 ) { _day =  '0' + _day; }

						var _year = date.getFullYear();
						var _date_str = _month + "/" + _day + "/" + _year;

						if (this.smartForm.settings.locale === "UK") {
							_date_str = _day + "/" + _month + "/" + _year;
						}

						if (!(/Invalid|NaN/).test(_date_str)) {
							this.currentElement.val(_date_str);
						}
					}
				}
			},
			setElementFromTimeStamp: function( timestamp ) {
				if (this.isDateElement()) {
					var date = new Date(timestamp);
					var realDate = false;
					if ( Object.prototype.toString.call(date) === "[object Date]" ) {
						if ( !isNaN( date.getTime()) ) {
							realDate = true;
							// Get the current date at midnight
							var now = new Date();
							now.setHours(0,0,0,0);
							// Verify the date is not before today or too far in the future
							if (date < now || date.getFullYear() > now.getFullYear() + 2) {
								BBDebug.log("Date out of range in date element value() call: " + timestamp);
							} else {
								this.setElementFromDate(date);
							}
						}
					}
					if (!realDate) {
						BBDebug.log("Invalid timestamp in date date element setElementFromTimeStamp() call: " + timestamp);
					}

				}
			},
			getDateFromElement: function(  ) {
				if (this.isDateElement()) {
					var _value = this.value();
					var _datePart = _value.split('/');

					var _month = _datePart[0];
					var _day = _datePart[1];
					var _year = _datePart[2];
					if (this.smartForm.settings.locale === "UK") {
						_day = _datePart[0];
						_month = _datePart[1];
					}

					_month = parseInt(_month, 10)-1;

					return new Date(_year, _month, _day);
				}
				return null;
			},
			getTimeStampFromElement: function() {
				if (this.isDateElement()) {
					var _date = this.getDateFromElement();
					return _date.getTime();
				}
				return null;
			},

			dateUpdate: function() {
				// Check to see if departure date is now after return date
				var returnDateElement = this.smartForm.getElementByClass($.smartForm.constants.RETURN_DATE);
				var departureDateElement = this.smartForm.getElementByClass($.smartForm.constants.DEPARTURE_DATE);
				if (returnDateElement && departureDateElement) {
					// Reset departure date to the default if it is invalid
					if (!departureDateElement.check()) {
						departureDateElement.value(departureDateElement.defaultVal);
					}

					var departDate = departureDateElement.getDateFromElement();
					var returnDate = returnDateElement.getDateFromElement();

					// If so, add default amount to return date and call updateCommon for return date.
					// Also check return date in the current state data to prevent potentially
					// overwriting valid saved search data
					if (($.isNullOrUndefined(returnDate) || departDate > returnDate)) {
						returnDate = departDate;
						returnDate.setDate(departDate.getDate()+this.smartForm.settings.returnDateOffset);
						returnDateElement.value(returnDate);
						returnDateElement.currentElement.change();

						returnDateElement.updateCommon();
						returnDateElement.updateCurrentSearchData();
					}
				}
			}
		}
	});


	// Validators
	$.smartValidator = {
		name: "base validator",
		doValidation: function() { },
		getMessage: function(element) {
			return $j.tmpl(element.smartForm.settings.messages[this.name], {field_name: element.title.toLowerCase()});
		},
		message: null
	};
	$.smartElementValidator = $.extend($.smartValidator, {
			apply: function( element ) {
				return this.doValidation(element);
			},
			getFormElementValue: function(smartElement) {
				var _element = smartElement.currentElement;
				return $(_element).val();
			}
	});
	$.smartValidators = {
			required: $.extend({}, $.smartElementValidator, {
				name: "required",
				doValidation: function(element) {
					var _valid = true;
					if (element.smartForm.submitting) {
						if ($(element.currentElement).attr("type") == "checkbox") {
							_valid = $(element.currentElement).attr("checked");
						} else {
							var _value = this.getFormElementValue(element);
							_valid = $.trim(_value).length > 0;
						}
					}
					return _valid;
				},
				getMessage: function(element) {
					var _msg = "";
					if ((element.smartForm.mode === "air"|| element.smartForm.mode === "vacation") && element.isLocationSuggestElement) {
						var _msgKey = "ls_required";
						if (element.formContextId === $.smartForm.constants.DEPARTURE_CITY_CONTEXT_ID  && $(element.currentElement).hasClass("airport") ) {
							_msgKey = "ls_departure_required";
						} else if (element.formContextId === $.smartForm.constants.ARRIVAL_CITY_CONTEXT_ID  && $(element.currentElement).hasClass("airport") ) {
							_msgKey = "ls_arrival_required";
						}
						_msg = $.smartValidators.locationSuggest.getLSMessage(element, _msgKey);
					} else if ( (element.smartForm.mode === "car" || element.smartForm.mode === "hotel" ) && element.formContextId === $.smartForm.constants.ARRIVAL_CITY_CONTEXT_ID) {
						_msg = $j.tmpl(element.smartForm.settings.messages['required'], {field_name: element.title});
					} else if (element.smartForm.mode === "vacation_rental" && element.formContextId === $.smartForm.constants.ARRIVAL_CITY_CONTEXT_ID) {
						_msg = $j.tmpl(element.smartForm.settings.messages['required'], {field_name: element.title});
					} else if (element.smartForm.mode === "holiday") {
						if (element.formContextId === $.smartForm.constants.DEPARTURE_CITY_CONTEXT_ID) {
							_msg = $j.tmpl(element.smartForm.settings.messages['holiday_departure_required'], {field_name: element.title});
						} else if (element.formContextId === $.smartForm.constants.ARRIVAL_CITY_CONTEXT_ID) {
							_msg = $j.tmpl(element.smartForm.settings.messages['holiday_arrival_required'], {field_name: element.title});
						}
					} else {
						_msg = $j.tmpl(element.smartForm.settings.messages[this.name], {field_name: element.title.toLowerCase()});
					}
					return _msg;
				}

			}),
			zip: $.extend({}, $.smartElementValidator, {
				name: "zip",
				doValidation: function(element) {
					var _valid = true;
					var _value = this.getFormElementValue(element);
					if (!$.emptyString(_value)) {
						var _regex = /(^\d{5})(-\d{4})?|(^[a-zA-Z][0-9][a-zA-Z] ?[0-9][a-zA-Z][0-9])$/;
						_valid = _regex.test(_value);
					}
					return _valid;
				}
			}),
			email: $.extend({}, $.smartElementValidator, {
				name: "email",
				doValidation: function(element) {
					var _valid = true;
					var _value = this.getFormElementValue(element);
					if (!$.emptyString(_value)) {
						var _regex=(/[A-Za-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[A-Za-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?\.)+(?:[A-Za-z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)/);
						_valid = _regex.test(_value);
					}
					return _valid;
				}
			}),
			emailVerify: $.extend({}, $.smartElementValidator, {
				name: "emailVerify",
				doValidation: function(element) {
					var _valid = true;
					var _value = this.getFormElementValue(element);
					var _matchElement = $("#" + element.elementId + "_verify");
					var _value2 = _matchElement.val();
					_valid = _value.toLowerCase() == _value2.toLowerCase();
					return _valid;
				}
			}),
			date: $.extend({}, $.smartElementValidator, {
				name: "date",
				doValidation: function(element) {
					var _value = this.getFormElementValue(element);
					var _valid = true;
					if (_value) {
						var _regex = element.smartForm.settings.dateFormat;
						_valid = _regex.test(_value);
					}
					return _valid;
			}
			}),
			dateNotPast: $.extend({}, $.smartElementValidator, {
				name: "dateNotPast",
				doValidation: function(element) {
					var _value = this.getFormElementValue(element);
					var _valid = true;
					if ( ! (element.smartForm.isOneWaySearch() && $(element.currentElement).hasClass($.smartForm.constants.RETURN_DATE) ) ) {
						if (_value) {
							// Check to see that the date is >= today
							var _now = new Date();
							_now.setHours(0,0,0,0);
							var _date = new Date(_value);
							// Make sure the date is formatted properly for the UK
							if (element.smartForm.settings.locale === 'UK') {
								var _datePart = _value.split('/');
								_date.setFullYear(_datePart[2], _datePart[1]-1, _datePart[0]);
							}
							_valid = _now <= _date;
						}
					}
					return _valid;
				}
			}),
			departureDate: $.extend({}, $.smartElementValidator, {
				name: "departureDate",
				doValidation: function(element) {
					var _value = this.getFormElementValue(element);
					var _valid = true;
					if (!element.smartForm.isOneWaySearch()) {
						if (!$.emptyString(_value)) {
							//  Check to see that the date is <= returnDate (if returnDate exists);
							var _returnElem = element.smartForm.getElementByClass($.smartForm.constants.RETURN_DATE);
							if (_returnElem) {
								var _departDate = element.getDateFromElement();
								var _returnDate = _returnElem.getDateFromElement();
								if (!$.isNullOrUndefined(_returnDate)) {
									_valid =  _departDate <= _returnDate;
								}
							}
						}
					}
					return _valid;
				}
			}),
			returnDate: $.extend({}, $.smartElementValidator, {
				name: "returnDate",
				doValidation: function(element) {
					var _value = this.getFormElementValue(element);
					var _valid = true;
					if (!element.smartForm.isOneWaySearch()) {
						if (!$.emptyString(_value)) {
							// Check to see that the date is >= departureDate (if departureDate exists);
							var _departElem = element.smartForm.getElementByClass($.smartForm.constants.DEPARTURE_DATE);
							if (_departElem) {
								var _returnDate = element.getDateFromElement();
								var _departDate = _departElem.getDateFromElement();
								if (!$.isNullOrUndefined(_departDate)) {
									_valid = _returnDate >= _departDate;
								}
							}
						}
					}
					return _valid;
				},
				getMessage: function(element) {
					var _departElem = element.smartForm.getElementByClass($.smartForm.constants.DEPARTURE_DATE);
					return $j.tmpl(element.smartForm.settings.messages[this.name], {field_name_1: element.title.toLowerCase(), field_name_2: _departElem.title.toLowerCase()});
				}

			}),
			locationSuggest: $.extend({}, $.smartElementValidator, {
				name: "locationSuggest",
				doValidation: function(element) {
					var _valid = true;
					if (element.smartForm.submitting) {
						_valid = !$(element.currentElement).hasClass($.smartForm.constants.LS_INVALID);
						var _value = this.getFormElementValue(element);
						if (_valid && _value.length  < 2) {
							_valid = false;
							$(element.currentElement).addClass($.smartForm.constants.LS_INVALID);
						}
					}
					return _valid;
				},
				getMessage: function(element) {
					var msgKey = "ls_invalid";
					if (!$.isNullOrUndefined(element.invalidLocationDropDown.select)) {
						msgKey = "ls_multiple_suggestions";
					}

					if (element.currentElement.hasClass($.smartForm.constants.LS_UNSUPPORTED_LOCATION)) {
						msgKey = "ls_unsupported_location";
					}

					return this.getLSMessage(element, msgKey);
				},
				getLSMessage: function(element, msgKey) {
				        var _msg = '';
					switch(msgKey) {
						case "required":
							_msg = $j.tmpl(element.smartForm.settings.messages[this.name], {field_name: element.title.toLowerCase()});
							break;
						case "ls_multiple_suggestions":
						case "ls_departure_required":
						case "ls_arrival_required":
						case "ls_invalid":
						default:
							var _loc_type = "location";
							if ($(element.currentElement).hasClass("airport") && !$(element.currentElement).hasClass("city")) {
								_loc_type = "airport";
							} else if (!$(element.currentElement).hasClass("airport") && $(element.currentElement).hasClass("city")) {
								_loc_type = "city";
							}
							var data = {
								location_type: _loc_type,
								field_name: element.title.toLowerCase(),
								location: element.currentElement.val()
							};
							_msg = $j.tmpl(element.smartForm.settings.messages[msgKey], data);
							break;
					}
					return _msg;
				}
			}),
			arrivalCity: $.extend({}, $.smartElementValidator, {
				name: "arrivalCity",
				doValidation: function(element) {
					var _valid = true;
					var _departElem = element.smartForm.getElement($.smartForm.constants.DEPARTURE_CITY_CONTEXT_ID);
					if (!$.isNullOrUndefined(_departElem) && !$.emptyString(this.getFormElementValue(element))) {
						var _arrive = this.getFormElementValue(element);
						var _depart = this.getFormElementValue(_departElem);
						_valid= _arrive !== _depart;
					}
					return _valid;
				},
				getMessage: function(element) {
					var _departElem = element.smartForm.getElement($.smartForm.constants.DEPARTURE_CITY_CONTEXT_ID);
					return $j.tmpl(element.smartForm.settings.messages[this.name], {field_name_1: _departElem.title.toLowerCase(), field_name_2: element.title.toLowerCase()});
				}
			})


	};


})(jQuery);

/**
 * Draw a google map with optional markers and info screens
 * 
 * Usage:
 * - To set initial options on the google map, pass an options object with
 * 	supported properties found here:
 * 	http://code.google.com/apis/maps/documentation/javascript/reference.html#MapOptions
 *
 *  { zoom: 10, disableDefaultUI: true, draggable: false; scrollwheel: false }
 *
 * - To add event handlers to the map and info window, pass an events object specifying
 *	which element the event belongs to, the event and handler. Regular dom events are
 *	supported for the info window. Supported map events can be found under "Events" here:
 *	http://code.google.com/apis/maps/documentation/javascript/reference.html#Map	
 *
 *	NOTE: All handlers will be executed in the context of this class. Marker events
 *	are added based on the marker type or can be added for each individual marker.
 *
 *	{
 *		map: {
 *			bounds_changed: function() { alert('map center point changed');  },
 *			dblclick: function() { this.resize(5); }
 *		},
 *		infoWindow: {
 *			blur: function() { this.blur(); },
 *			click: function() { this.showMarkersByType('all'); }
 *		}
 *	}
 */
(function($) {

	/**
	 * Create a googleMap object and store it as data of this jQuery element
	 *
	 * @param object coords - latitude and longitude of the map center point
	 * @param object options - map options to pass directly to the google map constructor
	 * @param object events - event types and handlers for the map and info window
	 */
	$.fn.googleMapFactory = function(coords, options, events) {

		if (!this.length) {
			return null;
		}

		var map = $.data(this[0], 'googleMap');
		if (map) {
			return map;
		}

		if ($.isNullOrUndefined(coords)) {
			coords = { latitude: 0, longitude: 0 };
		}

		map = new GoogleMap(this[0], coords, options, events);
		$.data(this[0], 'googleMap', map);

		map.element.trigger(GoogleMap.constants.EVENT_READY);

		return map;
	};

	/**
	 * Constructor
	 */
	GoogleMap = function(element, coords, options, events) {

		this.element = $(element);

		// map of markers indexed by marker id
		this.markers = {};
		// map of marker ids indexed by marker type and subtype
		this.markersByType = {};

		// marker currently being focused
		this.currentMarker = null;
		// marker ids that are currently visible; ids are stored
		// as keys to ensure there are no duplicates
		this.visibleMarkers = {};



		// the delay in ms before an info window is made visible
		// after the show event is triggered
		this.popupDelay = 250;
		// the setTimeout object used for delaying the info window popup
		this.popupTimeout = null;

		// create a custom overlay to be able to access a MapCanvasProjection
		this.overlay = null;

		// initialize the center point and map
		this.coords = coords;
		var center = new google.maps.LatLng(this.coords.latitude, this.coords.longitude);

		this.options = $.extend({}, GoogleMap.defaults, options, {center: center});

		/**
		 * Create the google map display. This will actually load the map
		 * and display it on the screen.
		 *
		 * NOTE: The element in which this is loaded cannot be hidden.
		 * Otherwise, the map will not know what width/height to use
		 * and you'll get a blank area for part of the map
		 */
		this.map = new google.maps.Map(this.element[0], this.options);	

		/**
		 * Create a custom overlay for the map. This is necessary to be able to
		 * convert latitude/longitude values into pixel offsets on the map and
		 * create info windows "outside" the map. Otherwise, all info windows would
		 * need to be bound within the map area. Nothing will actually be added
		 * directly to this new overlay.
		 */
		var STMOverlay = function() {};
		STMOverlay.prototype = google.maps.OverlayView.prototype;
		STMOverlay.prototype.classname = 'STMOverlay';
		STMOverlay.prototype.draw = function () {};
		STMOverlay.prototype.onAdd = function () {};
		STMOverlay.prototype.onRemove = function () {};

		this.overlay = new STMOverlay();
		this.overlay.setMap(this.map);

		// Handle setting up any events that should be triggered on the map
		// or the info window.  The context of all handlers is this map class
		if (events) {
			var self = this;
			if (events.map) {
				$.each(events.map, function(event, handler) {
					google.maps.event.addListener(self.map, event,function() {
						$.proxy(handler, self)();
					});
				});	
			}
		}
	};

	$.extend(GoogleMap, {

		/**
		 * constants on the global GoogleMap object
		 */
		constants: {
			EVENT_READY: 'GoogleMap:ready',
			EVENT_BLUR: 'GoogleMap:blur',
			EVENT_LOADING: 'GoogleMap:loading',
			EVENT_LOADED: 'GoogleMap:loaded',
			EVENT_SCRIPT_LOADED: 'GoogleMap:script_loaded',

			CLASS_MAIN: 'google_map_display',
			CLASS_INFO_WINDOW: 'map_info_window',

			ZOOM_CITY: 13,
			ZOOM_STATE: 6
		},

		/**
		 * pool of marker types on the global GoogleMap object
		 */
		markerTypes: {},

		/**
		 * default values for options on the global GoogleMap object
		 */
		defaults: {
			// ideally, this would use the commented var, but because
			// it isn't guaranteed the google maps api will be loaded
			// before this executes, the value is hard-coded
			mapTypeId: 'roadmap', //google.maps.MapTypeId.ROADMAP,
			zoom: 6
		},
		
		/**
		 * Instance methods
		 */
		prototype: {

			/**
			 * Attach a marker to the map with the specified configurations. This will
			 * only initialize the marker, not show it on the map
			 *
			 * @param string id - unique identifier for the marker
			 * @param object typeData - the type and optional sub type of the marker
			 * @param object config - configuration data specific to this marker
			 * @param object options - marker options to set on this marker that
			 *		will be extended to options on the specified marker type
			 */
			addMarker: function(id, typeData, config, options) {
				var self = this;

				// the coordinates are required for displaying the marker
				if (!config || !config.coords) {
					return;
				}

				// verify the marker has not aleady been added
				if (!$.isUndefined(self.markers[id])) {
					return;
				}

				var position = new google.maps.LatLng(config.coords.latitude, config.coords.longitude);
				// add the coordinates to the marker options
				options.position = position;

				var type = typeData.type;
				var subType = typeData.subType;
				if ($.isUndefined(subType)) {
					subType = '_undefined';
				}

				// add any marker options that exist on the marker type
				var markerType = GoogleMap.MarkerType.singleton(type);
				$.extend(options, markerType.getOptions());

				var googleMarker = new google.maps.Marker(options);	

				var marker = {
					id: id,
					type: type,
					googleMarker: googleMarker
				};

				self.markers[id] = marker;

				// add the marker type event listeners to the marker
				$.each(markerType.getTriggerEvents(), function(event, handler) {
					google.maps.event.addListener(googleMarker, event, function() {
						handler(self, marker, config);
					});
				});


				// store a mapping of marker ids in a friendly manner for accessing
				// by filters later
				if ($.isUndefined(self.markersByType[type])){
					self.markersByType[type] = {};
				}

				if ($.isUndefined(self.markersByType[type][subType])) {
					self.markersByType[type][subType] = [];
				}

				self.markersByType[type][subType].push(id);
			},

			/**
			 * Hide the specified marker
			 *
			 * @param string id - identifier for the marker
			 * @param bool show - flag to show the marker
			 */
			displayMarker: function(id, show) {

				var mark = this.markers[id];

				if (!$.isUndefined(mark)) {

					if (show) {
						mark.googleMarker.setMap(this.map);
						this.visibleMarkers[id] = true;
					} else {
						mark.googleMarker.setMap(null);
						delete this.visibleMarkers[id];
					}
				}
			},

			/**
			 * Toggle all markers for the optional type and sub type
			 *
			 * @param bool show - flag if the markers should be made visible
			 * @param string type - the marker type to show
			 * @param string subType - optional marker sub type to show
			 */
			toggleMarkersByType: function(show, type, subType) {
				// NOTE: the actual toggling code is repeated to prevent
				// looping over the markers more than once
				var self = this;

				var map = show ? self.map : null;

				// if no type is specified, hide all markers
				if ($.isUndefined(type)) {
					$.each(self.markers, function(id, mark) {

						mark.googleMarker.setMap(map);

						if (show) {
							self.visibleMarkers[id] = true;
						} else {
							delete self.visibleMarkers[id];
						}
					});

					return;
				}

				// don't toggle anything if a marker type is
				// specified that doesn't exist
				if (self.markersByType[type]) {

					// only hide all markers of the given type if 
					// no subtype is given
					if ($.isUndefined(subType)) {

						$.each(self.markersByType[type], function(subType, ids) {

							for (index in ids) {
								var id = self.markersByType[type][subType][index];

								self.markers[id].googleMarker.setMap(map);
								if (show) {
									self.visibleMarkers[id] = true;
								} else {
									delete self.visibleMarkers[id];
								}
							}
						});

					} else if (!$.isUndefined(self.markersByType[type][subType])) {
					
						for (index in self.markersByType[type][subType]) {
							var id = self.markersByType[type][subType][index];

							self.markers[id].googleMarker.setMap(map);
							if (show) {
								self.visibleMarkers[id] = true;
							} else {
								delete self.visibleMarkers[id];
							}
						}
					}
				}
			},

			/**
			 * Trigger the map blur event. This hides any info windows
			 * and triggers the blur event for the current marker
			 */
			blur: function() {
				// hide all info windows
				$('.' + GoogleMap.constants.CLASS_INFO_WINDOW).hide();

				// trigger the blur event for the active marker
				if (this.currentMarker) {
					google.maps.event.trigger(this.currentMarker.googleMarker, GoogleMap.constants.EVENT_BLUR);

					this.currentMarker = null;
				}
			},

			/**
			 * Change the zoom level of the map to the specified number.
			 * If no number is specified, resize to fit all visible markers.
			 *
			 * @param int size - optional zoom level between 1 and 15
			 */
			resize: function(size) {
				if (!$.isNullOrUndefined(size)) {
					this.map.setZoom(size);
				} else {
					var bounds = new google.maps.LatLngBounds();
					var markerCount = 0;

					var self = this;
					$.each(self.visibleMarkers, function(id) {
						var position = self.markers[id].googleMarker.getPosition();
						bounds.extend(position);
						markerCount++;
					});

					if (markerCount > 0) {
						this.map.fitBounds(bounds);
					}

					// with only one point, you get ridiculously close to the
					// position of the marker, so zoom out a bit
					if (markerCount == 1) {
						this.resize(GoogleMap.constants.ZOOM_CITY);
					}
				}
			},

			/**
			 * Delay a function call. Multiple calls to this will
			 * clear any active previous ones.
			 *
			 * @param function func - the function to call after the delay
			 */
			delayAction: function(func) {
				if (this.popupTimeout) {
					clearTimeout(this.popupTimeout);
				}

				this.popupTimeout = setTimeout(func, this.popupDelay);
			},

			/**
			 * Set the info window popup delay
			 *
			 * @param int delay - the popup delay in ms
			 */
			setPopupDelay: function(delay) {
				this.popupDelay = delay;
				return this;
			}
		}

    });


	/**
	 * constructor for a google map marker type
	 * these can exist indepenendent from a GoogleMap and can
	 * therefore be shared between multiple maps on the page
	 *
	 * @param string id - unique identifier for this marker type
	 */
	GoogleMap.MarkerType = function(id) {

		this.id = id;

		this.infoWindow = null;

		this.options = {};
		this.triggerEvents = {};

		this.init();
	};

	/**
	 * singleton pattern for creating/retrieving specific
	 * instances of the marker type object
	 *
	 * @param string id - the identifier for a marker type
	 */
	GoogleMap.MarkerType.singleton = function(id) {
		if ($.isUndefined(GoogleMap.markerTypes[id])) {
			GoogleMap.markerTypes[id] = new GoogleMap.MarkerType(id);
		}

		return GoogleMap.markerTypes[id];
	};

	/**
	 * Create a new google maps marker type to be reused for displaying markers
	 * on an existing map.
	 */
	$.extend(GoogleMap.MarkerType, {

		/**
		 * Instance methods for the marker type object. 
		 */
		prototype: {

			init: function() {
				// just take care of storing the newly created marker type
				GoogleMap.markerTypes[this.id] = this;
			},

			/**
			 * Add the info window template to the page
			 * NOTE: If creating a new info window to append to the page,
			 * the call to this function must happen after the page has loaded.
			 * The call can be wrapped in a "$(function () {  ...  })"  statement
			 *
			 * @param string id - element id for this marker type's info window
			 * @param string template - optional html for the info window
			 */
			createInfoWindow: function(id, template) {

				if ($('#' + id).length) {
					$('#' + id).addClass(GoogleMap.constants.CLASS_INFO_WINDOW).hide();
				} else {
					$(template).attr('id', id).addClass(GoogleMap.constants.CLASS_INFO_WINDOW).appendTo('body').hide();
				}

				this.infoWindow = DHTMLPopup_Factory.create( { id: id });

				var self = this;
				// add an event trigger on the info window to blur the marker it's attached
				// to when the info window is closed
				this.infoWindow.popup.bind('dhtmlpopup:hide', function(e) {
					var map = self.infoWindow.popup.data('map');
					if (map) {
						map.blur();
					}
				});
			},

			/**
			 * Update the info window with the supplied 
			 *
			 * @param function operations - replacements that can't
			 *	be handled by simple string replace
			 */
			updateInfoWindow: function(operations) {
				if ($.isNull(this.infoWindow)) {
					return;
				}

				$.proxy(operations, this.infoWindow.popup)();
				
			},

			/**
			 * Show the info window
			 *
			 * @param object map - the GoogleMap object to which this info
			 * 	window is being attached.
			 * @param object marker - the marker to which this info window
			 *	is being attached. Used for determining where to show the popup.
			 * @param object offsets - additional left and top pixel offsets
			 *	to control the exact popup position.
			 */
			showInfoWindow: function(map, marker, offsets) {
				if ($.isNull(this.infoWindow) || !map) {
					return;
				}

				// trigger the map blur event to hide any open info windows
				map.blur();

				this.infoWindow.popup.data('map', map);

				var position = map.element.offset();

				if (marker) {
					map.currentMarker = marker;

					var markerPos = map.overlay.getProjection().fromLatLngToContainerPixel(marker.googleMarker.getPosition());
					position.top += markerPos.y;
					position.left += markerPos.x;

				}

				if (offsets) {
					position.top = Math.max(position.top + offsets.top, 0);
					position.left = Math.max(position.left + offsets.left, 0);
				}

				var self = this;
				map.delayAction(function() { self.infoWindow.show(true, position); });
			},

			/**
			 * Hide the info window
			 */
			hideInfoWindow: function() {
				if (!this.infoWindow) {
					return;
				}

				this.infoWindow.popup.hide();
			},

			/**
			 * Get the marker type options
			 *
			 * @param object - the marker type options
			 */
			getOptions: function() {
				return this.options;
			},

			/**
			 * Get the marker type trigger events
			 *
			 * @param object - the marker type trigger events
			 */
			getTriggerEvents: function() {
				return this.triggerEvents;
			},

			/**
			 * Set parameters used to create a google maps marker
			 *
			 * @param object options - parameters for a marker
			 */
			setOptions: function(options) {
				$.extend(this.options, options);
				return this;
			},

			/**
			 * Set trigger events and handlers to modify markers based on user input
			 * NOTE: Handler functions will be passed the following arguments:
			 * - map - the GoogleMap object on which the event is being added
			 * - marker - the GoogleMap marker to which the event is being applied
			 * - config - configuration settings for the marker/info window
			 *
			 * @param object events - key/value pairs of events and handler functions
			 *
			 * NOTE: Currently recognized marker trigger events include:
			 *	- click, dblclick, mouseup, mousedown, mouseover, mouseout
			 *	- GoogleMap.constants.EVENT_BLUR (triggered when calling blur()
			 *		on a GoogleMap with a focused marker)
			 */
			setTriggerEvents: function(events) {
				$.extend(this.triggerEvents, events);
				return this;
			}
		}
	});

})(jQuery);

(function($) {
	$.toggleGroups = {};
	
	$.toggleGroupsDebug = function() {
		BBDebug.group("Toggle Groups", true);
		BBDebug.dir($.toggleGroups);
		BBDebug.groupEnd();
	};
	$.extend($.fn, {
		
		togglerHidden : function() {
			return !$(this[0]).is(':visible');
		},

		togglerVisible : function() {
			return $(this[0]).is(':visible');
		},
	
		toggler: function(){
		
			$(this).each( function() {
				// Set up toggle elements
				if(!$(this).hasClass("toggleActive")) {
					var _id = $(this).attr("id");
					var _prefix = _id.substring(0, _id.lastIndexOf("_"));
					var _toggle_info = _id.substring(_id.lastIndexOf("_")+1).split("-");
					var _action = _toggle_info[0];
					var _group = _toggle_info[1] ? _toggle_info[1] : false;
					var _dhtmlpopup = $("#"+ _prefix).hasClass("dhtml_popup");
					var _tabs = $(this).hasClass("tabbed");
					var _type = this.tagName.toLowerCase();
					if(_type == "input") {
						_type = $(this).attr("type").toLowerCase();
					}
					if(_group) {
						if(!$.toggleGroups[_group]) {
							$.toggleGroups[_group] = [];
							$.data(this, "group_ids", $.toggleGroups[_group]); 
						}
						$.toggleGroups[_group][$.toggleGroups[_group].length] = _prefix;
						
					}
					$(this).click( function() { 
						if(_dhtmlpopup) {
							var _popup_elem = $("#" +_prefix).get(0);
							if(_popup_elem) {
								var _popup = $.data(_popup_elem, "dhtmlpopup");
								if(!_popup) {
									_popup = new DHTMLPopup(_prefix);
								}
								if (_action=="hide") {
									_popup.hide();
								}
								else if (_action=="show") {
									var _offset = null;
									if($("#"+ _prefix + "_offset").length > 0) {
										_offset = _prefix + "_offset";
									}
									_popup.show(_offset);
								}
							}
							
						} else {
							if(_group) {
								$.each( $.toggleGroups[_group], function(i, groupId) {
									$("#"+ groupId).hide();
									if(_tabs) {
										$("."+ groupId+"_tab").removeClass("selected");
										$("."+ groupId +"_tab").parent(".toggle_tabs").removeClass(groupId +"_tab_active");
										
									}
								});
							}
							if(_tabs) {
								$("."+ _prefix +"_tab").parent(".toggle_tabs").addClass(_prefix +"_tab_active");
								$("."+ _prefix +"_tab").addClass("selected");
							}
							if(_type == "radio") {
								if (_action == "hide") {
									$("#" + _prefix).hide();
									$("#" + _prefix).trigger('STMToggle:hide');
								} else if (_action=="show") {
									$("#" + _prefix).show();
									$("#" + _prefix).trigger('STMToggle:show');
								}
							} else if(_type == "checkbox") {
								if (!$("#" + _prefix).is(':visible')) {
									$("#" + _prefix ).show();
									$("#" + _prefix).trigger('STMToggle:show');
								} else {
									$("#" + _prefix ).hide();
									$("#" + _prefix).trigger('STMToggle:hide');
								}
							} else {
								if($("#"+ _prefix + ":visible").length) { 
									$(this).find(".close").hide(); 
									$(this).find(".open").show(); 
									$("#"+ _prefix ).hide(); 
									if(_action=="swap") {
										$("#"+ _prefix + "_initial" ).show();
										$("#"+ _prefix + "_initial" ).trigger("STMToggle:initialStateShow");
									}
								}
								else { 
									$(this).find(".close").show(); 
									$(this).find(".open").hide();
									$("#"+ _prefix).show(); 
									if(_action=="swap") {
										$("#"+ _prefix + "_initial" ).hide();
										$("#"+ _prefix + "_initial" ).trigger("STMToggle:initialStateHide");
									}
								}
							}
						}
						$(this).blur();
						$(this).trigger("STMToggle:complete");
					} );
					
					$(this).addClass("toggleActive");
				}
			});
			
		}
	});
	
})(jQuery);
/**
 * This file defines a few classes for displaying and managing subscriptions
 * on a page and then attaches them to various elements on document.ready even
 * though that really belongs elsewhere.
 */
(function($) {

	/**
	 * AJAX subscriptions for departure or route newsletters driven
	 * by validation event callbacks for locationSuggest elements.
	 */
	AjaxSubscriptionLocation = Object.subClass({

		/**
		 * Current departure location for inputs driving this sub form.
		 */
		departure: '',

		/**
		 * Current arrival location for inputs driving this sub form.
		 */
		arrival: '',

		/**
		 * Is the current departure location considered domestic
		 */
		departureIsDomestic: false,

		/**
		 * Block wrapping this entire section of sub inputs.
		 */
		element: null,

		/**
		 * Email input for this block of sub inputs.
		 */
		email_element: null,

		/**
		 * Store the block containing this sub instance and email input.
		 */
		init: function(elm) {
			this.element = $(elm);
			if (!this.element.length) {
				return;
			}

			this.email_element = $('.nl_sub_inline_e1_div', this.element);

			this._saCallback();
		},

		/**
		 * Given a selector to use to find inputs to drive this signup instance from
		 * the context of a smartForm, attach callbacks to the inputs we find.
		 */
		bind: function(input_selector) {
			if (!this.element.length) {
				return;
			}

			var form_elm = $(this.element).closest('.smartForm');
			if (!form_elm.length) {
				return;
			}

			$(input_selector, form_elm).each($.proxy(function(index, elm) {
				$(elm).bind('LocationSuggest:valid LocationSuggest:valid:quickcheck', $.proxy(this._validationCallback, this));
			}, this));
		},


		/**
		 * Show SniqueAway input (doesn't really correspond to a location input).
		 */
		_saCallback: function(event) {
			if (!BookingBuddy.getCookie('rico_invite')) {
				var template = BookingBuddy.Strings.NL.inlineSATeaser;
				$('.nl_sub_inline_sa_desc', this.element).html($j.tmpl(template, {}));
				this._showInlineInput('.nl_sub_inline_sa');
			}
		},

		/**
		 * Update our stored locations as a response to LocationSuggest validation
		 * events and show an inline subscription input if it makes sense.
		 */
		_validationCallback: function(event) {
			var BBS_NL = BookingBuddy.Strings.NL;
			var template_dep = BBS_NL.inlineDepTeaser;
			var template_route = BBS_NL.inlineRouteTeaser;

			var $elm = $(event.target);
			if (!$elm[0]) {
				return;
			}


			this._setLocations($elm);

			if ('departure_city' === $elm.attr('name')) {
				// Check if the departure location is domestic before showing
				// the c2c and departure fare sign up form
				if (this.departure && this.departureIsDomestic) {

					// show the departure fares sign up
					$('.nl_sub_inline_departure_desc', this.element).html($j.tmpl(template_dep,
						{ dep_airport: this.departure
					}));

					this._showInlineInput('.nl_sub_inline_departure');

					if (this.arrival) {

						// show the city to city fares sign up
						$('.nl_sub_inline_route_desc', this.element).html($j.tmpl(template_route, {
							dep_airport: this._getAirportCode(this.departure),
							arr_airport: this._getAirportCode(this.arrival)
						}));

						this._showInlineInput('.nl_sub_inline_route');
					}

				} else {
					this._hideInputs();
				}

			} else if ('arrival_city' === $elm.attr('name')) {

				// as long we have a domestic departure city, show/update
				// the city to city signup with the new arrival city
				if (this.departure && this.departureIsDomestic && this.arrival) {
					$('.nl_sub_inline_route_desc', this.element).html($j.tmpl(template_route, {
						dep_airport: this._getAirportCode(this.departure),
						arr_airport: this._getAirportCode(this.arrival)
					}));

					this._showInlineInput('.nl_sub_inline_route');
				}

				// handle showing the hotel watch sign up form
				if (this.arrival) {

					var $hotelWatchDesc = $('.nl_sub_inline_hotel_watch_desc', this.element);
					var template = $hotelWatchDesc.hasClass('existing') ?
						BBS_NL.inlineHotelWatchAddTeaser : BBS_NL.inlineHotelWatchTeaser;

					$hotelWatchDesc.html($j.tmpl(template, { arrival_city: this.arrival }));

					this._showInlineInput('.nl_sub_inline_hotel_watch');

				}
			}
		},

		/**
		 * Find and show the element specified by the selector if it exists.
		 */
		_showInlineInput: function(elm_selector) {
			if (!$(elm_selector, this.element).length) {
				return;
			}

			this.element.show();
			if ($('input', elm_selector).attr('checked')) {
				this.email_element.fadeIn();
			}
			$(elm_selector, this.element).fadeIn();
		},

        /**
         * Hide the subscription element
         */
        _hideInputs: function() {

            if (this.element.is(':visible')) {
                this.element.fadeOut();
            }
        },

		/**
		 * Turn a location string like "Boston, MA (BOS)" into just an airport code like "BOS".
		 */
		_getAirportCode: function(val) {
			var matches = val.match(/\(([\w]{3})\)/);
			if (matches) {
				return matches[1];
			}

			return '';
		},

		/**
		 * Update our locations based on the value of the given element. Do this
		 * instead of using recent/current search data since that won't be updated
		 * until after the location validation event we are reacting to.
		 */
		_setLocations: function(elm) {
			if ('departure_city' === elm.attr('name')) {
				this.departure = elm.val();
				this.departureIsDomestic = this.isDomestic(elm.data('country'));
			} else if ('arrival_city' === elm.attr('name')) {
				this.arrival = elm.val();
			}
		},

		/**
		 * Determine if the given country code is considered
		 * to be domestic.
		 *
		 * @param string countryCode
		 * @return bool
		 */
		isDomestic: function(countryCode) {
			return countryCode === 'US' || countryCode === 'CA';

		}
	});


	/**
	 * Manage various frontends that interact with the AJAX subscription
	 * endpoint (DHTML popups, simple forms, etc.).
	 */
	AjaxSubscription = function( id, options ) {
		this.id = id;
		this.popup = null;
		this.popupPos = null;
		this.form = null;
		this.init(options);
	};

	$.extend(AjaxSubscription, {
		options: {
			offset: {left: 0, top: -50},
			useIframe: false,
			cookie_name: 'BBDHTMLPopup_Sub'
		},
		prototype : {
			init: function(options) {

				this.options = $.extend( {}, AjaxSubscription.options, options );

				// store this once so you aren't re-selecting it 1000 times
				var $wrapper = $('#' + this.id);

				this.popupPos = $('#'+this.id+'_marker').length > 0 ? this.id+'_marker' : null;
				this.popup = DHTMLPopup_Factory.create({ id: this.id, type: "sub", use_iframe: this.options.useIframe });
				this.popup.setCookieName(this.options.cookie_name);
				this.form = $('#'+this.id+'_form');
				this.departure = $('input[name="ac1"]', this.form).val();

				this.departureCity = $('.departure_name', this.form).text();
				this.arrivalCity = $('.arrival_name', this.form).text();

				$('.locationSuggest', $wrapper).keypress(BookingBuddy.handleEnterKey);

				$('.closeSub', $wrapper).click( $.proxy(function(e) {
					$wrapper.trigger('dhtmlpopup:sub:closed');
					this.popup.hide(e);
				}, this));

				var _ajaxSub = this,
					subId = _ajaxSub.id;

				$('input[type="text"]', this.form).focus(function(event) {
					$('#' + subId + '_msg').hide().html('');
				});

				$wrapper.bind('dhtmlpopup:sub:success', $.proxy( function(event, data) {
					this.step2(data);
				}, this));
				$wrapper.bind('dhtmlpopup:sub:error', $.proxy( function(event, data) {
					this.showError(data);
				}, this));

				this.form.smartFormFactory( {
					submitHandler: function(form ,target, e) {
						var _autoConfirmEmail = $(form).find(".autoConfirmEmail").get(0);
						if(_autoConfirmEmail) {
							var _e1 = $(form).find(" input[name=e1]");
							$(_autoConfirmEmail).val(_e1.val());
						}
						_ajaxSub.popup.submit();
						e.stopPropagation();
						e.preventDefault();
						return false;
					}
				});
				$.data( $wrapper.get(0), "ajaxSub", this);
			},

			/**
			 * Get all currently selected newsletter products in our form.
			 */
			_getNLProducts: function() {
				if (this.form.length != 1) {
					return [];
				}

				var ret = [];
				$.each(this.form.serializeArray(), function(index, elm) {
					if ('nlp[]' == elm.name) {
						ret.push(elm.value);
					}
				});

				return ret;
			},

			/**
			 * Return true if the given newsletter products are selected in our form.
			 */
			_isSubSelected: function(nl_products) {
				var values = this._getNLProducts();
				var has_sub = false;

				$.each(nl_products, function(index, product) {
					if (-1 != $.inArray(product, values)) {
						has_sub = true;
					}

					return !has_sub;
				});

				return has_sub;
			},

			/**
			 * Is a fare alert departure product selected?
			 */
			_isFareAlertSubDeparture: function() {
				return this._isSubSelected(['nl_product_bb_departure']);
			},

			/**
			 * Is a fare alert route product selected?
			 */
			_isFareAlertSubroute: function() {
				return this._isSubSelected(['nl_product_bb_route']);
			},

			/**
			 * Is a regular newsletter product selected?
			 */
			_isDefaultSub: function() {
				return this._isSubSelected(['default', 'nl_product_bbtd']);
			},

			/**
			 * Is this a SniqueAway co-reg?
			 */
			_isSASub: function() {
				return this._isSubSelected(['nl_product_sa']);
			},

			/**
			 * Is this a hotel watch product selected?
			 */
			_isHotelWatchSub: function() {
				return this._isSubSelected(['nl_product_bb_hotel_watch']);
			},

			step1: function() {
				var $wrapper = $('#' + this.id);
				$wrapper.trigger('dhtmlpopup:sub:step1');
				this.popup.show(this.popupPos, this.options.offset);
				$('.step1', $wrapper).show();
				$('.step2', $wrapper).hide();
			},

			step2: function(data) {
				var $wrapper = $('#' + this.id);
				$('.step1', $wrapper).hide();

				if (this.id !== 'dhtmlsignup_uk') {
					$('#not_you').hide();
					$('#username_block').html("Back");
				}

				// save the user's email address and the status of the fare alerts checkbox into the data object so that we have access to them
				// within the dhtmlpopup:sub:step2 event
				data["email"] = this.form.find('#sub_e1').val();
				data["fare_alerts"] = this.form.find('#sub_nlp').attr('checked');

				this.form.data("smartForm").reset();
				this.showSuccess(data);
				$('.step2', $wrapper).show();
				var newTitle = $('.step2 .title', $wrapper).html();
				$wrapper.parents('.inline_subs_module').find('.title').html(newTitle);

				if (this.id !== 'dhtmlsignup_uk') {
					// modify the text shown in the top sign up box when subbing
					// through the deals box
					if (this.id === 'dealssub' && !data.existing) {
						$('#user_area .user_welcome').text('Welcome!');
					}
					$('#billboard').hide();
					$('#user_area').show();
				}

				$wrapper.trigger('dhtmlpopup:sub:step2', [data]);
			},

			showError: function(data) {
				var msg = data.message || $('#' + this.id + ' .sub_error_msg').html();
				$('#' + this.id + ' .step1').show();
				$('#' + this.id + '_form_errorMessages').html(msg).show();
			},

			showSuccess: function(data) {
				var sub_types = [];

				if (this._isFareAlertSubDeparture()) { sub_types.push('Fare Alert Departure'); }
				if (this._isFareAlertSubroute()) 	 { sub_types.push('Fare Alert Route'); }
				if (this._isDefaultSub()) 			 { sub_types.push('Default'); }
				if (this._isSASub()) 				 { sub_types.push('Snique Away'); }
				if (this._isHotelWatchSub()) 		 { sub_types.push('Hotel Watch'); }

				if (this._isHotelWatchSub() && this._isSASub()) {
					$('#' + this.id + '_wrapup_header').html(BookingBuddy.Strings.NL.inlineSAPlusHWWrapupHeader);
					$('#' + this.id + '_wrapup_sub').html(BookingBuddy.Strings.NL.inlineSAPlusHWWrapupMsg);
					$('#' + this.id + '_wrapup_sub_2').html($.tmpl(BookingBuddy.Strings.NL.inlineSAPlusHWWrapupMsgSubText,
								{arrival_city: this.arrivalCity}));
				} else if (this._isHotelWatchSub()) {
					$('#' + this.id + '_wrapup_header').html($.tmpl(BookingBuddy.Strings.NL.inlineHotelWatchWrapupHeader,
								{arrival_city: this.arrivalCity}));
					$('#' + this.id + '_wrapup_sub').html($.tmpl(BookingBuddy.Strings.NL.inlineHotelWatchWrapupMsg,
								{arrival_city: this.arrivalCity}));
				// If SA sub ONLY, show SA messaging, hide 2nd part of wrapup
				} else if (this._isSASub() && !this._isFareAlertSubDeparture() && !this._isFareAlertSubroute() && !this._isDefaultSub()) {
					// show SA messages if it's only SA sub
					$('#' + this.id + '_wrapup_header').html(BookingBuddy.Strings.NL.inlineSAWrapupHeader);
					$('#' + this.id + '_wrapup_sub').html(BookingBuddy.Strings.NL.inlineSAWrapupMsg);
					$('#' + this.id + '_wrapup_sub_2').hide();
				} else {
					// default
					$('#' + this.id + '_wrapup_header').html(BookingBuddy.Strings.NL.inlineWrapupHeader);
					$('#' + this.id + '_wrapup_sub').html($j.tmpl(BookingBuddy.Strings.NL.inlineWrapupMsg,
						{dep_airport: this.departure}
					));
				}

				if (!data.existing || !data.existing_default || this._isSASub()) {
					$.publish('/sub/new', [$('#'+this.id), sub_types]);
				} else {
					$('#' + this.id + '_wrapup_header').html($('#' + this.id + ' .welcome_back_msg').html());
					$.publish('/sub/login', [$('#'+this.id), sub_types]);
				}
			}
		}
	});
})(jQuery);


$j(document).ready(function() {

	var _hasTopJoinForm =$j("#topjoin_form").length > 0;
	var _hasDhtmlSignupUK = $j('#dhtmlsignup_uk').length > 0;
	var _hasNLSignup_inline = $j("#nl_sub_step2").length > 0;
	var _hasSub_form = $j("#sub_form").length > 0;
	var $dealsSubForm = $j("#dealssub_form");
	var terms_cid = 2610404;
	var priv_cid = 2609433;

	if (_hasTopJoinForm) {
		$j('#topjoin_form').smartFormFactory( {
			submitHandler: function(form ,target, e) {
				$j.asyncLoad('#dhtmlsignup', 'topjoin', function(elm_selector) {
					$j('#topjoin_e1').blur();
					$j('#dhtmlsignup_e1').val($j('#topjoin_e1').val());

					var _dhtmlsignup = new AjaxSubscription($j(elm_selector).attr('id'), {offset: {top: 0, left: -5}, cookie_name: null});
					_dhtmlsignup.step1();
				});

				e.stopPropagation();
				e.preventDefault();
				return false;
			}
		});
	}

	if ($dealsSubForm[0]) {
		var dhtmlsignup = new AjaxSubscription('dealssub', {offset: {top: 0, left: 0}, cookie_name: null});
	}

	// hotel watch sub form without dhtml layer
	var $hotelWatchSubForm = $j('#hotelwatch_signup_form');
	if ($hotelWatchSubForm [0]) {
		var hotelwatchSignup = new AjaxSubscription('hotelwatch_signup', {offset: {top: 0, left: 0}, cookie_name: null});
	}

	if (_hasDhtmlSignupUK) {
		var _dhtmlsignup_uk = new AjaxSubscription('dhtmlsignup_uk', { offset: { top: 0, left: 0 } });
		terms_cid = 2617144;
		priv_cid = 2617145;
		$j('#traveldeals').click( function() {
			_dhtmlsignup_uk.popup.setCookieName(null);
			_dhtmlsignup_uk.step1();
		});

		$j('.smartForm.BBUKSearch').bind("bbuk:submit", function() {
			_dhtmlsignup_uk.popup.setCookieName('BBDHTMLPopup_Sub');
			_dhtmlsignup_uk.step1();
		});
	}

	$j('.terms').live('click', function() {
		openInfoPopup(terms_cid, 'BookingBuddy+Terms+of+Use', 'bb_terms', 'scrollbars=1,resizable=1,width=460,height=500');
	});

	$j('.privacy').live('click', function() {
		openInfoPopup(priv_cid, 'BookingBuddy+Privacy+Policy', 'bb_privacy_policy', 'scrollbars=1,resizable=1,width=460,height=500');
	});

	if(_hasSub_form) {
		BBDebug.log("Sub Join form");
		// hook onto the event fired when the form is submitted successfully. redirect to the homepage with settings to fire off the dhtml lightbox
		$j('#sub').bind('dhtmlpopup:sub:step2', function(event, data) {
			var query_str = '?lightbox=new_sub&user=' + encodeURIComponent(data.email ? data.email : '') + '&existing=' + (data.existing ? 'true' : 'false') + '&existing_default=' + (data.existing_default ? 'true' : 'false') + '&fare_alerts=' + (data.fare_alerts ? 'true' : 'false');
			// redirect user in 1.5 seconds, giving the tracking request time to get sent
			setTimeout("window.location.href = '/" + query_str + "'", 1500);
		});

 		var nl_form = new AjaxSubscription('sub', {});
	}
	if(_hasNLSignup_inline) {
		BBDebug.log("Found step 2 newsletter signup");

		var nlsignup_inline = new AjaxSubscription('nl_sub_step2', { cookie_name: null });
		var email_elm = $j('#nl_sub_step2_e1').data('smartElement');
		var email_entered = !$j.emptyString(email_elm.value());
		if (email_elm.defaultVal && email_elm.value() == email_elm.defaultVal) {
			email_entered = false;
		}

		var hotelWatchChecked = $j('#nl_sub_step2_hotel_watch').is(':checked');
		var dep_checked = $j('#nl_sub_step2_dep').is(':checked');
		var rt_checked = $j('#nl_sub_step2_route').is(':checked');
		var sa_checked = $j('#nl_sub_step2_sa').is(':checked');

		BBDebug.log("Departure checked: " + dep_checked + " - Route checked: " + rt_checked);
		nlsignup_inline.step1();
		if (email_entered || hotelWatchChecked || dep_checked || rt_checked || sa_checked) {
			$j(nlsignup_inline.form).submit();
		}
	}

	$j('.smartForm .nl_sub_inline').each(function(index, elm) {
                var sub = new AjaxSubscriptionLocation(elm);
                sub.bind('.ajaxLocationSignup');

                $j('input[type=checkbox]', elm).change(function() {
                        var count = 0;

                        $j(elm).find('input[type=checkbox]').each(function(index2, elm2) {
                                if ($j(elm2).attr('checked')) {
                                        count++;
                                }
                        });

                        if (count > 0) {
                                sub.email_element.fadeIn();
                        } else {
                                sub.email_element.hide();
                        }
                });
        });
});

var BBInit = {

	all: function() {

		// Forms should be initialized first to make sure the $j.smartFormsBBSavedSearchData is initialized
		this.forms();
		this.deals();
		this.fareEntries();
		this.popupHotelPlanner();

		this.taCheckRates();

		var hotelImageLoader = new BBHotelImages();
		hotelImageLoader.loadHotelImages();

		this.googleMaps();

		// Open location details
		this.openLocationDetails();

		this.affiliateLanding();

		// set up the TRAQ Lander
		this.traqLander();

		// Launches the DHTML sub layer on step2
		this.launchDHTMLSub();

		// BBS-1970
		this.bbs1970();

		// BBS-2026
		this.bbs2026();

		//js for SEMBlurb DHTML
		this.semBlurbPopup();

		// BBS-2342 404 Test
		this.bbs2342();

		// CRM-59413 lapsed test
		this.crm59413();

		// BBS-2200 hotel cross-sell test
		this.bbs2200();

		this.tabBrowsing();
	},

	// START CRM_59413
	crm59413: function() {
		var $confirmPopup = $j('#dhtml_confirm');
		if ($confirmPopup.length > 0) {
			var dhtmlPopup = DHTMLPopup_Factory.create({ id: "dhtml_confirm" , type: "base" });

			dhtmlPopup.setPageMask('page_mask');
			var offset = {left: 220, top: 25};
			dhtmlPopup.show('basic_content',offset);
		}
	},
	// END CRM_59413

	// START: BBS-2342
	bbs2342: function() {
		// 404 test that's not really a 404 test
		var $privateSalesLink = $j('#nav a.navtab.news');
		if ($privateSalesLink.length > 0) {
			$privateSalesLink.click(function(event) {
				// prevent the default action (opening the blog)
				event.stopPropagation();
				// Launch snique away in a new window
				var popup = new ExternalPopup({
					url: 'https://secure.sniqueaway.com/invite/BookingBuddy?sid=436',
					width: 730,
					height: 730,
					placement: ExternalPopup.PLACEMENTS.PARENT
				});
				return false;
			});
		}
	},
	// END: BBS-2342

	/**
	 * Loads and Launches the TRAQ splash lander
	 *
	 */
	traqLander: function() {
		var q_string = $j.extend({}, BookingBuddy.queryString);
		if (q_string.ctm) {
			var params = q_string.ctm;

			// START: BBS-1705
			var BB_AB = BookingBuddy.abTests;
			if (BB_AB.bbs1705) {
				// how the recipe is constructed: the recipe has two parts, (i.e. a1, a2, b1, b2, etc.):
				// 1) alpha char describing what parameters should be passed to the traq lander (handled js side)
				// 2) numeric digit describing what page the user should be sent to (handled php side)
				var param_lookup = {
					a: 'A1',
					b: 'B1',
					c: 'C1',
					d: 'A1B1',
					e: 'B1C1',
					f: 'A1C1',
					g: 'A1B1C1',
					h: ''
				};

				// override the traq lander parameters for people in a test
				if (BB_AB.bbs1705.length === 2 && BB_AB.bbs1705.charAt(0) in param_lookup) {
					params = param_lookup[BB_AB.bbs1705.charAt(0)];
				}

			}
			// END: BBS-1705

			// initialize the splash page if necessary
			var splash_page = new BBTraqLander({
				params: params,
				splash_container: 'splash_container',
				page_mask: 'traq_lander_page_mask'
			});
			splash_page.display();
		}
	},

	/**
	 * init deals
	 */
	deals: function() {

		var deal_options = {};
		var config_options = {};

		// check to see if any bbdn deals modules exist on the page
		if (!$j('.deals_module')[0]) {
			return;
		}

		// Default settings

		var target_input =  BookingBuddy.searchMode == 'car' ? 'pickup_city' : BookingBuddy.searchMode == 'cruise' ? 'destination' : 'arrival_city';
		var origin_input = 'departure_city';

		// Get the destination city
		$j.smartFormsBBSavedSearchData = BBSavedSearchData.singleton();
		var destination_city = $j.smartFormsBBSavedSearchData.getCurrentSearchData(target_input, BookingBuddy.searchMode);
		if ($j.emptyString(destination_city)) {
			if (typeof $j.smartFormsBBSavedSearchData.getCurrentState().c2 !== 'undefined') {
				destination_city = $j.smartFormsBBSavedSearchData.getCurrentState().c2;
			}
		}

		// Get the origin city
		var origin_city = $j.smartFormsBBSavedSearchData.getCurrentSearchData(origin_input, BookingBuddy.searchMode);
		if ($j.emptyString(origin_city)) {
			if (typeof $j.smartFormsBBSavedSearchData.getCurrentState().c1 !== 'undefined') {
				origin_city = $j.smartFormsBBSavedSearchData.getCurrentState().c1;
			}
		}

		// We want to pull any type of deal for air
		if (BookingBuddy.searchMode == 'air' || BookingBuddy.searchMode == 'home') { deal_options.deal_type = ''; }

		if (BookingBuddy.queryString.city) {
			deal_options.stm_destination_id = BookingBuddy.queryString.city;
		} else if (!$j.emptyString(destination_city)) {
			deal_options.arrival_city = destination_city;
		}

		if (!$j.emptyString(origin_city)) {
			deal_options.departure_city = origin_city;
		}

		/**
		 * Populate the default bbdn top deals module
		 */
		var $topDealsDivs = $j('.deals_module.top_deals');
		if ($topDealsDivs[0]) {

			var $topDealsBox = $j("#top_deals_box");

			// intermediate step for loading hidden images to vacation tab deals - BBDN-585
			var _template = $topDealsBox.hasClass("narrowDealsLayout") ?
				BookingBuddy.Strings.Deals.RevNarrowTemplate :
				BookingBuddy.Strings.Deals.RevSpanTemplate;

			deal_options['placement'] = 'BBS_rr_' + BookingBuddy.searchMode;

			config_options = {
				'containerDivs'    : $topDealsDivs.first(),
				'fold_price'       : true,
				'fold_destination' : true,
				'target_input'     : target_input,
				'search_mode'      : BookingBuddy.search_mode,
				'dealTemplate'     : _template,
				'onSuccess'        : function() { $topDealsBox.show(); $j.publish('/load/module/bbdeal', ['top_deals']);},
				'onError'          : function() { $topDealsBox.hide(); }
			};

			BookingBuddy.Deals.register(deal_options, config_options);
			BookingBuddy.Deals.init({ 'feedUrl': 'BBDeals.getTopDeals' });
		}

		/**
		 * Load one set of deals and split them up into different sections
		 * when being displayed based on the existence of multiple deal
		 * modules with the "split_deals" class.
		 */
		var $splitDivs = $j('.deals_module.split_deals');
		if ($splitDivs[0]) {

			var numSplits = $splitDivs.size();
			var numAdsPerSplit = 3;

			deal_options['no_ads'] = numSplits * numAdsPerSplit;
			deal_options['placement'] = 'BBS_sp_' + BookingBuddy.searchMode;

			config_options = {
				'containerDivs'		: $splitDivs,
				'search_mode'		: BookingBuddy.search_mode,
				'dealTemplate'		: BookingBuddy.Strings.Deals.SponsoredTemplate,
				'onSuccess'			: function() { $splitDivs.show(); },
				'onError'			: function() { $splitDivs.hide(); }
			};

			BookingBuddy.Deals.register(deal_options, config_options, 'split_deals');
			BookingBuddy.Deals.init({ 'feedUrl': 'BBDeals.getTopDeals' });
		}


		/**
		 * Load a deal for a specific property without any backfill
		 */
		var $targetedDiv = $j('.deals_module.targeted_deals');
		if ($targetedDiv[0]) {
			var propertyId = $targetedDiv.find('.property_id').text();

			var targetedParams = {
				hotel_id: propertyId,
				no_ads: 1,
				placement: 'BBS_targeted_' + BookingBuddy.searchMode
			};

			config_options = {
				'containerDivs'		: $targetedDiv.first(),
				'search_mode'		: BookingBuddy.search_mode,
				'dealTemplate'		: BookingBuddy.Strings.Deals.TargetedHotelTemplate,
				'onSuccess'			: function() { $targetedDiv.show(); },
				'onError'			: function() { $targetedDiv.hide(); }
			};

			BookingBuddy.Deals.register(targetedParams, config_options, 'targeted_deals');

			BookingBuddy.Deals.init({ 'feedUrl': 'BBDeals.getTargetedDeals' });
		}

	},

	/**
	 * Initilize 'fare entries' links. These are the links that appear in the
	 * Expedia fare cache module. They are usually contained in a tr, but any
	 * element with properly labeled children works. This init function replaces
	 * the default functionality of the 'Find this fare' links so that they
	 * populate the air widget
	 */
	fareEntries: function() {
		/**
		* Extract just the city from a locations long name "city, state (code)"
		* @param {string} location
		* @returns {string} the city or empty string on error
		*/
		function extractCity(location) {
			var holder = location.split(',');
			if (holder && holder.length > 0) {
				return holder[0];
			} else {
				return '';
			}
		}

		// on click of a populate link on the find fares module
		// on step 1 or step 2
		$j('a.populate', '.sample_fares_module').click(function(event) {
			event.preventDefault();
			event.stopPropagation();

			var $parent	= $j(this).parents('tr'),
				widget	= $j('.step_1_widget:first'),
				data	= {};

			data.from		= $parent.find('.departure_name').text();
			data.to			= $parent.find('.arrival_name').text();
			data.dep_date	= $parent.find('.departure_date').text();
			data.ret_date	= $parent.find('.return_date').text();
			data.price		= $parent.find('.fare_price').text();

			// build the omniture description body
			data.omniture_desc = "Find this fare|From-" + extractCity(data.from) +
				"|To-" + extractCity(data.to) +
				"|Dpt Date-" + data.dep_date.replace(/\//g, '') +
				"|Rtn Date-" + data.ret_date.replace(/\//g,'') +
				"|Prx-" + data.price.replace(/[^0-9]/g, "");

			data.page_type = $parent.find('.page_type').text();

			// publish events
			if ($parent.hasClass('populates_step1') && widget[0]) {
				$j.publish('/click/FindFare/step1', [widget, data]);
			} else {
				$j.publish('/click/FindFare/tabbedSearch', [data]);
			}
		});
	},

	/**
	 * Load ta check rates providers via ajax
	 */
	taCheckRates: function() {
		var $checkRates = $j('.check_rates_wrapper:not(.loaded)');

		if ($checkRates.length === 0) {
			return;
		}

		// keep track of what hotels have already been loaded
		// to ensure only one ajax request is made for check rates
		// providers per hotel. (ONLY used for collapsed checkrates)
		var loadedHotels = {};

		var ta = { ids: [],	inputs: [], type: 'ta'};
		var stm = {ids: [], inputs: [], type: 'stm'};

		var id = null,
			input = null;

		/**
		 * OnChange Event Handler for Collapsed Checkrates.
		 * Currently gets executed when a user opens a new
		 * checkrates dhtml layer.
		 */
		var collapsedOnChangeHandler = function(e) {
			var $this = $j(this),
			$wrapper = $this.parents('.check_rates_wrapper'),	// check rates wrapper div
			$dump = $this.siblings('.check_rates_providers'),	// where the check rates boxes should be written
			hotel_id = $this.data('hotel_id');					// hotel id

			// clear out any of the old checkrates checkboxes
			$dump.html('');

			// load in the new ones from the cache if it exists, or from the ajax service if it doesn't
			if (hotel_id in loadedHotels) {
				$dump.html(loadedHotels[hotel_id]);
			} else {
				loadCollapsedCheckRates($wrapper, hotel_id);
				$this.data('loaded_hotels', loadedHotels);
			}
		};

		// iterate over each of the found checkrates forms and perform the necessary processing
		$j.each($checkRates, function(index, element) {
			id = $j(this).find('.hotel_id').first().text();
			$input = $j(this);

			if ($input.hasClass('showNow')) {
				// build the necessary associative arrays for exposed checkrates.
				// they will all be processed together at the bottom of this method.
				if ($input.hasClass('use_taid')) {
					ta.ids.push(id);
					ta.inputs.push($input);
				} else {
					stm.ids.push(id);
					stm.inputs.push($input);
				}
			} else {
				// hookup for collapsed checkrates, these get populated w/ a seperate ajax call per request.
				$input.find('input[name="provider"]').change(collapsedOnChangeHandler);
			}
		});

		/**
		 * Updates the supplied input with a message saying that the checkrates couldn't be found
		 *
		 * @param {jqueryObject} input checkrates input
		 */
		var noCheckRates = function(input) {
			$j(input).find('.check_rates_providers').html('We were unable to load providers at this time');
		};

		/**
		 * this method is used my loadCheckRates it takes in an offer
		 * and the checkrates form that it belongs to, bulids the html
		 * and appends it to the checkrates form
		 *
		 * @param {array} offer (data returned from TACheckRates service)
		 * @param {int} hotel_id
		 * @param {jqueryObject} providers section of the checkrates form
		 */
		var processOffer = function(offer, hotel_id, providers) {
			// create a unique id for this hotel and provider and
			// append the tracking hash
			var vendor_id = offer.vendor.replace(/_/, '-');
			var id = hotel_id + '_' + vendor_id + '_' + offer.code;

			// initialize a check rates instance for each provider
			var ad = TACheckRates.singleton(id);
			ad.setMetaDataMulti({
				author_name: offer.author,
				display_name: offer.vendor,
				external_url: offer.url
			});

			var params = {
				name: id,
				vendor: offer.vendor,
				checked: offer.checked ? 'checked' : '',
				code: offer.code
			};

			$j(providers).prepend($j.tmpl(BookingBuddy.Strings.CheckRates.DefaultTemplate, params));
		};


		/**
		 * Loads checkrates for multiple forms in one request!
		 * expects an associative array for an input 'args'
		 *
		 * @param {array} args
		 *   ids {integer array} array of integers (hotel ids)
		 *   inputs {jqueryObject array} array of inputs linked to the hotel ids
		 *   type {string} type of ids passed ('ta' or 'stm')
		 */
		var loadCheckRates = function(args) {
			if (args.ids.length === 0) {
				return;
			}

			var options = {
				ids: args.ids,
				type: args.type,
				mcid: BookingBuddy.taCheckRatesMCID,
				max_providers: 6 // BBS-2242
			};

			$j.bbget('TACheckRates.fetchMultiple', options, function(results) {
				var html = '',
					offers = null,
					max_length = null,
					$providers = null;

				if (!results || results.error) {
					$j.each(args.inputs, function() {
						noCheckRates(this);
					});
				} else {
					$j.each(args.ids, function(index, element) {
						// find it's mate in the result
						if (element in results) {
							$providers = $j(args.inputs[index]).find('.check_rates_providers');
							$providers.html('');
							// do something w/ the results
							offers = results[element];
							max_length = offers.length;
							for (var i = max_length; i--;) {
								processOffer(offers[i], element, $providers);
								$j(args.inputs[index]).addClass('loaded');
							}
						} else {
							noCheckRates(args.inputs[index]);
						}
					});
				}
			});
		};

		/**
		 * Make an AJAX request to load ta check rates for the specified hotel.
		 * If the check rates container div has the class 'use_taid', check
		 * rates will be looked up by ta id, otherwise by stm id
		 *
		 * @param $container - jquery check rates container object
		 * @param hotelId - id to load check rates providers for
		 */
		var loadCollapsedCheckRates = function($container, hotelId) {

			var html = '';

			// need to pass the mcid
			var options = {
				id: hotelId,
				type: $container.hasClass('use_taid') ? 'ta' : 'stm',
				max_providers: 6 // BBS-2242
			};

			if (BookingBuddy.taCheckRatesMCID !== undefined) {
				$j.extend(options, {mcid: BookingBuddy.taCheckRatesMCID});
			}

			// fetch ta check rates for the specified id
			$j.bbget('TACheckRates.fetch', options, function(results) {
				if (!results || results.error) {
					html = 'We were unable to load providers at this time';

				} else {

					$j.each(results, function(idx, data) {

						// create a unique id for this hotel and provider and
						// append the tracking hash
						var vendorId = data.vendor.replace(/_/, '-');
						var id = hotelId + '_' + vendorId + '_' + data.code;

						// initialize a check rates instance for each provider
						var ad = TACheckRates.singleton(id);
						ad.setMetaDataMulti({
							author_name: data.author,
							display_name: data.vendor,
							external_url: data.url
						});

						var params = {
							name: id,
							vendor: data.vendor,
							checked: data.checked ? 'checked' : '',
							code: data.code
						};

						html += $j.tmpl(BookingBuddy.Strings.CheckRates.DefaultTemplate, params);
					});
				}

				$container.find('.check_rates_providers').html(html);
				loadedHotels[hotelId] = html;
			});
		};

		// load the STM and TA checkrates in batches
		loadCheckRates(ta);
		loadCheckRates(stm);
	},

	/**
	 * Create the dhtml google map popup. Additionally peform tracking when the
	 * google map is initially shown.
	 */
	googleMaps: function() {
		// google map dhtml popup
		if ($j("#dhtml_gmap_wrapper").length) {
			var dhtmlPopup = DHTMLPopup_Factory.create({ id: "dhtml_gmap_wrapper" , type: "googleMap" });

			// NOTE: ideally we would eliminate the custom event and use $.publish directly
			// but other products might be counting on the custom event right now
			dhtmlPopup.popup.bind('dhtmlpopup:show', function() {
				$j.publish('/dhtmlPopup/googleMap');
			});
		}

		// check to see if there are any google maps on the page
		var maps = $j('.google_map');
		if (maps.length) {
			// create a global callback function for the google maps api to notify the
			// maps on the page the external script has finished loading
			BookingBuddy.Maps.googleMapLoaded = function() {
				maps.trigger(GoogleMap.constants.EVENT_SCRIPT_LOADED);
			};
			// pass a callback function for google maps to execute since the
			// callback handling by jQuery.getScript is very unreliable
			$j.getScript('http://maps.google.com/maps/api/js?v=3.2&sensor=false&client=gme-expedia&callback=BookingBuddy.Maps.googleMapLoaded');
		}
	},

	/**
	 * Set up dhtml popups for the white label samples on the affiliate landing page
	 */
	affiliateLanding: function() {
		var wrapper = $j('.become_affiliate');
		if (wrapper[0]) {
			var dest360 = DHTMLPopup_Factory.create({ id: "dest_360_popup" , type: "base" });
			dest360.setPageMask('page_mask');
			dest360.maskClick('page_mask');

			var nydn = DHTMLPopup_Factory.create({ id: "nydn_popup" , type: "base" });
			nydn.setPageMask('page_mask');
			nydn.maskClick('page_mask');

			wrapper.find('.white_label.dest_360').click(function(e) {
				e.preventDefault();
				e.stopPropagation();
				dest360.show();
			});

			wrapper.find('.white_label.daily_news').click(function(e) {
				e.preventDefault();
				e.stopPropagation();
				nydn.show();
			});
		}
	},

	/**
	 * Initialize all smartForms
	 */
	forms: function() {
		var BSH 	= BookingBuddy.submitHandlers,
			BBSH 	= BookingBuddy.bbSubmitHandlers;

		// Limit the no default dates to pages that contain hotel listings.
		// All forms on a page need this parameter set for it to do anything,
		// so pass it for all smart forms, but only enable it on pages with hotel
		// listings. This wasn't completely finalized, but product is okay with it
		// the way it is, so it rolls as is.
		var hideDefaultDates = $j('.hotel_listing')[0];

		// When a user leaves the page we should close any prepop windows
		$j(window).bind("beforeunload", function() {
			BBSH.closePrepops();
		});

		// settings for a one window one search form
		$j(".smartForm.oneSearch").each( function() {
			$j(this).smartFormFactory( {
				noDefaultDates: hideDefaultDates,
				submitTarget: "_blank",
				submitHandler: function(form, target, e) {
					BBSH.submitForm(form, target, e);
				}
			});
		});

		$j(".smartForm.classicSearch").each( function() {
			// use oneSearch to control the dropdown
				$j.dhtmlDropdown.utils.classicSearch($j(this), $j('#BBAdDropdown'));

				$j('#BBAdDropdown').dhtmlDropdownFactory(BookingBuddy.dropdownOptions, {
					tpt_premium: BookingBuddy.Strings.DHTMLDropdown.PremiumAdTemplate,
					tpt_basic: BookingBuddy.Strings.DHTMLDropdown.BasicAdTemplate,
					tpt_placeholder: BookingBuddy.Strings.DHTMLDropdown.PlaceholderAdTemplate,
					list_position: 'top'
				});

				$j(this).smartFormFactory( {
					noDefaultDates: hideDefaultDates,
					submitHandler: function(form, target, e) {
						BBSH.submitForm(form, target, e);
					}
				});
		});

		// want smartForm functionality but just use the html action and such
		$j(".smartForm.noHandler").each(function() {
			$j(this).smartFormFactory({
				noDefaultDates: hideDefaultDates,
				submitHandler: function(form, target, e) {
					return BSH.noHandler(form, target, e);
				}
			});
		});

		// Check Hotel Rates popup
		if($j("#dhtml_hotel_search").length > 0) {
			DHTMLPopup_Factory.create( { id: "dhtml_hotel_search" , type: "checkRates" });
		}

		// Check Hotel Rates widget form
		// NOTE: This can appear other places than the dhtml_hotel_search popup
		// so account for those as well
		$j(".smartForm.checkHotelRates").each( function() {
			$j(this).smartFormFactory( {
				noDefaultDates: hideDefaultDates,
				submitHandler: function(form, target, e) {
					BBSH.submitForm(form, target, e);
				}
			});
		});

		// Hotel reviews
		if ($j("#hotel_reviews_container:not(.ta_id)").length > 0) {

			$j("#hotel_reviews_container:not(.ta_id) .review_blurb").each(function() {
				var elem_id = $j(this).attr("id");
				var hotel_id = elem_id.substr(elem_id.lastIndexOf("_")+1);

				$j.bbget('HotelReviews.reviews', {hotel_id: hotel_id, num_reviews: 1}, function(data) {

					if ($j.isNullOrUndefined(data) || data.length === 0) {
						$j('#review_blurb_' + hotel_id).html("");
						return;
					}

					try {
						$j('#review_blurb_' + hotel_id + " .review_title a").attr("href", data['0'].review_url);
						$j('#review_blurb_' + hotel_id + " .review_title span.review_title").html(data['0'].title);
						if(data['0'].author=="0") {
							$j('#review_blurb_' + hotel_id + " .pub_info span.author_section").remove();
						} else {
							$j('#review_blurb_' + hotel_id + " .pub_info span.review_author").html(data['0'].author);
						}
						$j('#review_blurb_' + hotel_id + " .summary span.review_summary").html(data['0'].review);
						$j('#review_blurb_' + hotel_id + " .summary a").attr("href", data['0'].review_url);
						$j('#review_blurb_' + hotel_id).show();

					} catch (e) {
						BBDebug.log("Error getting hotel review: " + hotel_id, e);
						$j('#review_blurb_' + hotel_id).html("");
					}
				});
			});
		}

		// hotel reviews based on ta ids
		if ($j("#hotel_reviews_container.ta_id").length > 0) {
			$j("#hotel_reviews_container.ta_id .review_blurb").each(function() {
				var elem_id = $j(this).attr("id");
				var hotel_id = elem_id.substr(elem_id.lastIndexOf("_")+1);

				$j.bbget('HotelReviews.reviewsByTAId', {ta_hotel_id: hotel_id, num_reviews: 1}, function(data) {
					if ($j.isNullOrUndefined(data) || data.length === 0) {
						$j('#review_blurb_' + hotel_id).html("");
						return;
					}

					try {
						// dates are formatted like: "2006-04-11T00:00:00-04:00"
						// convert to : "Apr 11 2006"
						var date_time = data[0].date.split('T');
						var date = date_time[0].split('-');
						var d = new Date(date[0], date[1] - 1, date[2]);
						var formatted_date = d.toDateString().substring(4);

						$j('#review_blurb_' + hotel_id + " .review_title a").attr("href", data['0'].url);
						$j('#review_blurb_' + hotel_id + " .review_title span.review_title").html(data['0'].title);
						if(data['0'].author=="0") {
							$j('#review_blurb_' + hotel_id + " .pub_info span.author_section").remove();
						} else {
							$j('#review_blurb_' + hotel_id + " .pub_info span.review_author").html(data['0'].author);
						}
						$j('#review_blurb_' + hotel_id + " .pub_info span.pub_date").html(formatted_date);
						$j('#review_blurb_' + hotel_id + " .summary span.review_summary").html(data['0'].summary);
						$j('#review_blurb_' + hotel_id + " .summary a").attr("href", data['0'].url);
						$j('#review_blurb_' + hotel_id).show();

					} catch (e) {
						BBDebug.log("Error getting hotel review: " + hotel_id, e);
						$j('#review_blurb_' + hotel_id).html("");
					}
				});
			});
		}

		// Step 1 search form. Open tabbed search window AND go to step 2
		$j(".smartForm.skipStep2").each(function() {
			$j(this).smartFormFactory({
				noDefaultDates: hideDefaultDates,
				submitHandler: function(form, target, e) {
					return BBSH.skipStep2(form, target, e);
				}
			});
		});

		// Opens the tabbed browsing window and does nothing to the page
		$j(".smartForm.openTabbedBrowsing").each(function() {
			if($j.browser.msie && $j.browser.version == 6) {
				$j(this).smartFormFactory({
					noDefaultDates: hideDefaultDates,
					submitHandler: function(form, target, e) {
						return BBSH.SkipStep2(form, target, e);
					}
				});
			} else {
				$j(this).smartFormFactory({
					noDefaultDates: hideDefaultDates,
					submitHandler: function(form, target, e) {
						return BBSH.bbsToTabbed(form, target, e);
					}
				});
			}
		});

		// Tabbed search page form
		$j(".smartForm.tabSearch").each(function() {
			$j(this).bind("SmartForm:invalid", function(event) {
				$j("a.edit_details").click();
			});
			$j(this).bind( "SmartForm:ready", function(event){
				var _invalidLS = function(evt) {
					$j(evt.currentTarget).data("smartElement").smartForm.preValidate();
					$j(evt.currentTarget).unbind("LocationSuggest:invalid:quickcheck", _invalidLS);
				};
				$j(this).data("smartForm").getLSElements().bind("LocationSuggest:invalid:quickcheck", _invalidLS) ;
				$j(this).data("smartForm").getLSElements().bind("LocationSuggest:valid:quickcheck", function(evt) {
					$j(evt.currentTarget).unbind("LocationSuggest:invalid:quickcheck", _invalidLS);
				});
				$j(this).data("smartForm").preValidate();
			});

			// START: BBS-2618
			// Since the dropdown for the new code is initialiazed in
			// this.tabBrowsing, we don't wanna load it here.  It will
			// just confuse the dhtml element b/c BookingBuddy.dropdownOptions
			// isn't currently set.
			if (!BookingBuddy.tabBrowsing) {
			// END: BBS-2618

			// hook up the dropdown to the checkbox
			$j.dhtmlDropdown.utils.revSearch($j(this), $j('#BBAdDropdown'));
			$j('#BBAdDropdown').dhtmlDropdownFactory(BookingBuddy.dropdownOptions, {
				tpt_premium: BookingBuddy.Strings.DHTMLDropdown.PremiumAdTemplate,
				tpt_basic: BookingBuddy.Strings.DHTMLDropdown.BasicAdTemplate,
				tpt_placeholder: BookingBuddy.Strings.DHTMLDropdown.PlaceholderAdTemplate,
				list_position: 'bottom'
			});

			// START: BBS-2618
			}
			// END: BBS-2618

		});

		/**
		 * Step 2 search page form
		 * All user interaction should be contained within the form
		 * Things should be handled as form.button.click instead of button.click
		 */
		$j(".smartForm.bbSearch").each( function() {
			var $form = $j(this);

			$form.bind("SmartForm:invalid", function(event){
				$j("a.edit_details").click();
			});

			$form.bind("SmartForm:ready", function(event){
				// Need to check for #invalidLSFields
				var lsInvalidFields = $j("#invalidLSFields").html();
				if(!$j.emptyString(lsInvalidFields)) {
					var smartForm = $j.data( this,"smartForm");
					var elems = lsInvalidFields.split(",");
					var elemFound = false;
					$j.each( elems, $j.proxy(function(index, elemId) {
						var elem = this.invalidateElement(elemId, "locationSuggest");
						if(!$j.isNullOrUndefined(elem)) {
							$j(elem.currentElement).bind('LocationSuggest:invalid', function(event){
								$j("a.edit_details").click();
							});
						}
					}, smartForm));
				}

				var _invalidLS = function(evt) {
					$j(evt.currentTarget).data("smartElement").smartForm.preValidate();
					$j(evt.currentTarget).unbind("LocationSuggest:invalid:quickcheck", _invalidLS);
				};
				$form.data("smartForm").getLSElements().bind("LocationSuggest:invalid:quickcheck", _invalidLS) ;
				$form.data("smartForm").getLSElements().bind("LocationSuggest:valid:quickcheck", function(evt) {
					$j(evt.currentTarget).unbind("LocationSuggest:invalid:quickcheck", _invalidLS);
				}  );
			});

			$j(this).smartFormFactory( {
				noDefaultDates: hideDefaultDates,
				submitHandler: function(form, target, e) {
					BBSH.submitForm(form, target, e);
					$j.publish('/submit/step2');
				}
			});

			// Hook up the select all check box
			var $selectAll = $j("#" + $j(this).attr("id") + "_select_all");
			$selectAll.change( $j.proxy(function(e) {
				var check = $j(e.currentTarget).is(':checked');
				$j(this).find(".adIdCheckBox").attr("checked",  check);
			}, this));

			// Toggle the checkbox on click of an Ad image
			$j(this).find('.BBInputImage.toggleAdCheck').click( function(e) {
				var ad_id = $j(this).find("span.adId").html();
				if ($j.emptyString(ad_id)) {
					ad_id = $j(this).parent().find("span.adId").html();
				}
				var checkbox = $j('#check_' + ad_id);
				checkbox.attr("checked", !checkbox.is(":checked"));
				checkbox.change();
				e.preventDefault();
				e.stopPropagation();
				return false;
			});

			// Checks or unchecks the dropdown checkbox based on selection
			// of the "Select All" checkbox
			$j(this).find('.BBInputCheckBox.toggleAdCheck').change( function(e) {
				var all_ct = $j(".BBInputCheckBox.toggleAdCheck").length;
				var checked_ct = $j(".BBInputCheckBox.toggleAdCheck:checked").length;
				if ($selectAll[0]) {
					$selectAll.get(0).checked = all_ct == checked_ct;
				}
			});

			// hook up the dropdown to the checkbox
			$j.dhtmlDropdown.utils.revSearch($j(this), $j('#BBAdDropdown'));
			$j('#BBAdDropdown').dhtmlDropdownFactory(BookingBuddy.dropdownOptions, {
				tpt_premium: BookingBuddy.Strings.DHTMLDropdown.PremiumAdTemplate,
				tpt_basic: BookingBuddy.Strings.DHTMLDropdown.BasicAdTemplate,
				tpt_placeholder: BookingBuddy.Strings.DHTMLDropdown.PlaceholderAdTemplate
			});

			// Find a dropdown entry matching the QS param and make it selected and
			// checked by default
			if (BookingBuddy.getQSParam('placeFirst')) {
				var fare_airline = BookingBuddy.getQSParam('placeFirst');
				var $ddl = $j('#BBAdDropdown').data('dhtmlDropdown');

				if ($ddl) {
					$ddl.updateCurrent(fare_airline);
				}
			}

			$selectAll.attr('checked', true).change();
			$j('#'+BookingBuddy.searchMode+'_more_options').removeAttr('checked');
		});

		// initialize the Hotel Listing Filter Form
		$j('#hotel_listing_filter').hotelListing();

		// intitialize the hotel details tabs box
		$j("#hotel_details_tabs").propertyDetailTabs();

		/**
		 * Handles the bestChance calendars found on the fare details page.
		 * The Fare data is populated in a hidden div with the id expFaresDates.
		 * - datepicker highlighting for bestChance dates
		 * - adds color key to bottom of datepicker
		 */
		if ($j('#expFaresDates').length > 0) {
			// set up the best chance calendars (used on the fare details page)
			var BEFD = $j('#expFaresDates')[0] ? JSON.parse($j('#expFaresDates').html()) : "";

			/**
			 * Run on every shown date in the jquery datepicker so this should be snappy.
			 * Determines if the given date is in the bestChance listing. if so a css class
			 * to make that date stand out is applied.
			 *
			 * @param {Date} date - date object for the calendar day you want to compare with the cache
			 *
			 * @return {Array}
			 */
			var bestChanceShowDay = function (date) {
				var type = $j(this).hasClass('departureDate') ? 'departure' : 'destination';
				var compare = date.getMonth() + '-' + date.getDate() + '-' + date.getFullYear();

				if (BEFD[type] && $j.inArray(compare, BEFD[type]) !== -1) {
					return [true, 'expFareDate'];
				}
				return [true, ''];
			};

			/**
			 * Returns the first date available for the given type, if no dates
			 * are available the original date inputed will be returned
			 *
			 * @param {string} type - either 'departure' or 'destination'
			 * @param {string} orig - original date to be returned if no dates are available
			 *
			 * @return {Date}
			 */
			var bestChanceFirstDate = function (type, orig) {
				if (BEFD && BEFD[type][0]) {
					var vals = BEFD[type][0].split('-');
					return new Date(vals[2], vals[0], vals[1]);
				}
				return orig;
			};

			/**
			 * Returns the postfix html for the bestChance calendar.
			 * will return the html if there is data for the matching type
			 * else it will return an empty string.
			 *
			 * @param {string} type - either 'destination' or 'departure'
			 *
			 * @return {string} - post fix html
			 */
			var bestChancePostfix = function (type) {
				if (BEFD && BEFD[type].length > 0) {
					return '<div class="cal_legend"><span class="cal_key"></span><span class="cal_key_text">Best chance to get low fare.</span></div>';
				} else {
					return '';
				}
			};

			/**
			 * Apply custom changes to the bestChance datepickers
			 * - Add custom postfixed html footer to calendar
			 * - Add custom css to bestChance dates
			 * - Pre-set calendar to 1st bestChance date
			 */
			$j('.hasDatepicker.bestChance').each(function (index, elem) {
				var type = $j(this).hasClass('departureDate') ? 'departure' : 'destination';
				$j(elem).datepicker('option', {
					'calendarPostfix' : bestChancePostfix(type),
					'beforeShowDay': bestChanceShowDay
				});
				// set the date for the 1st best chance value
				$j(elem).datepicker('setDate', bestChanceFirstDate(type, $j(this).val()));
			});
		}

		var $dealClicks = $j('.bbdn_deal_click');
		if ($dealClicks[0]) {
			$dealClicks.click(function(e) {
				e.preventDefault();

				var data = {},
					$omnitureData = $j(this).closest('.deal_container').find('.omniture_bbd');

				if ($omnitureData[0]) {
					data = JSON.parse($omnitureData.text());
				}

				$j.publish('/click/bbdeal', [data, this]);
			});
		}

	},

	popupHotelPlanner: function() {
		// Ensure that num_rooms does not default to "3":
		num_rooms = $j.smartFormsBBSavedSearchData.getCurrentState().nrms;
		if(num_rooms == '3') {
			$j('#hotel_num_rooms').val('1').change();
		}
	},

	/**
	 * Binds a click event handler that displayes edit details on step2
	 * Uses that event handler when edit details has openOnLoad class
	 */
	openLocationDetails: function() {
		$j('.step_2_ads .toggle_edit').click(function(e) {
			e.preventDefault();
			$j('.step_2_ads #edit_details_tagline, .step_2_ads #dhtml_edit_step2').slideToggle();
		});
		if ($j('.step_2_ads .toggle_edit').hasClass('openOnLoad')) {
			$j('.step_2_ads .toggle_edit').click();
		}
		var classicStep2 = $j('.step_2_ads.air.classic');
		if (classicStep2[0]) {
			var toggleMore = classicStep2.find('.toggle_more a');
			toggleMore.click(function(e) {
				e.preventDefault();
				classicStep2.find('.more_options').slideToggle("fast", function() {
					if ($j(this).is(':visible')) {
						toggleMore.text('more options-');
					} else {
						toggleMore.text('more options+');
					}
				});
			});
		}
	},

	launchDHTMLSub: function() {
		$j(document).ready(function () {
			var dhtmlsignup_inline;
			if ($j('#hotel_watch_dhtml').length == 1) {
				dhtmlsignup_inline = new AjaxSubscription('hotel_watch_dhtml', {
					offset: {left: 0, top: 50},
					cookie_name: null
				});

				dhtmlsignup_inline.popup.setPageMask('page_mask');
			} else if ($j('#dhtmlsignup_inline').length == 1) {
				dhtmlsignup_inline = new AjaxSubscription('dhtmlsignup_inline');
			}

			if (dhtmlsignup_inline) {
				dhtmlsignup_inline.step1();
			}
		});
	},


	// START: BBS-1970
	bbs1970: function() {
		var $dhtml = $j('#bbs1970_widget');
		var $links = $j('#fare_details_widget .airline_link');
		var popup = null;

		if ($dhtml[0] && $links[0]) {
			$links.click(function() {
				var $this = $j(this);
				// launch the popup in a new window
				popup = new ExternalPopup({
					url: $this.find('span.hide.url').text(),
					width: 1000,
					height: 1000,
					placement: ExternalPopup.PLACEMENTS.PARENT
				});

				// launch the dhmtl popup
				modal = DHTMLPopup_Factory.create({id: $dhtml.attr('id'), type: 'base'});
				modal.setPageMask('page_mask');
				modal.show();

				// instatiate tracking
				BookingBuddy.Tracking.trackBBSearch([$this.find('span.hide.id').text()]);
			});
		}
	},
	// END: BBS-1970

	// START: BBS-2200
	bbs2200: function() {
		if ((BookingBuddy.abTests !== undefined)
			&& (BookingBuddy.abTests.bbs2200 !== undefined)
			&& (BookingBuddy.abTests.bbs2200.recipe !== 'default')) {

			// BBS-2441: This check is to allow the old and new tab browsing
			// pages to support loading this AB test. The old code expects
			// "destination_id" to be set, whereas the new code expects
			// "destinationId"
			if (BookingBuddy.abTests.bbs2200['destinationId']) {
				this.bbs2200WithNewTabBrowsing();
				return;
			}

			// Determine placement (rank) based on $bbs2200_where
			// Load the redirect
			ad = BBAd.singleton('bbs_2200'); // Redirect to the hotel_specific page!
			ad.setMetaDataMulti({
				"display_name": "Hotels",
				"tab_logo": BookingBuddy.abTests.bbs2200.img,
				"rank": 1
			});

			var $tabContainer = $j('#tabs_container');
			var $hotelContainer = $j('#hotel_container');
			var bbs2200_ads_clicked = 0, bbs2200_count = BookingBuddy.abTests.bbs2200.post_after;

			// Adjust the size of tabs_container to accommodate the width of the hotel cross-sell
			function adjustContainerWidth() {
				if ($hotelContainer) {
					// Make sure we've got enough space for the hotel cross-sell tab & container
					var hotelWidth = $hotelContainer.width();
					if ($j(window).width() < 1000 + hotelWidth) {
						$tabContainer.css('width', $tabContainer.width() - hotelWidth);
					}
				}
			}

			// Adjust the width of the tab_container to reflect the new hotel cross-sell tab
			// when the screen is resized
			$tabContainer.bind('tabBrowsing:windowResized', adjustContainerWidth);

			$tabContainer.bind('tabBrowsing:tabDisplayed', function(element, data) {
				// Deactivate the X-sell if a real tab is clicked:
				$j('ul li.active', $hotelContainer).removeClass('active');
			});

			$hotelContainer.bind('tabBrowsing:tabDisplayed', function(element, data) {
				// Deactiveate the real tab if the X-sell is clicked:
				$j('ul li.active', $tabContainer).removeClass('active');
			});

			$hotelContainer.bind('tabBrowsing:loaded', function() {
				// Update the hotel cross-sell tab's URL and force it to open in an IFRAME
				var tab = $j('li:first', this).data('tabObj').tab;
				// HACKY SOLUTION BELOW:
				// Use of "&ab=bbs_2026_a" is intended to force the user into 2026-A
				// to avoid a stupid bug which fatals this page
				tab.ad_url = "http://rd.bookingbuddy.com?r=bbs_2200&pt=1&inTab=1&allow_framed=1&cityId=" + BookingBuddy.abTests.bbs2200.destination_id+'&ab=bbs_2026_a';
				tab.shouldPopNewWindow = false;
			});

			$hotelContainer.tabBrowsing({
				tabIds: [ 'bbs_2200' ],
				adIds: [ 'bbs_2200' ],
				shouldLoadFirstTab: false,
				supressInstructionsHtml: true
			});

			// Keep track of FS clicks -- once the count is correct
			// we want to present the hotel X-sell tab:
			$tabContainer.bind('tabBrowsing:tabDisplayed', function () {
				bbs2200_ads_clicked++;
				if (bbs2200_ads_clicked === bbs2200_count) {
					$j('#hotel_container').show('slow');
				}
			});
		}

	},

	// BBS-2441: The code below is for loading the BBS-2200 AB test on
	// the new tab browsing page. This is only called from the bbs2200
	// function after verifying we're on the new tab browsing page. When
	// the old tab browsing code goes away, the above function should
	// be removed the this function should be renamed "bbs2200"
	bbs2200WithNewTabBrowsing: function() {
		var bbs2200 = BookingBuddy.abTests.bbs2200;
		if (!bbs2200 || !bbs2200['destinationId']) {
			return;
		}

		var $tabsContainer = $j('#tabsContainer'),
			$oneOffTabContainer = $j('<div />', {
				'id': 'oneOffTabContainer',
				'class': 'hide'
			});

		if (!$tabsContainer[0]) {
			return;
		}

		if (bbs2200['rightOfTabs']) {
			$tabsContainer.after($oneOffTabContainer);
		} else {
			$tabsContainer.before($oneOffTabContainer);
		}

		var showAfterXSearches = bbs2200['showAfterXSearches'],
			destinationId = bbs2200['destinationId'],
			adClickCount = 0;

		// set up a one-off BBAd object for redirecting to hotel_specific.php
		var ad = BBAd.singleton('bbs_2200');
		ad.setMetaDataMulti({
			"display_name": "Hotels",
			"tab_logo": 'http://i.slimg.com/bookingbuddy/ab_images/2200/hotel-cross+sell+tab.png',
			"rank": 1
		});


		var handleResize = function() {

			if ($oneOffTabContainer.is(':visible')) {
				// Make sure we've got enough space for the hotel cross-sell tab & container
				var width = $oneOffTabContainer.width();
				if ($j(window).width() < 1000 + width) {
					$tabsContainer.css('width', $tabsContainer.width() - width);
				}
			}
		};
		// Adjust the size of tab container to accommodate the width of the
		// one-off ad being shown 
		$tabsContainer.bind('tabBrowsing:windowResized', handleResize);

		$tabsContainer.bind('tabBrowsing:tabDisplayed', function() {
			// Deactivate the cross-sell tab if a real tab is clicked
			$j('ul li.active', $oneOffTabContainer).removeClass('active');

			// show the one-off tab if enough tabs have been opened
			if (++adClickCount === showAfterXSearches) {
				$oneOffTabContainer.show('slow');
				handleResize();
			}
		});

		// Deactiveate the real tab if the X-sell is clicked
		$oneOffTabContainer.bind('tabBrowsing:tabDisplayed', function() {
			$j('ul li.active', $tabsContainer).removeClass('active');
			handleResize();
		});

		$oneOffTabContainer.bind('tabBrowsing:loaded', function() {
			// Update the hotel cross-sell tab's URL and force it to open in an IFRAME
			var tab = $j('li:first', this).data('tabObj').tab;
			// HACKY SOLUTION BELOW:
			// Use of "&ab=bbs_2026_a" is intended to force the user into 2026-A
			// to avoid a stupid bug which fatals this page
			tab.adUrl = "http://rd.bookingbuddy.com?r=bbs_2200&pt=1&inTab=1&allow_framed=1&cityId=" + destinationId + '&ab=bbs_2026_a';
			tab.shouldPopNewWindow = false;
		});

		// initialize the tab browsing
		var properties = {
			searchMode:      BookingBuddy.searchMode,
			siteRedirectUrl: BookingBuddy.redirectUrl,
			headerElem:      $j('#tabHeader'),
			panelElem:       $j('#tabPanel'),
			dropdownElem:    $j(),
			tabIds:          ['bbs_2200'],
			adList:          {'bbs_2200': ad},
			searchParams:    BookingBuddy.queryString
		};

		var options = {
			loadFirstTab: false,
			suppressInstructionsHTML: true
		};

		$oneOffTabContainer.tabBrowsing(properties, options);
	},
	// END: BBS-2200

	// START: BBS-2026
	bbs2026: function() {
		var options = {
			bbs2026: true,
			controls: {


				city: {
					input: '.bbs2026 .hotel_listing .destination_city',
					type: 'textbox',
					hide: false,
					options: {
						locs_field: 'city',
						bbs2026: true,
						apply_object: '#btn_update_id'
					}
				},

				/**
				 * Hotel Amenities
				 */
				amenities: {
					input: "#hotel_amenities",
					type: "checkbox",
					hide: false,
					options: {
						"locs_field": 'amenities',
						'collapsable': true,
						'startCollapsed': true
					}
				},
				/**
				 * Star Rating multi-select checkbox list
				 */
				star_rating: {
					input: '#hotel_star_rating',
					type: 'checkbox',
					hide: false,
					options: {}
				},

				/**
				 * TripAdvisor Rating double slider
				 */
				ta_rating: {
					input: '#hotel_ta_rating',
					type: 'slider',
					hide: false,
					options: {
						message: '#{low} - #{high}',
						locs_field: 'rating',
						has_image: false,
						slider_options: {
							min: 1,
							max: 5,
							step: 0.5,
							values: [1,5],
							range: true
						}
					}
				},

				/**
				 * Average Prive double slider
				 */
				avg_price: {
					input: '#hotel_avg_price',
					type: 'slider',
					hide: false,
					options: {
						message: "$#{low} - $#{high}",
						locs_field: 'avg_price',
						has_image: false,
						slider_options: {
							min: 10,
							max: 500,
							step: 10,
							values: [10,500],
							range: true
						}
					}
				},

				/**
				 * Hotel Name filter input textbox
				 */
				hotel: {
					input: '#hotel_arrival_name',
					type: 'textbox',
					hide: false,
					options: {
						locs_field: 'hotel_name'
					}
				},

				/**
				 * Pager controls
				 */
				pager_top: {
					input: '#hotel_pager_top',
					type: 'pager',
					hide: false,
					options: {
						results_limit: 10,
						locs_field: 'offset'
					}
				},
				pager_bottom: {
					input: '#hotel_pager_bottom',
					type: 'pager',
					hide: false,
					options: {
						results_limit: 10,
						locs_field: 'offset'
					}
				},

				/**
			 	 * Results sorting control
			 	 */
				sorter: {
					input: '#hotel_sorter',
					type: 'sorter',
					hide: false,
					options: {

						fields: {

							star_rating: {
								text: 'Star Rating',
								order: 'desc'
							},
							rating: {
								text: 'Popularity',
								order: 'desc'
							},
							price_avg: {
								text: 'Price',
								order: 'asc'
							}
						}
					}
				}

			} // END: Controls
		};

		$j('.bbs2026 #hotel_results_filters').hotelListing(options);
	},
	// END: BBS-2026

	/**
	 * Initialize the tab browsing plugin and dhtml dropdown
	 * BBS-2441: Note this only gets executed when loading the new tab browsing page
	 */
	tabBrowsing: function() {
		var TB = BookingBuddy.tabBrowsing;
		if (!TB) {
			return;
		}

		// initialize BBAd objects from the ad data
		var adList = {};
		for (adId in TB.adData) {
			adList[adId] = BBAd.singleton(adId).setMetaDataMulti(TB.adData[adId]);
		}

		// initialize the dropdown
		var $dhtmlDropdown = $j('#BBAdDropdown');
		$dhtmlDropdown.dhtmlDropdownFactory(TB.dropdownAdIds, {
			tpt_premium:     BookingBuddy.Strings.DHTMLDropdown.PremiumAdTemplate,
			tpt_basic:       BookingBuddy.Strings.DHTMLDropdown.BasicAdTemplate,
			tpt_placeholder: BookingBuddy.Strings.DHTMLDropdown.PlaceholderAdTemplate,
			list_position:   'bottom'
		});

		// initialize the tab browsing
		var properties = {
			searchMode:      BookingBuddy.searchMode,
			siteRedirectUrl: BookingBuddy.redirectUrl,
			headerElem:      $j('#tabHeader'),
			panelElem:       $j('#tabPanel'),
			dropdownElem:    $dhtmlDropdown,
			tabIds:          TB.tabAdIds,
			adList:          adList,
			searchParams:    BookingBuddy.queryString
		};

		// primitive attempt at converting the qs string param to a boolean
		var noInstructions = !!Number(BookingBuddy.getQSParam('noInstr'));

		var options = {
			delayTabFadeIn: TB.traqEnabled,
			suppressInstructionsHTML: noInstructions,
			loadFirstTab: noInstructions,
			showHotelCount: false
		};

		var afName = BookingBuddy.affiliateName;
		if (afName && afName !== 'bookingbuddy_com') {
			options.logoLinkAffiliate = afName;
		}

		$j('#tabsContainer').tabBrowsing(properties, options);

		if (TB.linkshareEnabled) {
			BookingBuddy.Tracking.trackLinkShareClick('search');
		}

	},

	//START: SEM Blurb DHTML Popup
	semBlurbPopup: function() {
		var $popupDiv = $j(".semblurb.dhtml_popup");
		if($popupDiv[0]){
		    $j('body').append($popupDiv);

		    $j(".blurb_clicky").click(function() {
				// This should really be changed to make this valid HTML
				var blurbIndex = $j(this).attr("blurbid");
				var fullTextPopup = new DHTMLPopup('full_text_dhtml_' + blurbIndex, false);
				fullTextPopup.setPageMask('page_mask');
				fullTextPopup.show();
				//Need to make sure that the top is not set to a negative value by the centering code
				$j('#full_text_dhtml_' + blurbIndex).css({top: '50px'});
				return false;
		    });
		}
	}
	//END: SEM Blurb DHTML Popup
};

BBAffInit = {

	all: function() {
		// Forms should be initialized first to make sure the $j.smartFormsBBSavedSearchData is initialized
		this.forms();
		this.targeting();
		this.deals();
		
	},

	allUK: function() {
		BookingBuddy.UK.init();

		// searchMode-specific initializations
		switch (BookingBuddy.searchMode) {
			case 'hotel':
				BBUKInit.affiliate_hotel_listing();
				BBUKInit.targeting();
				BBUKInit.forms();
				BBUKInit.hotel();
				BBUKInit.taCheckRates();
				break;
			case 'holiday':
				BBUKInit.targeting();
				BBUKInit.forms();
				BBUKInit.holiday();
				break;
			case 'air':
			default:
				BBUKInit.targeting();
				BBUKInit.forms();
				break;
		}
	},


	/**
	 * Initialize BBTargeting and BBUKTargeting
	 */
	targeting: function () {

		var inputIDS = [];

		$j('.BBTargeting.smartElement').each(function() {
			inputIDS.push(this.id);
		});
		
		var	targeting = new BBTargeting(inputIDS);
	},

	/** 
	 * Initialize deals
	 * 
	 */
	deals: function() {

		var $dealsDiv = $j('#BB-deals-sidecar-deals-section');
		if ($dealsDiv[0]) {

			var deal_options = {
				'placement' : 'bbdn_' + BookingBuddy.affiliateName + '_wlabelside'
			};

			var config_options = {
				'target_input' : BookingBuddy.searchMode == 'car' ? 'pickup_city' : BookingBuddy.searchMode == 'cruise' ? 'destination' : 'arrival_city',
				'divDealsHeader' : 'BB-deals-sidecar-header',
				'containerDivs' : $dealsDiv.first(),
				'onSuccess': function() {
					// Display city name if we got one
					if (this.cityName) {
						$j('#BB-deals-sidecar-header').show();
					} else {
						$j('#BB-deals-sidecar-header').hide();
					}

					$j('#BB-deals-sidecar').show();
				},

				'onError': function() {
					$j('#BB-deals-sidecar-header').hide();
				}
			};


			// BookingBuddy.loadAffiliateDeals is no longer called from anywhere.
			// Deals are currently disabled for all affiliates.
			if (typeof BookingBuddy.loadAffiliateDeals !== 'undefined') {
				BookingBuddy.Deals.register(deal_options, config_options);
				BookingBuddy.Deals.init({ 'feedUrl': 'BBDeals.getTopDeals' });
			}
		}
	}, 

	/**
	 * Initialize forms
	 * Settings for a one search - one window form
	 *
	 */
	forms: function() {
		$j(".smartForm.bbSearch").each( function() { 
			$j(this).smartFormFactory( {  
				submitTarget: "_blank",
				submitHandler: function(form, target, e) {
					BookingBuddy.bbSubmitHandlers.submitForm(form, target, e);
				}
			});

			$j.dhtmlDropdown.utils.oneSearch($j(this), $j('#BBAdDropdown'));
			$j('#BBAdDropdown').dhtmlDropdownFactory(BookingBuddy.dropdownOptions, {
				tpt_premium: BookingBuddy.Strings.DHTMLDropdown.PremiumAdTemplate,
				tpt_basic: BookingBuddy.Strings.DHTMLDropdown.BasicAdTemplate,
				tpt_placeholder: BookingBuddy.Strings.DHTMLDropdown.PlaceholderAdTemplate
			});
		});

	}

};

$j(document).ready(function() {
	// Common object initializations
	var settings = {};

	settings.searchMode 		= $j("#searchModeName")[0] ? $j("#searchModeName").html() : "";
	settings.domain 		= $j("#siteDomainBase")[0] ? $j("#siteDomainBase").html() : "";
	settings.affiliateName 		= $j("#affiliateName")[0] ? $j("#affiliateName").html() : "";
	settings.allowFramed 		= $j("#allow_framed").length > 0;
	settings.redirectUrl 		= $j('#siteRedirectUrl')[0] ? $j('#siteRedirectUrl').text() : '';
	settings.taCheckRatesMCID 	= $j('#TACheckRatesMCID')[0] ? $j('#TACheckRatesMCID').text() : '';
	settings.taHotelPhotosMCID 	= $j('#TAHotelPhotosMCID')[0] ? $j('#TAHotelPhotosMCID').text() : '';
	settings.taReviewsMCID		= $j('#TAReviewsMCID')[0] ? $j('#TAReviewsMCID').text() : '';

	BookingBuddy.init(settings);

	BookingBuddy.User.init();

	// load cookied data
	$j.smartFormsBBSavedSearchData = BBSavedSearchData.singleton();


	/**
	 *  Generic Settings
	 */

	//  Activate 'toggler' plugin
	$j(".toggle").toggler();

	// Hook up the stop clicks
	$j(".stopClick").live('click', function(e) {
		e.stopPropagation();
		e.preventDefault();
		$j(e.currentElement).blur();
		return false;
	});


	// Disable cut/paste where marked
	$j(".noCopy").live('cut copy paste', function(e) {
		e.stopPropagation();
		e.preventDefault();
		return false;
	});

	// initialize modules based on affiliate
	switch (BookingBuddy.affiliateName) {
		case 'bookingbuddy_com':
		case 'cheapflightsomg_com':
		case 'cheaphotelsomg_com':
		case 'cheapoair_com':
		case 'vayama_com':
		case 'tripzen':
		case 'bootsnall':
		case 'skyauction':
		case 'ifly_com':
		case 'travelmath':
		case 'webjet':
		case 'onetravel':
		case 'airticketsdirect':
			BBInit.all();
			break;

		case 'bookingbuddy_co_uk':
			BBUKInit.all();
			break;

		case 'world_travel_guide':
		case 'uk_holiday_weather':
		case 'uk_airfaresflights':
		case 'holidaywatchdog':
			BBAffInit.allUK();
			break;

		default: // affiliates
			BBAffInit.all();
			break;
	}

	/**
	 * Settings for a all smart forms not already initialized, by setting the second argument
	 * to $.smartFormFactory to TRUE, the options passed in will NOT override any smartForms already
	 * created.
	 */
	$j(".smartForm").each( function() {
		$j(this).smartFormFactory( {
			submitHandler: function(form, target, e) {
				BookingBuddy.submitHandlers.defaultHandler(form, target, e);
			}
		}, true);
	});

	// Hook up IPLoc fields
	$j("input.ipLocAirport:visible").each(function() {
		var af_redirect = BookingBuddy.affiliateName == 'bookingbuddy_com' || BookingBuddy.affiliateName == 'bookingbuddy_co_uk';
		BBIPLocation.populate('airport', $j(this).attr('id'), false, af_redirect);
	});

	// If we are on the hotel listings page, we need to share the arrival city value from the check rates form
	if($j("#hotel_reviews_container.hotel_listings").length > 0) {
		$j("#hotel_arrival_city").val($j("#hotelcheckrates_arrival_city").val());
	}

	/**
	 * Set up the FirstClick popup (if the requisite div exists)
	 */
	var $popunder = $j('#firstClickBBDPopunder');

	var showPopunder = (($j.browser.webkit !== undefined) || window.location.pathname != '/tabsearches.php');
	if ($popunder[0] && showPopunder) {
		$popunder.popUnderAd({});
	}
});

/**
 * Calls that load anything from servers we don't control go after
 * window.load so they do not prevent other handlers from being run.
 */
$j(window).load(function() {
	// find each div container that is meant to hold a DART ad and load in in an iframe
	$j('.ad_frame').each(function(index, elm) {
		var handler = new ContentFrame(elm);
		// Hide the content frame initially
		handler.load(false);
	});

	// find the iframe in each div container that is meant to hold a DART ad and
	// check the contents to determine if it should be shown
	$j('.ad_frame iframe').load(function(e) {
		var iframe = $j(e.currentTarget);

		// check for a div with class "hide_this_ad"
		if (iframe.contents().find(".hide_this_ad").length > 0) {
			return;
		}
		// check for a default image ending in "817-grey.gif"
		if (iframe.contents().find("img[src$='817-grey.gif']").length > 0) {
			return;
		}

		// resize the iframe to the size of the content returned within the iframe
		// NOTE: this is in a setTimeout call to get around an IE bug that otherwise
		// always returns 0 for the iframe content height
		setTimeout(function() {
			var height = iframe.contents().find("body").height();
			if (height > 0) {
				iframe.attr('height', height);
			}
		}, 0);

		// find the outer containing element and show it
		var container = iframe.closest('.dart_ad');
		if (container.length > 0) {
			container.show();
		}
	});
});


